@kubb/plugin-ts 5.0.0-beta.42 → 5.0.0-beta.64
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +244 -279
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +124 -110
- package/dist/index.js +242 -278
- package/dist/index.js.map +1 -1
- package/package.json +8 -16
- package/extension.yaml +0 -1080
- package/src/components/Enum.tsx +0 -83
- package/src/components/Type.tsx +0 -60
- package/src/constants.ts +0 -39
- package/src/factory.ts +0 -1003
- package/src/generators/typeGenerator.tsx +0 -294
- package/src/index.ts +0 -9
- package/src/plugin.ts +0 -92
- package/src/printers/functionPrinter.ts +0 -197
- package/src/printers/printerTs.ts +0 -326
- package/src/resolvers/resolverTs.ts +0 -69
- package/src/types.ts +0 -315
- package/src/utils.ts +0 -140
package/src/factory.ts
DELETED
|
@@ -1,1003 +0,0 @@
|
|
|
1
|
-
import { camelCase, pascalCase, screamingSnakeCase, snakeCase } from '@internals/utils'
|
|
2
|
-
import { ast } from '@kubb/core'
|
|
3
|
-
import ts from 'typescript'
|
|
4
|
-
import { OPTIONAL_ADDS_UNDEFINED } from './constants.ts'
|
|
5
|
-
|
|
6
|
-
const { SyntaxKind, factory } = ts
|
|
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
|
-
|
|
22
|
-
// https://ts-ast-viewer.com/
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* TypeScript AST modifiers for common keywords (async, export, const, static).
|
|
26
|
-
*/
|
|
27
|
-
export const modifiers = {
|
|
28
|
-
async: factory.createModifier(ts.SyntaxKind.AsyncKeyword),
|
|
29
|
-
export: factory.createModifier(ts.SyntaxKind.ExportKeyword),
|
|
30
|
-
const: factory.createModifier(ts.SyntaxKind.ConstKeyword),
|
|
31
|
-
static: factory.createModifier(ts.SyntaxKind.StaticKeyword),
|
|
32
|
-
} as const
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* TypeScript syntax kind constants for union, literal, and string types.
|
|
36
|
-
*/
|
|
37
|
-
export const syntaxKind = {
|
|
38
|
-
union: SyntaxKind.UnionType as 192,
|
|
39
|
-
literalType: SyntaxKind.LiteralType,
|
|
40
|
-
stringLiteral: SyntaxKind.StringLiteral,
|
|
41
|
-
} as const
|
|
42
|
-
|
|
43
|
-
function isNonNullable<T>(value: T | null | undefined): value is T {
|
|
44
|
-
return value !== null && value !== undefined
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function isValidIdentifier(str: string): boolean {
|
|
48
|
-
if (!str.length || str.trim() !== str) {
|
|
49
|
-
return false
|
|
50
|
-
}
|
|
51
|
-
|
|
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
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function propertyName(name: string | ts.PropertyName): ts.PropertyName {
|
|
70
|
-
if (typeof name === 'string') {
|
|
71
|
-
const isValid = isValidIdentifier(name)
|
|
72
|
-
return isValid ? factory.createIdentifier(name) : factory.createStringLiteral(name)
|
|
73
|
-
}
|
|
74
|
-
return name
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const questionToken = factory.createToken(ts.SyntaxKind.QuestionToken)
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Creates a question token for optional type annotations.
|
|
81
|
-
* Pass `true` to use the cached token, or provide a pre-created token.
|
|
82
|
-
*/
|
|
83
|
-
export function createQuestionToken(token?: boolean | ts.QuestionToken) {
|
|
84
|
-
if (!token) {
|
|
85
|
-
return undefined
|
|
86
|
-
}
|
|
87
|
-
if (token === true) {
|
|
88
|
-
return questionToken
|
|
89
|
-
}
|
|
90
|
-
return token
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Creates a TypeScript intersection type node from multiple type nodes.
|
|
95
|
-
* Returns the single node if only one is provided, or wraps in parentheses if requested.
|
|
96
|
-
*/
|
|
97
|
-
export function createIntersectionDeclaration({ nodes, withParentheses }: { nodes: Array<ts.TypeNode>; withParentheses?: boolean }): ts.TypeNode | null {
|
|
98
|
-
if (!nodes.length) {
|
|
99
|
-
return null
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (nodes.length === 1) {
|
|
103
|
-
return nodes[0] || null
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const node = factory.createIntersectionTypeNode(nodes)
|
|
107
|
-
|
|
108
|
-
if (withParentheses) {
|
|
109
|
-
return factory.createParenthesizedType(node)
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return node
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Creates a TypeScript array type node.
|
|
117
|
-
* Use `arrayType: 'array'` for bracket syntax (`T[]`), or `'generic'` for `Array<T>`.
|
|
118
|
-
*
|
|
119
|
-
* @example Array bracket syntax
|
|
120
|
-
* `createArrayDeclaration({ nodes: [stringType], arrayType: 'array' }) // → string[]`
|
|
121
|
-
*
|
|
122
|
-
* @example Generic Array syntax
|
|
123
|
-
* `createArrayDeclaration({ nodes: [stringType], arrayType: 'generic' }) // → Array<string>`
|
|
124
|
-
*/
|
|
125
|
-
export function createArrayDeclaration({ nodes, arrayType = 'array' }: { nodes: Array<ts.TypeNode>; arrayType?: 'array' | 'generic' }): ts.TypeNode | null {
|
|
126
|
-
if (!nodes.length) {
|
|
127
|
-
return factory.createTupleTypeNode([])
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
if (nodes.length === 1) {
|
|
131
|
-
const node = nodes[0]
|
|
132
|
-
if (!node) {
|
|
133
|
-
return null
|
|
134
|
-
}
|
|
135
|
-
if (arrayType === 'generic') {
|
|
136
|
-
return factory.createTypeReferenceNode(factory.createIdentifier('Array'), [node])
|
|
137
|
-
}
|
|
138
|
-
return factory.createArrayTypeNode(node)
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// For union types (multiple nodes), respect arrayType preference
|
|
142
|
-
const unionType = factory.createUnionTypeNode(nodes)
|
|
143
|
-
if (arrayType === 'generic') {
|
|
144
|
-
return factory.createTypeReferenceNode(factory.createIdentifier('Array'), [unionType])
|
|
145
|
-
}
|
|
146
|
-
// For array syntax with unions, we need parentheses: (string | number)[]
|
|
147
|
-
return factory.createArrayTypeNode(factory.createParenthesizedType(unionType))
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Minimum nodes length of 2
|
|
152
|
-
* @example Union type example
|
|
153
|
-
* `string | number`
|
|
154
|
-
*/
|
|
155
|
-
export function createUnionDeclaration({ nodes, withParentheses }: { nodes: Array<ts.TypeNode>; withParentheses?: boolean }): ts.TypeNode {
|
|
156
|
-
if (!nodes.length) {
|
|
157
|
-
return keywordTypeNodes.any
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
if (nodes.length === 1) {
|
|
161
|
-
return nodes[0] as ts.TypeNode
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const node = factory.createUnionTypeNode(nodes)
|
|
165
|
-
|
|
166
|
-
if (withParentheses) {
|
|
167
|
-
return factory.createParenthesizedType(node)
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
return node
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Creates a TypeScript property signature for object/interface members.
|
|
175
|
-
* Supports optional markers, readonly modifiers, and type annotations.
|
|
176
|
-
*/
|
|
177
|
-
export function createPropertySignature({
|
|
178
|
-
readOnly,
|
|
179
|
-
modifiers = [],
|
|
180
|
-
name,
|
|
181
|
-
questionToken,
|
|
182
|
-
type,
|
|
183
|
-
}: {
|
|
184
|
-
readOnly?: boolean
|
|
185
|
-
modifiers?: Array<ts.Modifier>
|
|
186
|
-
name: ts.PropertyName | string
|
|
187
|
-
questionToken?: ts.QuestionToken | boolean
|
|
188
|
-
type?: ts.TypeNode
|
|
189
|
-
}) {
|
|
190
|
-
return factory.createPropertySignature(
|
|
191
|
-
[...modifiers, readOnly ? factory.createToken(ts.SyntaxKind.ReadonlyKeyword) : undefined].filter(
|
|
192
|
-
(modifier): modifier is ts.Modifier => modifier !== undefined,
|
|
193
|
-
),
|
|
194
|
-
propertyName(name),
|
|
195
|
-
createQuestionToken(questionToken),
|
|
196
|
-
type,
|
|
197
|
-
)
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Creates a function parameter declaration with optional markers, rest parameters, and type annotations.
|
|
202
|
-
*/
|
|
203
|
-
export function createParameterSignature(
|
|
204
|
-
name: string | ts.BindingName,
|
|
205
|
-
{
|
|
206
|
-
modifiers,
|
|
207
|
-
dotDotDotToken,
|
|
208
|
-
questionToken,
|
|
209
|
-
type,
|
|
210
|
-
initializer,
|
|
211
|
-
}: {
|
|
212
|
-
decorators?: Array<ts.Decorator>
|
|
213
|
-
modifiers?: Array<ts.Modifier>
|
|
214
|
-
dotDotDotToken?: ts.DotDotDotToken
|
|
215
|
-
questionToken?: ts.QuestionToken | boolean
|
|
216
|
-
type?: ts.TypeNode
|
|
217
|
-
initializer?: ts.Expression
|
|
218
|
-
},
|
|
219
|
-
): ts.ParameterDeclaration {
|
|
220
|
-
return factory.createParameterDeclaration(modifiers, dotDotDotToken, name, createQuestionToken(questionToken), type, initializer)
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* Creates a JSDoc comment node from an array of comment strings.
|
|
225
|
-
* Returns null if no comments are provided.
|
|
226
|
-
*/
|
|
227
|
-
export function createJSDoc({ comments }: { comments: Array<string> }) {
|
|
228
|
-
if (!comments.length) {
|
|
229
|
-
return null
|
|
230
|
-
}
|
|
231
|
-
return factory.createJSDocComment(
|
|
232
|
-
factory.createNodeArray(
|
|
233
|
-
comments.map((comment, i) => {
|
|
234
|
-
if (i === comments.length - 1) {
|
|
235
|
-
return factory.createJSDocText(comment)
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
return factory.createJSDocText(`${comment}\n`)
|
|
239
|
-
}),
|
|
240
|
-
),
|
|
241
|
-
)
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* Attaches JSDoc comments to an AST node as synthetic leading comments.
|
|
246
|
-
* Filters out undefined comments before attaching.
|
|
247
|
-
*
|
|
248
|
-
* @see https://github.com/microsoft/TypeScript/issues/44151
|
|
249
|
-
*/
|
|
250
|
-
export function appendJSDocToNode<TNode extends ts.Node>({ node, comments }: { node: TNode; comments: Array<string | undefined> }) {
|
|
251
|
-
const filteredComments = comments.filter(Boolean)
|
|
252
|
-
|
|
253
|
-
if (!filteredComments.length) {
|
|
254
|
-
return node
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
const text = filteredComments.reduce((acc = '', comment = '') => {
|
|
258
|
-
return `${acc}\n * ${comment.replaceAll('*/', '*\\/')}`
|
|
259
|
-
}, '*')
|
|
260
|
-
|
|
261
|
-
// Use the node directly instead of spreading to avoid creating Unknown nodes
|
|
262
|
-
// TypeScript's addSyntheticLeadingComment accepts the node as-is
|
|
263
|
-
return ts.addSyntheticLeadingComment(node, ts.SyntaxKind.MultiLineCommentTrivia, `${text || '*'}\n`, true)
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* Creates a TypeScript index signature for dynamic property access.
|
|
268
|
-
* Defines the key type (default: `string`) and value type on an object.
|
|
269
|
-
*/
|
|
270
|
-
export function createIndexSignature(
|
|
271
|
-
type: ts.TypeNode,
|
|
272
|
-
{
|
|
273
|
-
modifiers,
|
|
274
|
-
indexName = 'key',
|
|
275
|
-
indexType = factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
|
|
276
|
-
}: {
|
|
277
|
-
indexName?: string
|
|
278
|
-
indexType?: ts.TypeNode
|
|
279
|
-
decorators?: Array<ts.Decorator>
|
|
280
|
-
modifiers?: Array<ts.Modifier>
|
|
281
|
-
} = {},
|
|
282
|
-
) {
|
|
283
|
-
return factory.createIndexSignature(modifiers, [createParameterSignature(indexName, { type: indexType })], type)
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* Creates a TypeScript type alias declaration with optional modifiers and type parameters.
|
|
288
|
-
*/
|
|
289
|
-
export function createTypeAliasDeclaration({
|
|
290
|
-
modifiers,
|
|
291
|
-
name,
|
|
292
|
-
typeParameters,
|
|
293
|
-
type,
|
|
294
|
-
}: {
|
|
295
|
-
modifiers?: Array<ts.Modifier>
|
|
296
|
-
name: string | ts.Identifier
|
|
297
|
-
typeParameters?: Array<ts.TypeParameterDeclaration>
|
|
298
|
-
type: ts.TypeNode
|
|
299
|
-
}) {
|
|
300
|
-
return factory.createTypeAliasDeclaration(modifiers, name, typeParameters, type)
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
/**
|
|
304
|
-
* Creates a TypeScript interface declaration with optional modifiers, type parameters, and members.
|
|
305
|
-
*/
|
|
306
|
-
export function createInterfaceDeclaration({
|
|
307
|
-
modifiers,
|
|
308
|
-
name,
|
|
309
|
-
typeParameters,
|
|
310
|
-
members,
|
|
311
|
-
}: {
|
|
312
|
-
modifiers?: Array<ts.Modifier>
|
|
313
|
-
name: string | ts.Identifier
|
|
314
|
-
typeParameters?: Array<ts.TypeParameterDeclaration>
|
|
315
|
-
members: Array<ts.TypeElement>
|
|
316
|
-
}) {
|
|
317
|
-
return factory.createInterfaceDeclaration(modifiers, name, typeParameters, undefined, members)
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* Creates a TypeScript type declaration as either a type alias or interface.
|
|
322
|
-
* Intelligently selects the syntax based on the type structure and attaches JSDoc comments.
|
|
323
|
-
*/
|
|
324
|
-
export function createTypeDeclaration({
|
|
325
|
-
syntax,
|
|
326
|
-
isExportable,
|
|
327
|
-
comments,
|
|
328
|
-
name,
|
|
329
|
-
type,
|
|
330
|
-
}: {
|
|
331
|
-
syntax: 'type' | 'interface'
|
|
332
|
-
comments: Array<string | undefined>
|
|
333
|
-
isExportable?: boolean
|
|
334
|
-
name: string | ts.Identifier
|
|
335
|
-
type: ts.TypeNode
|
|
336
|
-
}) {
|
|
337
|
-
if (syntax === 'interface' && 'members' in type) {
|
|
338
|
-
const node = createInterfaceDeclaration({
|
|
339
|
-
members: [...(type as ts.TypeLiteralNode).members],
|
|
340
|
-
modifiers: isExportable ? [modifiers.export] : [],
|
|
341
|
-
name,
|
|
342
|
-
typeParameters: undefined,
|
|
343
|
-
})
|
|
344
|
-
|
|
345
|
-
return appendJSDocToNode({
|
|
346
|
-
node,
|
|
347
|
-
comments,
|
|
348
|
-
})
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
const node = createTypeAliasDeclaration({
|
|
352
|
-
type,
|
|
353
|
-
modifiers: isExportable ? [modifiers.export] : [],
|
|
354
|
-
name,
|
|
355
|
-
typeParameters: undefined,
|
|
356
|
-
})
|
|
357
|
-
|
|
358
|
-
return appendJSDocToNode({
|
|
359
|
-
node,
|
|
360
|
-
comments,
|
|
361
|
-
})
|
|
362
|
-
}
|
|
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
|
-
/**
|
|
377
|
-
* Creates an import declaration with support for default imports, named imports, namespace imports, and type-only imports.
|
|
378
|
-
* Optionally rename imported members with `propertyName` and `name` pairs.
|
|
379
|
-
*
|
|
380
|
-
* @example Default import
|
|
381
|
-
* `import Pet from './Pet'`
|
|
382
|
-
*
|
|
383
|
-
* @example Named imports with rename
|
|
384
|
-
* `import { Pet as Cat } from './Pet'`
|
|
385
|
-
*
|
|
386
|
-
* @example Namespace import
|
|
387
|
-
* `import * as Pet from './Pet'`
|
|
388
|
-
*/
|
|
389
|
-
export function createImportDeclaration({
|
|
390
|
-
name,
|
|
391
|
-
path,
|
|
392
|
-
isTypeOnly = false,
|
|
393
|
-
isNameSpace = false,
|
|
394
|
-
}: {
|
|
395
|
-
name: string | Array<string | { propertyName: string; name?: string }>
|
|
396
|
-
path: string
|
|
397
|
-
isTypeOnly?: boolean
|
|
398
|
-
isNameSpace?: boolean
|
|
399
|
-
}) {
|
|
400
|
-
if (!Array.isArray(name)) {
|
|
401
|
-
const importPropertyName = isNameSpace ? undefined : factory.createIdentifier(name)
|
|
402
|
-
const importName = isNameSpace ? factory.createNamespaceImport(factory.createIdentifier(name)) : undefined
|
|
403
|
-
|
|
404
|
-
return factory.createImportDeclaration(
|
|
405
|
-
undefined,
|
|
406
|
-
factory.createImportClause(isTypeOnly, importPropertyName, importName),
|
|
407
|
-
factory.createStringLiteral(path),
|
|
408
|
-
undefined,
|
|
409
|
-
)
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
// Sort the imports alphabetically for consistent output across platforms
|
|
413
|
-
const sortedName = name.toSorted((a, b) => compareStrings(typeof a === 'object' ? a.propertyName : a, typeof b === 'object' ? b.propertyName : b))
|
|
414
|
-
|
|
415
|
-
return factory.createImportDeclaration(
|
|
416
|
-
undefined,
|
|
417
|
-
factory.createImportClause(
|
|
418
|
-
isTypeOnly,
|
|
419
|
-
undefined,
|
|
420
|
-
factory.createNamedImports(
|
|
421
|
-
sortedName.map((item) => {
|
|
422
|
-
if (typeof item === 'object') {
|
|
423
|
-
const obj = item as { propertyName: string; name?: string }
|
|
424
|
-
if (obj.name) {
|
|
425
|
-
return factory.createImportSpecifier(false, factory.createIdentifier(obj.propertyName), factory.createIdentifier(obj.name))
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
return factory.createImportSpecifier(false, undefined, factory.createIdentifier(obj.propertyName))
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
return factory.createImportSpecifier(false, undefined, factory.createIdentifier(item))
|
|
432
|
-
}),
|
|
433
|
-
),
|
|
434
|
-
),
|
|
435
|
-
factory.createStringLiteral(path),
|
|
436
|
-
undefined,
|
|
437
|
-
)
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
/**
|
|
441
|
-
* Creates an export declaration with support for named exports, namespace exports, and type-only exports.
|
|
442
|
-
* Sorts export names alphabetically for consistent output across platforms.
|
|
443
|
-
*/
|
|
444
|
-
export function createExportDeclaration({
|
|
445
|
-
path,
|
|
446
|
-
asAlias,
|
|
447
|
-
isTypeOnly = false,
|
|
448
|
-
name,
|
|
449
|
-
}: {
|
|
450
|
-
path: string
|
|
451
|
-
asAlias?: boolean
|
|
452
|
-
isTypeOnly?: boolean
|
|
453
|
-
name?: string | Array<ts.Identifier | string>
|
|
454
|
-
}) {
|
|
455
|
-
if (name && !Array.isArray(name) && !asAlias) {
|
|
456
|
-
console.warn(`When using name as string, asAlias should be true ${name}`)
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
if (!Array.isArray(name)) {
|
|
460
|
-
const parsedName = name?.match(/^\d/) ? `_${name?.slice(1)}` : name
|
|
461
|
-
|
|
462
|
-
return factory.createExportDeclaration(
|
|
463
|
-
undefined,
|
|
464
|
-
isTypeOnly,
|
|
465
|
-
asAlias && parsedName ? factory.createNamespaceExport(factory.createIdentifier(parsedName)) : undefined,
|
|
466
|
-
factory.createStringLiteral(path),
|
|
467
|
-
undefined,
|
|
468
|
-
)
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
// Sort the exports alphabetically for consistent output across platforms
|
|
472
|
-
const sortedName = name.toSorted((a, b) => compareStrings(typeof a === 'string' ? a : a.text, typeof b === 'string' ? b : b.text))
|
|
473
|
-
|
|
474
|
-
return factory.createExportDeclaration(
|
|
475
|
-
undefined,
|
|
476
|
-
isTypeOnly,
|
|
477
|
-
factory.createNamedExports(
|
|
478
|
-
sortedName.map((propertyName) => {
|
|
479
|
-
return factory.createExportSpecifier(false, undefined, typeof propertyName === 'string' ? factory.createIdentifier(propertyName) : propertyName)
|
|
480
|
-
}),
|
|
481
|
-
),
|
|
482
|
-
factory.createStringLiteral(path),
|
|
483
|
-
undefined,
|
|
484
|
-
)
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
/**
|
|
488
|
-
* Apply casing transformation to enum keys
|
|
489
|
-
*/
|
|
490
|
-
function applyEnumKeyCasing(key: string, casing: 'screamingSnakeCase' | 'snakeCase' | 'pascalCase' | 'camelCase' | 'none' = 'none'): string {
|
|
491
|
-
if (casing === 'none') {
|
|
492
|
-
return key
|
|
493
|
-
}
|
|
494
|
-
if (casing === 'screamingSnakeCase') {
|
|
495
|
-
return screamingSnakeCase(key)
|
|
496
|
-
}
|
|
497
|
-
if (casing === 'snakeCase') {
|
|
498
|
-
return snakeCase(key)
|
|
499
|
-
}
|
|
500
|
-
if (casing === 'pascalCase') {
|
|
501
|
-
return pascalCase(key)
|
|
502
|
-
}
|
|
503
|
-
if (casing === 'camelCase') {
|
|
504
|
-
return camelCase(key)
|
|
505
|
-
}
|
|
506
|
-
return key
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
/**
|
|
510
|
-
* Creates a TypeScript enum declaration or equivalent construct in various formats.
|
|
511
|
-
* Returns a tuple of [name node, type node] - name node may be undefined for certain types.
|
|
512
|
-
*
|
|
513
|
-
* @example
|
|
514
|
-
* ```ts
|
|
515
|
-
* const [name, type] = createEnumDeclaration({
|
|
516
|
-
* type: 'enum',
|
|
517
|
-
* name: 'petType',
|
|
518
|
-
* typeName: 'PetType',
|
|
519
|
-
* enums: [['cat', 'cat'], ['dog', 'dog']],
|
|
520
|
-
* })
|
|
521
|
-
* ```
|
|
522
|
-
*/
|
|
523
|
-
export function createEnumDeclaration({
|
|
524
|
-
type = 'enum',
|
|
525
|
-
name,
|
|
526
|
-
typeName,
|
|
527
|
-
enums,
|
|
528
|
-
enumKeyCasing = 'none',
|
|
529
|
-
}: {
|
|
530
|
-
/**
|
|
531
|
-
* Choose to use `enum`, `asConst`, `asPascalConst`, `constEnum`, or `literal` for enums.
|
|
532
|
-
* - `enum`: TypeScript enum
|
|
533
|
-
* - `asConst`: const with camelCase name (e.g., `petType`)
|
|
534
|
-
* - `asPascalConst`: const with PascalCase name (e.g., `PetType`)
|
|
535
|
-
* - `constEnum`: const enum
|
|
536
|
-
* - `literal`: literal union type
|
|
537
|
-
* @default `'enum'`
|
|
538
|
-
*/
|
|
539
|
-
type?: 'enum' | 'asConst' | 'asPascalConst' | 'constEnum' | 'literal' | 'inlineLiteral'
|
|
540
|
-
/**
|
|
541
|
-
* Enum name in camelCase.
|
|
542
|
-
*/
|
|
543
|
-
name: string
|
|
544
|
-
/**
|
|
545
|
-
* Enum name in PascalCase.
|
|
546
|
-
*/
|
|
547
|
-
typeName: string
|
|
548
|
-
enums: Array<[key: string | number, value: string | number | boolean]>
|
|
549
|
-
/**
|
|
550
|
-
* Choose the casing for enum key names.
|
|
551
|
-
* @default 'none'
|
|
552
|
-
*/
|
|
553
|
-
enumKeyCasing?: 'screamingSnakeCase' | 'snakeCase' | 'pascalCase' | 'camelCase' | 'none'
|
|
554
|
-
}): [name: ts.Node | undefined, type: ts.Node] {
|
|
555
|
-
if (type === 'literal' || type === 'inlineLiteral') {
|
|
556
|
-
return [
|
|
557
|
-
undefined,
|
|
558
|
-
factory.createTypeAliasDeclaration(
|
|
559
|
-
[factory.createToken(ts.SyntaxKind.ExportKeyword)],
|
|
560
|
-
factory.createIdentifier(typeName),
|
|
561
|
-
undefined,
|
|
562
|
-
factory.createUnionTypeNode(
|
|
563
|
-
enums
|
|
564
|
-
.map(([_key, value]) => {
|
|
565
|
-
if (isNumber(value)) {
|
|
566
|
-
if (value < 0) {
|
|
567
|
-
return factory.createLiteralTypeNode(
|
|
568
|
-
factory.createPrefixUnaryExpression(ts.SyntaxKind.MinusToken, factory.createNumericLiteral(Math.abs(value))),
|
|
569
|
-
)
|
|
570
|
-
}
|
|
571
|
-
return factory.createLiteralTypeNode(factory.createNumericLiteral(value?.toString()))
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
if (typeof value === 'boolean') {
|
|
575
|
-
return factory.createLiteralTypeNode(value ? factory.createTrue() : factory.createFalse())
|
|
576
|
-
}
|
|
577
|
-
if (value) {
|
|
578
|
-
return factory.createLiteralTypeNode(factory.createStringLiteral(value.toString()))
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
return undefined
|
|
582
|
-
})
|
|
583
|
-
.filter((node): node is ts.LiteralTypeNode => node !== undefined),
|
|
584
|
-
),
|
|
585
|
-
),
|
|
586
|
-
]
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
if (type === 'enum' || type === 'constEnum') {
|
|
590
|
-
return [
|
|
591
|
-
undefined,
|
|
592
|
-
factory.createEnumDeclaration(
|
|
593
|
-
[factory.createToken(ts.SyntaxKind.ExportKeyword), type === 'constEnum' ? factory.createToken(ts.SyntaxKind.ConstKeyword) : undefined].filter(
|
|
594
|
-
(modifier): modifier is ts.ModifierToken<ts.SyntaxKind.ExportKeyword> | ts.ModifierToken<ts.SyntaxKind.ConstKeyword> => modifier !== undefined,
|
|
595
|
-
),
|
|
596
|
-
factory.createIdentifier(typeName),
|
|
597
|
-
enums
|
|
598
|
-
.map(([key, value]) => {
|
|
599
|
-
let initializer: ts.Expression = factory.createStringLiteral(value?.toString())
|
|
600
|
-
const isExactNumber = Number.parseInt(value.toString(), 10) === value
|
|
601
|
-
|
|
602
|
-
if (isExactNumber && isNumber(Number.parseInt(value.toString(), 10))) {
|
|
603
|
-
if ((value as number) < 0) {
|
|
604
|
-
initializer = factory.createPrefixUnaryExpression(ts.SyntaxKind.MinusToken, factory.createNumericLiteral(Math.abs(value as number)))
|
|
605
|
-
} else {
|
|
606
|
-
initializer = factory.createNumericLiteral(value as number)
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
if (typeof value === 'boolean') {
|
|
611
|
-
initializer = value ? factory.createTrue() : factory.createFalse()
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
if (isNumber(Number.parseInt(key.toString(), 10))) {
|
|
615
|
-
const casingKey = applyEnumKeyCasing(`${typeName}_${key}`, enumKeyCasing)
|
|
616
|
-
return factory.createEnumMember(propertyName(casingKey), initializer)
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
if (key) {
|
|
620
|
-
const casingKey = applyEnumKeyCasing(key.toString(), enumKeyCasing)
|
|
621
|
-
return factory.createEnumMember(propertyName(casingKey), initializer)
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
return undefined
|
|
625
|
-
})
|
|
626
|
-
.filter((member): member is ts.EnumMember => member !== undefined),
|
|
627
|
-
),
|
|
628
|
-
]
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
// used when using `as const` instead of an TypeScript enum.
|
|
632
|
-
// name is already PascalCase for asPascalConst and camelCase for asConst (set in Type.tsx)
|
|
633
|
-
// typeName has the Key suffix for type alias, so we use name for the const identifier
|
|
634
|
-
const identifierName = name
|
|
635
|
-
|
|
636
|
-
// When there are no enum items (empty or all-null enum), don't generate a runtime const.
|
|
637
|
-
// Return undefined for nameNode so the barrel won't try to export a non-existent symbol.
|
|
638
|
-
// Use `never` as the type alias to keep references valid without creating a broken const.
|
|
639
|
-
if (enums.length === 0) {
|
|
640
|
-
return [
|
|
641
|
-
undefined,
|
|
642
|
-
factory.createTypeAliasDeclaration(
|
|
643
|
-
[factory.createToken(ts.SyntaxKind.ExportKeyword)],
|
|
644
|
-
factory.createIdentifier(typeName),
|
|
645
|
-
undefined,
|
|
646
|
-
factory.createKeywordTypeNode(ts.SyntaxKind.NeverKeyword),
|
|
647
|
-
),
|
|
648
|
-
]
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
return [
|
|
652
|
-
factory.createVariableStatement(
|
|
653
|
-
[factory.createToken(ts.SyntaxKind.ExportKeyword)],
|
|
654
|
-
factory.createVariableDeclarationList(
|
|
655
|
-
[
|
|
656
|
-
factory.createVariableDeclaration(
|
|
657
|
-
factory.createIdentifier(identifierName),
|
|
658
|
-
undefined,
|
|
659
|
-
undefined,
|
|
660
|
-
factory.createAsExpression(
|
|
661
|
-
factory.createObjectLiteralExpression(
|
|
662
|
-
enums
|
|
663
|
-
.map(([key, value]) => {
|
|
664
|
-
let initializer: ts.Expression = factory.createStringLiteral(value?.toString())
|
|
665
|
-
|
|
666
|
-
if (isNumber(value)) {
|
|
667
|
-
// Error: Negative numbers should be created in combination with createPrefixUnaryExpression factory.
|
|
668
|
-
// The method createNumericLiteral only accepts positive numbers
|
|
669
|
-
// or those combined with createPrefixUnaryExpression.
|
|
670
|
-
// Therefore, we need to ensure that the number is not negative.
|
|
671
|
-
if (value < 0) {
|
|
672
|
-
initializer = factory.createPrefixUnaryExpression(ts.SyntaxKind.MinusToken, factory.createNumericLiteral(Math.abs(value)))
|
|
673
|
-
} else {
|
|
674
|
-
initializer = factory.createNumericLiteral(value)
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
if (typeof value === 'boolean') {
|
|
679
|
-
initializer = value ? factory.createTrue() : factory.createFalse()
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
if (key) {
|
|
683
|
-
const casingKey = applyEnumKeyCasing(key.toString(), enumKeyCasing)
|
|
684
|
-
return factory.createPropertyAssignment(propertyName(casingKey), initializer)
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
return undefined
|
|
688
|
-
})
|
|
689
|
-
.filter((property): property is ts.PropertyAssignment => property !== undefined),
|
|
690
|
-
true,
|
|
691
|
-
),
|
|
692
|
-
factory.createTypeReferenceNode(factory.createIdentifier('const'), undefined),
|
|
693
|
-
),
|
|
694
|
-
),
|
|
695
|
-
],
|
|
696
|
-
ts.NodeFlags.Const,
|
|
697
|
-
),
|
|
698
|
-
),
|
|
699
|
-
factory.createTypeAliasDeclaration(
|
|
700
|
-
[factory.createToken(ts.SyntaxKind.ExportKeyword)],
|
|
701
|
-
factory.createIdentifier(typeName),
|
|
702
|
-
undefined,
|
|
703
|
-
factory.createIndexedAccessTypeNode(
|
|
704
|
-
factory.createParenthesizedType(factory.createTypeQueryNode(factory.createIdentifier(identifierName), undefined)),
|
|
705
|
-
factory.createTypeOperatorNode(ts.SyntaxKind.KeyOfKeyword, factory.createTypeQueryNode(factory.createIdentifier(identifierName), undefined)),
|
|
706
|
-
),
|
|
707
|
-
),
|
|
708
|
-
]
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
/**
|
|
712
|
-
* Creates a TypeScript `Omit<T, Keys>` type reference node.
|
|
713
|
-
* Optionally wraps the type in `NonNullable<T>` if `nonNullable` is true.
|
|
714
|
-
*/
|
|
715
|
-
export function createOmitDeclaration({ keys, type, nonNullable }: { keys: Array<string> | string; type: ts.TypeNode; nonNullable?: boolean }) {
|
|
716
|
-
const node = nonNullable ? factory.createTypeReferenceNode(factory.createIdentifier('NonNullable'), [type]) : type
|
|
717
|
-
|
|
718
|
-
if (Array.isArray(keys)) {
|
|
719
|
-
return factory.createTypeReferenceNode(factory.createIdentifier('Omit'), [
|
|
720
|
-
node,
|
|
721
|
-
factory.createUnionTypeNode(
|
|
722
|
-
keys.map((key) => {
|
|
723
|
-
return factory.createLiteralTypeNode(factory.createStringLiteral(key))
|
|
724
|
-
}),
|
|
725
|
-
),
|
|
726
|
-
])
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
return factory.createTypeReferenceNode(factory.createIdentifier('Omit'), [node, factory.createLiteralTypeNode(factory.createStringLiteral(keys))])
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
/**
|
|
733
|
-
* Pre-built TypeScript keyword type nodes for common primitive types.
|
|
734
|
-
* Use these to avoid repeatedly creating the same type nodes.
|
|
735
|
-
*/
|
|
736
|
-
export const keywordTypeNodes = {
|
|
737
|
-
any: factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword),
|
|
738
|
-
unknown: factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword),
|
|
739
|
-
void: factory.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword),
|
|
740
|
-
number: factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword),
|
|
741
|
-
integer: factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword),
|
|
742
|
-
bigint: factory.createKeywordTypeNode(ts.SyntaxKind.BigIntKeyword),
|
|
743
|
-
object: factory.createKeywordTypeNode(ts.SyntaxKind.ObjectKeyword),
|
|
744
|
-
string: factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
|
|
745
|
-
boolean: factory.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword),
|
|
746
|
-
undefined: factory.createKeywordTypeNode(ts.SyntaxKind.UndefinedKeyword),
|
|
747
|
-
null: factory.createLiteralTypeNode(factory.createToken(ts.SyntaxKind.NullKeyword)),
|
|
748
|
-
never: factory.createKeywordTypeNode(ts.SyntaxKind.NeverKeyword),
|
|
749
|
-
} as const
|
|
750
|
-
|
|
751
|
-
/**
|
|
752
|
-
* Converts a path like '/pet/{petId}/uploadImage' to a template literal type
|
|
753
|
-
* like `/pet/${string}/uploadImage`
|
|
754
|
-
*/
|
|
755
|
-
/**
|
|
756
|
-
* Converts an OAS-style path (e.g. `/pets/{petId}`) or an Express-style path
|
|
757
|
-
* (e.g. `/pets/:petId`) to a TypeScript template literal type
|
|
758
|
-
* like `` `/pets/${string}` ``.
|
|
759
|
-
*/
|
|
760
|
-
export function createUrlTemplateType(path: string): ts.TypeNode {
|
|
761
|
-
// normalized Express `:param` → OAS `{param}` so a single regex handles both.
|
|
762
|
-
const normalized = path.replace(/:([^/]+)/g, '{$1}')
|
|
763
|
-
|
|
764
|
-
if (!normalized.includes('{')) {
|
|
765
|
-
return factory.createLiteralTypeNode(factory.createStringLiteral(normalized))
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
const segments = normalized.split(/(\{[^}]+\})/)
|
|
769
|
-
const parts: Array<string> = []
|
|
770
|
-
const parameterIndices: Array<number> = []
|
|
771
|
-
|
|
772
|
-
segments.forEach((segment) => {
|
|
773
|
-
if (segment.startsWith('{') && segment.endsWith('}')) {
|
|
774
|
-
parameterIndices.push(parts.length)
|
|
775
|
-
parts.push(segment)
|
|
776
|
-
} else if (segment) {
|
|
777
|
-
parts.push(segment)
|
|
778
|
-
}
|
|
779
|
-
})
|
|
780
|
-
|
|
781
|
-
const head = ts.factory.createTemplateHead(parts[0] || '')
|
|
782
|
-
const templateSpans: Array<ts.TemplateLiteralTypeSpan> = []
|
|
783
|
-
|
|
784
|
-
parameterIndices.forEach((paramIndex, i) => {
|
|
785
|
-
const isLast = i === parameterIndices.length - 1
|
|
786
|
-
const nextPart = parts[paramIndex + 1] || ''
|
|
787
|
-
const literal = isLast ? ts.factory.createTemplateTail(nextPart) : ts.factory.createTemplateMiddle(nextPart)
|
|
788
|
-
templateSpans.push(ts.factory.createTemplateLiteralTypeSpan(keywordTypeNodes.string, literal))
|
|
789
|
-
})
|
|
790
|
-
|
|
791
|
-
return ts.factory.createTemplateLiteralType(head, templateSpans)
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
/**
|
|
795
|
-
* Creates a TypeScript type literal node (anonymous object type).
|
|
796
|
-
*/
|
|
797
|
-
export const createTypeLiteralNode = factory.createTypeLiteralNode
|
|
798
|
-
|
|
799
|
-
/**
|
|
800
|
-
* Creates a TypeScript type reference node (e.g., `Array<string>`, `Record<K, V>`).
|
|
801
|
-
*/
|
|
802
|
-
export const createTypeReferenceNode = factory.createTypeReferenceNode
|
|
803
|
-
|
|
804
|
-
/**
|
|
805
|
-
* Creates a numeric literal type node.
|
|
806
|
-
*/
|
|
807
|
-
export const createNumericLiteral = factory.createNumericLiteral
|
|
808
|
-
|
|
809
|
-
/**
|
|
810
|
-
* Creates a string literal type node.
|
|
811
|
-
*/
|
|
812
|
-
export const createStringLiteral = factory.createStringLiteral
|
|
813
|
-
|
|
814
|
-
/**
|
|
815
|
-
* Creates an array type node (e.g., `T[]`).
|
|
816
|
-
*/
|
|
817
|
-
export const createArrayTypeNode = factory.createArrayTypeNode
|
|
818
|
-
|
|
819
|
-
/**
|
|
820
|
-
* Creates a parenthesized type node to control operator precedence.
|
|
821
|
-
*/
|
|
822
|
-
export const createParenthesizedType = factory.createParenthesizedType
|
|
823
|
-
|
|
824
|
-
/**
|
|
825
|
-
* Creates a literal type node (e.g., `'hello'`, `42`, `true`).
|
|
826
|
-
*/
|
|
827
|
-
export const createLiteralTypeNode = factory.createLiteralTypeNode
|
|
828
|
-
|
|
829
|
-
/**
|
|
830
|
-
* Creates a null literal type node.
|
|
831
|
-
*/
|
|
832
|
-
export const createNull = factory.createNull
|
|
833
|
-
|
|
834
|
-
/**
|
|
835
|
-
* Creates an identifier node.
|
|
836
|
-
*/
|
|
837
|
-
export const createIdentifier = factory.createIdentifier
|
|
838
|
-
|
|
839
|
-
/**
|
|
840
|
-
* Creates an optional type node (e.g., `T | undefined`).
|
|
841
|
-
*/
|
|
842
|
-
export const createOptionalTypeNode = factory.createOptionalTypeNode
|
|
843
|
-
|
|
844
|
-
/**
|
|
845
|
-
* Creates a tuple type node (e.g., `[string, number]`).
|
|
846
|
-
*/
|
|
847
|
-
export const createTupleTypeNode = factory.createTupleTypeNode
|
|
848
|
-
|
|
849
|
-
/**
|
|
850
|
-
* Creates a rest type node for variadic tuple elements (e.g., `...T[]`).
|
|
851
|
-
*/
|
|
852
|
-
export const createRestTypeNode = factory.createRestTypeNode
|
|
853
|
-
|
|
854
|
-
/**
|
|
855
|
-
* Creates a boolean true literal type node.
|
|
856
|
-
*/
|
|
857
|
-
export const createTrue = factory.createTrue
|
|
858
|
-
|
|
859
|
-
/**
|
|
860
|
-
* Creates a boolean false literal type node.
|
|
861
|
-
*/
|
|
862
|
-
export const createFalse = factory.createFalse
|
|
863
|
-
|
|
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
|
-
/**
|
|
875
|
-
* Creates a prefix unary expression (e.g., negative numbers, logical not).
|
|
876
|
-
*/
|
|
877
|
-
export const createPrefixUnaryExpression = factory.createPrefixUnaryExpression
|
|
878
|
-
|
|
879
|
-
/**
|
|
880
|
-
* Exports TypeScript SyntaxKind enum for AST node type checking.
|
|
881
|
-
*/
|
|
882
|
-
export { SyntaxKind }
|
|
883
|
-
|
|
884
|
-
// ─── Printer helpers ──────────────────────────────────────────────────────────
|
|
885
|
-
|
|
886
|
-
/**
|
|
887
|
-
* Converts a primitive const value to a TypeScript literal type node.
|
|
888
|
-
* Handles negative numbers via a prefix unary expression.
|
|
889
|
-
*/
|
|
890
|
-
export function constToTypeNode(value: string | number | boolean, format: 'string' | 'number' | 'boolean'): ts.TypeNode | undefined {
|
|
891
|
-
if (format === 'boolean') {
|
|
892
|
-
return createLiteralTypeNode(value === true ? createTrue() : createFalse())
|
|
893
|
-
}
|
|
894
|
-
if (format === 'number' && typeof value === 'number') {
|
|
895
|
-
if (value < 0) {
|
|
896
|
-
return createLiteralTypeNode(createPrefixUnaryExpression(SyntaxKind.MinusToken, createNumericLiteral(Math.abs(value))))
|
|
897
|
-
}
|
|
898
|
-
return createLiteralTypeNode(createNumericLiteral(value))
|
|
899
|
-
}
|
|
900
|
-
return createLiteralTypeNode(createStringLiteral(String(value)))
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
/**
|
|
904
|
-
* Returns a `Date` reference type node when `representation` is `'date'`, otherwise falls back to `string`.
|
|
905
|
-
*/
|
|
906
|
-
export function dateOrStringNode(node: { representation?: string }): ts.TypeNode {
|
|
907
|
-
return node.representation === 'date' ? createTypeReferenceNode(createIdentifier('Date')) : keywordTypeNodes.string
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
/**
|
|
911
|
-
* Maps an array of `SchemaNode`s through the printer, filtering out `null` and `undefined` results.
|
|
912
|
-
*/
|
|
913
|
-
export function buildMemberNodes(
|
|
914
|
-
members: Array<ast.SchemaNode> | undefined,
|
|
915
|
-
print: (node: ast.SchemaNode) => ts.TypeNode | null | undefined,
|
|
916
|
-
): Array<ts.TypeNode> {
|
|
917
|
-
return (members ?? []).map(print).filter(isNonNullable)
|
|
918
|
-
}
|
|
919
|
-
|
|
920
|
-
/**
|
|
921
|
-
* Builds a TypeScript tuple type node from an array schema's `items`,
|
|
922
|
-
* applying min/max slice and optional/rest element rules.
|
|
923
|
-
*/
|
|
924
|
-
export function buildTupleNode(node: ast.ArraySchemaNode, print: (node: ast.SchemaNode) => ts.TypeNode | null | undefined): ts.TypeNode | undefined {
|
|
925
|
-
let items = (node.items ?? []).map(print).filter(isNonNullable)
|
|
926
|
-
|
|
927
|
-
const restNode = node.rest ? (print(node.rest) ?? undefined) : undefined
|
|
928
|
-
const { min, max } = node
|
|
929
|
-
|
|
930
|
-
if (max !== undefined) {
|
|
931
|
-
items = items.slice(0, max)
|
|
932
|
-
if (items.length < max && restNode) {
|
|
933
|
-
items = [...items, ...Array(max - items.length).fill(restNode)]
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
if (min !== undefined) {
|
|
938
|
-
items = items.map((item, i) => (i >= min ? createOptionalTypeNode(item) : item))
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
if (max === undefined && restNode) {
|
|
942
|
-
items.push(createRestTypeNode(createArrayTypeNode(restNode)))
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
return createTupleTypeNode(items)
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
/**
|
|
949
|
-
* Applies `nullable` and optional/nullish `| undefined` union modifiers to a property's resolved base type.
|
|
950
|
-
*/
|
|
951
|
-
export function buildPropertyType(
|
|
952
|
-
schema: ast.SchemaNode,
|
|
953
|
-
baseType: ts.TypeNode,
|
|
954
|
-
optionalType: 'questionToken' | 'undefined' | 'questionTokenAndUndefined',
|
|
955
|
-
): ts.TypeNode {
|
|
956
|
-
const addsUndefined = OPTIONAL_ADDS_UNDEFINED.has(optionalType)
|
|
957
|
-
const meta = ast.syncSchemaRef(schema)
|
|
958
|
-
|
|
959
|
-
let type = baseType
|
|
960
|
-
|
|
961
|
-
if (meta.nullable) {
|
|
962
|
-
type = createUnionDeclaration({ nodes: [type, keywordTypeNodes.null] })
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
if ((meta.nullish || meta.optional) && addsUndefined) {
|
|
966
|
-
type = createUnionDeclaration({ nodes: [type, keywordTypeNodes.undefined] })
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
return type
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
/**
|
|
973
|
-
* Creates TypeScript index signatures for `additionalProperties` and `patternProperties` on an object schema node.
|
|
974
|
-
*/
|
|
975
|
-
export function buildIndexSignatures(
|
|
976
|
-
node: { additionalProperties?: ast.SchemaNode | boolean; patternProperties?: Record<string, ast.SchemaNode> },
|
|
977
|
-
propertyCount: number,
|
|
978
|
-
print: (node: ast.SchemaNode) => ts.TypeNode | null | undefined,
|
|
979
|
-
): Array<ts.TypeElement> {
|
|
980
|
-
const elements: Array<ts.TypeElement> = []
|
|
981
|
-
|
|
982
|
-
if (node.additionalProperties && node.additionalProperties !== true) {
|
|
983
|
-
const additionalType = print(node.additionalProperties) ?? keywordTypeNodes.unknown
|
|
984
|
-
|
|
985
|
-
elements.push(createIndexSignature(propertyCount > 0 ? keywordTypeNodes.unknown : additionalType))
|
|
986
|
-
} else if (node.additionalProperties === true) {
|
|
987
|
-
elements.push(createIndexSignature(keywordTypeNodes.unknown))
|
|
988
|
-
}
|
|
989
|
-
|
|
990
|
-
if (node.patternProperties) {
|
|
991
|
-
const first = Object.values(node.patternProperties)[0]
|
|
992
|
-
if (first) {
|
|
993
|
-
let patternType = print(first) ?? keywordTypeNodes.unknown
|
|
994
|
-
|
|
995
|
-
if (first.nullable) {
|
|
996
|
-
patternType = createUnionDeclaration({ nodes: [patternType, keywordTypeNodes.null] })
|
|
997
|
-
}
|
|
998
|
-
elements.push(createIndexSignature(patternType))
|
|
999
|
-
}
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
return elements
|
|
1003
|
-
}
|