@kubb/parser-ts 4.1.4 → 5.0.0-alpha.32

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.
@@ -0,0 +1,474 @@
1
+ import { normalize, relative } from 'node:path'
2
+ import type { ArrowFunctionNode, CodeNode, ConstNode, FileNode, FunctionNode, JSDocNode, SourceNode, TypeNode } from '@kubb/ast/types'
3
+ import type { Parser } from '@kubb/core'
4
+ import { defineParser } from '@kubb/core'
5
+ import ts from 'typescript'
6
+
7
+ const { factory } = ts
8
+
9
+ function slash(path: string): string {
10
+ return normalize(path).replaceAll(/\\/g, '/').replace('../', '')
11
+ }
12
+
13
+ function getRelativePath(rootDir: string, filePath: string): string {
14
+ const rel = relative(rootDir, filePath)
15
+ const slashed = slash(rel)
16
+ return slashed.startsWith('../') ? slashed : `./${slashed}`
17
+ }
18
+
19
+ function trimExtName(text: string): string {
20
+ return text.replace(/\.[^/.]+$/, '')
21
+ }
22
+
23
+ /**
24
+ * Validates TypeScript AST nodes before printing.
25
+ * Throws an error if any node has SyntaxKind.Unknown which would cause the
26
+ * TypeScript printer to crash.
27
+ */
28
+ export function validateNodes(...nodes: ts.Node[]): void {
29
+ for (const node of nodes) {
30
+ if (!node) {
31
+ throw new Error('Attempted to print undefined or null TypeScript node')
32
+ }
33
+ if (node.kind === ts.SyntaxKind.Unknown) {
34
+ throw new Error(
35
+ 'Invalid TypeScript AST node detected with SyntaxKind.Unknown. ' +
36
+ 'This typically indicates a schema pattern that could not be properly converted to TypeScript. ' +
37
+ `Node: ${JSON.stringify(node, null, 2)}`,
38
+ )
39
+ }
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Converts TypeScript/TSX AST nodes to a string using the TypeScript printer.
45
+ */
46
+ export function print(...elements: Array<ts.Node>): string {
47
+ const sourceFile = ts.createSourceFile('print.tsx', '', ts.ScriptTarget.ES2022, true, ts.ScriptKind.TSX)
48
+
49
+ const printer = ts.createPrinter({
50
+ omitTrailingSemicolon: true,
51
+ newLine: ts.NewLineKind.LineFeed,
52
+ removeComments: false,
53
+ noEmitHelpers: true,
54
+ })
55
+
56
+ const output = printer.printList(ts.ListFormat.MultiLine, factory.createNodeArray(elements.filter(Boolean)), sourceFile)
57
+
58
+ return output.replace(/\r\n/g, '\n')
59
+ }
60
+
61
+ /**
62
+ * Like `print` but validates nodes first to surface issues early.
63
+ */
64
+ export function safePrint(...elements: Array<ts.Node>): string {
65
+ validateNodes(...elements)
66
+ return print(...elements)
67
+ }
68
+
69
+ export function createImport({
70
+ name,
71
+ path,
72
+ root,
73
+ isTypeOnly = false,
74
+ isNameSpace = false,
75
+ }: {
76
+ name: string | Array<string | { propertyName: string; name?: string }>
77
+ path: string
78
+ root?: string
79
+ /** @default false */
80
+ isTypeOnly?: boolean
81
+ /** @default false */
82
+ isNameSpace?: boolean
83
+ }): ts.ImportDeclaration {
84
+ const resolvePath = root ? getRelativePath(root, path) : path
85
+
86
+ if (!Array.isArray(name)) {
87
+ if (isNameSpace) {
88
+ return factory.createImportDeclaration(
89
+ undefined,
90
+ factory.createImportClause(isTypeOnly, undefined, factory.createNamespaceImport(factory.createIdentifier(name))),
91
+ factory.createStringLiteral(resolvePath),
92
+ undefined,
93
+ )
94
+ }
95
+
96
+ return factory.createImportDeclaration(
97
+ undefined,
98
+ factory.createImportClause(isTypeOnly, factory.createIdentifier(name), undefined),
99
+ factory.createStringLiteral(resolvePath),
100
+ undefined,
101
+ )
102
+ }
103
+
104
+ const specifiers = name.map((item) => {
105
+ if (typeof item === 'object') {
106
+ const { propertyName, name: alias } = item
107
+ return factory.createImportSpecifier(false, alias ? factory.createIdentifier(propertyName) : undefined, factory.createIdentifier(alias ?? propertyName))
108
+ }
109
+ return factory.createImportSpecifier(false, undefined, factory.createIdentifier(item))
110
+ })
111
+
112
+ return factory.createImportDeclaration(
113
+ undefined,
114
+ factory.createImportClause(isTypeOnly, undefined, factory.createNamedImports(specifiers)),
115
+ factory.createStringLiteral(resolvePath),
116
+ undefined,
117
+ )
118
+ }
119
+
120
+ export function createExport({
121
+ path,
122
+ asAlias,
123
+ isTypeOnly = false,
124
+ name,
125
+ }: {
126
+ path: string
127
+ /** @default false */
128
+ asAlias?: boolean
129
+ /** @default false */
130
+ isTypeOnly?: boolean
131
+ name?: string | Array<ts.Identifier | string>
132
+ }): ts.ExportDeclaration {
133
+ if (name && !Array.isArray(name) && !asAlias) {
134
+ console.warn(`When using name as string, asAlias should be true: ${name}`)
135
+ }
136
+
137
+ if (!Array.isArray(name)) {
138
+ const parsedName = name?.match(/^\d/) ? `_${name?.slice(1)}` : name
139
+
140
+ return factory.createExportDeclaration(
141
+ undefined,
142
+ isTypeOnly,
143
+ asAlias && parsedName ? factory.createNamespaceExport(factory.createIdentifier(parsedName)) : undefined,
144
+ factory.createStringLiteral(path),
145
+ undefined,
146
+ )
147
+ }
148
+
149
+ return factory.createExportDeclaration(
150
+ undefined,
151
+ isTypeOnly,
152
+ factory.createNamedExports(
153
+ name.map((propertyName) =>
154
+ factory.createExportSpecifier(false, undefined, typeof propertyName === 'string' ? factory.createIdentifier(propertyName) : propertyName),
155
+ ),
156
+ ),
157
+ factory.createStringLiteral(path),
158
+ undefined,
159
+ )
160
+ }
161
+
162
+ /**
163
+ * Converts a {@link JSDocNode} to a JSDoc comment block string.
164
+ *
165
+ * @example
166
+ * ```ts
167
+ * printJSDoc({ comments: ['@description A pet', '@deprecated'] })
168
+ * // /**
169
+ * // * @description A pet
170
+ * // * @deprecated
171
+ * // *\/
172
+ * ```
173
+ */
174
+ export function printJSDoc(jsDoc: JSDocNode): string {
175
+ const comments = (jsDoc.comments ?? []).filter((c) => c != null)
176
+ if (comments.length === 0) return ''
177
+
178
+ const lines = comments
179
+ .flatMap((c) => c.split(/\r?\n/))
180
+ .map((l) => l.replace(/\*\//g, '* /').replace(/\r/g, ''))
181
+ .filter((l) => l.trim().length > 0)
182
+
183
+ if (lines.length === 0) return ''
184
+
185
+ return ['/**', ...lines.map((l) => ` * ${l}`), ' */'].join('\n')
186
+ }
187
+
188
+ /**
189
+ * Serialises the body / value content from a `nodes` array.
190
+ *
191
+ * Each element is either a raw string or a structured {@link CodeNode}
192
+ * (recursively converted via {@link printCodeNode}).
193
+ * Elements are joined with `\n`.
194
+ */
195
+ function printNodes(nodes: Array<CodeNode | string> | undefined): string {
196
+ if (!nodes || nodes.length === 0) return ''
197
+ return nodes.map((n) => (typeof n === 'string' ? n : printCodeNode(n))).join('\n')
198
+ }
199
+
200
+ /**
201
+ * Indents every non-empty line of `text` by `spaces` spaces.
202
+ */
203
+ function indentLines(text: string, spaces = 2): string {
204
+ if (!text) return ''
205
+ const pad = ' '.repeat(spaces)
206
+ return text
207
+ .split('\n')
208
+ .map((line) => (line.trim() ? `${pad}${line}` : ''))
209
+ .join('\n')
210
+ }
211
+
212
+ /**
213
+ * Converts a {@link ConstNode} to a TypeScript `const` declaration string.
214
+ *
215
+ * Mirrors the `Const` component from `@kubb/react-fabric`.
216
+ *
217
+ * @example
218
+ * ```ts
219
+ * printConst(createConst({ name: 'pet', export: true, nodes: ['{}'] }))
220
+ * // 'export const pet = {}'
221
+ * ```
222
+ *
223
+ * @example With type and `as const`
224
+ * ```ts
225
+ * printConst(createConst({ name: 'pets', export: true, type: 'Pet[]', asConst: true, nodes: ['[]'] }))
226
+ * // 'export const pets: Pet[] = [] as const'
227
+ * ```
228
+ */
229
+ export function printConst(node: ConstNode): string {
230
+ const { name, export: canExport, type, JSDoc, asConst, nodes } = node
231
+
232
+ const jsDocStr = JSDoc ? printJSDoc(JSDoc) : ''
233
+ const body = printNodes(nodes)
234
+
235
+ const parts: string[] = []
236
+ if (canExport) parts.push('export ')
237
+ parts.push('const ')
238
+ parts.push(name)
239
+ if (type) {
240
+ parts.push(`: ${type}`)
241
+ }
242
+ parts.push(' = ')
243
+ parts.push(body)
244
+ if (asConst) parts.push(' as const')
245
+
246
+ const declaration = parts.join('')
247
+ return [jsDocStr, declaration].filter(Boolean).join('\n')
248
+ }
249
+
250
+ /**
251
+ * Converts a {@link TypeNode} to a TypeScript `type` alias declaration string.
252
+ *
253
+ * Mirrors the `Type` component from `@kubb/react-fabric`.
254
+ *
255
+ * @example
256
+ * ```ts
257
+ * printType(createType({ name: 'Pet', export: true, nodes: ['{ id: number }'] }))
258
+ * // 'export type Pet = { id: number }'
259
+ * ```
260
+ */
261
+ export function printType(node: TypeNode): string {
262
+ const { name, export: canExport, JSDoc, nodes } = node
263
+
264
+ const jsDocStr = JSDoc ? printJSDoc(JSDoc) : ''
265
+ const body = printNodes(nodes)
266
+
267
+ const parts: string[] = []
268
+ if (canExport) parts.push('export ')
269
+ parts.push('type ')
270
+ parts.push(name)
271
+ parts.push(' = ')
272
+ parts.push(body)
273
+
274
+ const declaration = parts.join('')
275
+ return [jsDocStr, declaration].filter(Boolean).join('\n')
276
+ }
277
+
278
+ /**
279
+ * Converts a {@link FunctionNode} to a TypeScript `function` declaration string.
280
+ *
281
+ * Mirrors the `Function` component from `@kubb/react-fabric`.
282
+ *
283
+ * @example
284
+ * ```ts
285
+ * printFunction(createFunction({ name: 'getPet', export: true, params: 'id: string', returnType: 'Pet', nodes: ['return fetch(id)'] }))
286
+ * // 'export function getPet(id: string): Pet {\n return fetch(id)\n}'
287
+ * ```
288
+ *
289
+ * @example Async with generics
290
+ * ```ts
291
+ * printFunction(createFunction({ name: 'fetchPet', export: true, async: true, generics: ['T'], params: 'id: string', returnType: 'T' }))
292
+ * // 'export async function fetchPet<T>(id: string): Promise<T> {\n}'
293
+ * ```
294
+ */
295
+ export function printFunction(node: FunctionNode): string {
296
+ const { name, default: isDefault, export: canExport, async: isAsync, generics, params, returnType, JSDoc, nodes } = node
297
+
298
+ const jsDocStr = JSDoc ? printJSDoc(JSDoc) : ''
299
+
300
+ const genericsStr = generics ? `<${Array.isArray(generics) ? generics.join(', ') : generics}>` : ''
301
+
302
+ const returnTypeStr = returnType ? (isAsync ? `: Promise<${returnType}>` : `: ${returnType}`) : ''
303
+
304
+ const body = printNodes(nodes)
305
+ const indented = body ? indentLines(body) : ''
306
+
307
+ const parts: string[] = []
308
+ if (canExport) parts.push('export ')
309
+ if (isDefault) parts.push('default ')
310
+ if (isAsync) parts.push('async ')
311
+ parts.push('function ')
312
+ parts.push(name)
313
+ parts.push(genericsStr)
314
+ parts.push(`(${params ?? ''})`)
315
+ parts.push(returnTypeStr)
316
+ parts.push(' {')
317
+ if (indented) {
318
+ parts.push(`\n${indented}\n`)
319
+ }
320
+ parts.push('}')
321
+
322
+ const declaration = parts.join('')
323
+ return [jsDocStr, declaration].filter(Boolean).join('\n')
324
+ }
325
+
326
+ /**
327
+ * Converts an {@link ArrowFunctionNode} to a TypeScript arrow function declaration string.
328
+ *
329
+ * Mirrors the `Function.Arrow` component from `@kubb/react-fabric`.
330
+ *
331
+ * @example Multi-line arrow function
332
+ * ```ts
333
+ * printArrowFunction(createArrowFunction({ name: 'getPet', export: true, params: 'id: string', nodes: ['return fetch(id)'] }))
334
+ * // 'export const getPet = (id: string) => {\n return fetch(id)\n}'
335
+ * ```
336
+ *
337
+ * @example Single-line arrow function
338
+ * ```ts
339
+ * printArrowFunction(createArrowFunction({ name: 'double', params: 'n: number', singleLine: true, nodes: ['n * 2'] }))
340
+ * // 'const double = (n: number) => n * 2'
341
+ * ```
342
+ */
343
+ export function printArrowFunction(node: ArrowFunctionNode): string {
344
+ const { name, default: isDefault, export: canExport, async: isAsync, generics, params, returnType, JSDoc, nodes, singleLine } = node
345
+
346
+ const jsDocStr = JSDoc ? printJSDoc(JSDoc) : ''
347
+
348
+ const genericsStr = generics ? `<${Array.isArray(generics) ? generics.join(', ') : generics}>` : ''
349
+
350
+ const returnTypeStr = returnType ? (isAsync ? `: Promise<${returnType}>` : `: ${returnType}`) : ''
351
+
352
+ const body = printNodes(nodes)
353
+
354
+ const arrowBody = singleLine ? ` => ${body}` : body ? ` => {\n${indentLines(body)}\n}` : ' => {}'
355
+
356
+ const parts: string[] = []
357
+ if (canExport) parts.push('export ')
358
+ if (isDefault) parts.push('default ')
359
+ parts.push('const ')
360
+ parts.push(name)
361
+ parts.push(' = ')
362
+ if (isAsync) parts.push('async ')
363
+ parts.push(genericsStr)
364
+ parts.push(`(${params ?? ''})`)
365
+ parts.push(returnTypeStr)
366
+ parts.push(arrowBody)
367
+
368
+ const declaration = parts.join('')
369
+ return [jsDocStr, declaration].filter(Boolean).join('\n')
370
+ }
371
+
372
+ /**
373
+ * Converts a {@link CodeNode} to its TypeScript string representation.
374
+ *
375
+ * Dispatches to the appropriate printer based on the node's `kind`.
376
+ *
377
+ * @example
378
+ * ```ts
379
+ * printCodeNode(createConst({ name: 'x', nodes: ['1'] }))
380
+ * // 'const x = 1'
381
+ * ```
382
+ */
383
+ export function printCodeNode(node: CodeNode): string {
384
+ switch (node.kind) {
385
+ case 'Const':
386
+ return printConst(node)
387
+ case 'Type':
388
+ return printType(node)
389
+ case 'Function':
390
+ return printFunction(node)
391
+ case 'ArrowFunction':
392
+ return printArrowFunction(node)
393
+ }
394
+ }
395
+
396
+ /**
397
+ * Converts a {@link SourceNode} to its TypeScript string representation.
398
+ *
399
+ * Uses `value` if present; otherwise converts the structured `nodes` array
400
+ * via {@link printCodeNode}.
401
+ *
402
+ * @example From value
403
+ * ```ts
404
+ * printSource({ kind: 'Source', value: 'const x = 1' })
405
+ * // 'const x = 1'
406
+ * ```
407
+ *
408
+ * @example From nodes
409
+ * ```ts
410
+ * printSource({ kind: 'Source', nodes: [createConst({ name: 'x', nodes: ['1'] })] })
411
+ * // 'const x = 1'
412
+ * ```
413
+ */
414
+ export function printSource(node: SourceNode): string {
415
+ if (node.value) return node.value
416
+ if (node.nodes && node.nodes.length > 0) {
417
+ return node.nodes.map(printCodeNode).join('\n')
418
+ }
419
+ return ''
420
+ }
421
+
422
+ /**
423
+ * Parser that converts `.ts` and `.js` files to strings using the TypeScript
424
+ * compiler. Handles import/export statement generation from file metadata.
425
+ *
426
+ * @default Used automatically when no `parsers` option is set in `defineConfig`.
427
+ */
428
+ export const parserTs: Parser = defineParser({
429
+ name: 'typescript',
430
+ extNames: ['.ts', '.js'],
431
+ async parse(file, options = { extname: '.ts' }) {
432
+ const sourceParts: Array<string> = []
433
+ for (const item of file.sources) {
434
+ const sourceStr = printSource(item as SourceNode)
435
+ if (sourceStr) {
436
+ sourceParts.push(sourceStr)
437
+ }
438
+ }
439
+ const source = sourceParts.join('\n\n')
440
+
441
+ const importNodes: Array<ts.ImportDeclaration> = []
442
+ for (const item of (file as FileNode).imports) {
443
+ const importPath = item.root ? getRelativePath(item.root, item.path) : item.path
444
+ const hasExtname = !!/\.[^/.]+$/.exec(importPath)
445
+
446
+ importNodes.push(
447
+ createImport({
448
+ name: item.name as string | Array<string | { propertyName: string; name?: string }>,
449
+ path: options?.extname && hasExtname ? `${trimExtName(importPath)}${options.extname}` : item.root ? trimExtName(importPath) : importPath,
450
+ isTypeOnly: item.isTypeOnly,
451
+ isNameSpace: item.isNameSpace,
452
+ }),
453
+ )
454
+ }
455
+
456
+ const exportNodes: Array<ts.ExportDeclaration> = []
457
+ for (const item of (file as FileNode).exports) {
458
+ const exportPath = item.path
459
+ const hasExtname = !!/\.[^/.]+$/.exec(exportPath)
460
+
461
+ exportNodes.push(
462
+ createExport({
463
+ name: item.name as string | Array<ts.Identifier | string> | undefined,
464
+ path: options?.extname && hasExtname ? `${trimExtName(item.path)}${options.extname}` : trimExtName(item.path),
465
+ isTypeOnly: item.isTypeOnly,
466
+ asAlias: item.asAlias,
467
+ }),
468
+ )
469
+ }
470
+
471
+ const parts = [file.banner, print(...importNodes, ...exportNodes), source, file.footer].filter((segment): segment is string => segment != null)
472
+ return parts.join('\n')
473
+ },
474
+ })
@@ -0,0 +1,20 @@
1
+ import type { Parser } from '@kubb/core'
2
+ import { defineParser } from '@kubb/core'
3
+ import { parserTs } from './parserTs.ts'
4
+
5
+ /**
6
+ * Parser that converts `.tsx` and `.jsx` files to strings.
7
+ * Delegates to `typescriptParser` since the TypeScript compiler natively
8
+ * supports JSX/TSX syntax via `ScriptKind.TSX`.
9
+ *
10
+ * Add this parser to the `parsers` option in `defineConfig` when generating `.tsx`/`.jsx` files.
11
+ *
12
+ * @default extname '.tsx'
13
+ */
14
+ export const parserTsx: Parser = defineParser({
15
+ name: 'tsx',
16
+ extNames: ['.tsx', '.jsx'],
17
+ async parse(file, options = { extname: '.tsx' }) {
18
+ return parserTs.parse(file, options)
19
+ },
20
+ })
package/README.md DELETED
@@ -1,54 +0,0 @@
1
- <div align="center">
2
- <h1>Parser TypeScript</h1>
3
- <a href="https://kubb.dev" target="_blank" rel="noopener noreferrer">
4
- <img width="180" src="https://raw.githubusercontent.com/kubb-labs/kubb/main/assets/logo.png" alt="Kubb logo">
5
- </a>
6
-
7
-
8
- [![npm version][npm-version-src]][npm-version-href]
9
- [![npm downloads][npm-downloads-src]][npm-downloads-href]
10
- [![Coverage][coverage-src]][coverage-href]
11
- [![License][license-src]][license-href]
12
- [![Sponsors][sponsors-src]][sponsors-href]
13
- <h4>
14
- <a href="https://codesandbox.io/s/github/kubb-labs/kubb/tree/main//examples/typescript" target="_blank">View Demo</a>
15
- <span> · </span>
16
- <a href="https://kubb.dev/" target="_blank">Documentation</a>
17
- <span> · </span>
18
- <a href="https://github.com/kubb-labs/kubb/issues/" target="_blank">Report Bug</a>
19
- <span> · </span>
20
- <a href="https://github.com/kubb-labs/kubb/issues/" target="_blank">Request Feature</a>
21
- </h4>
22
- </div>
23
-
24
- Typescript Parser
25
-
26
- ## Supporting Kubb
27
-
28
- Kubb uses an MIT-licensed open source project with its ongoing development made possible entirely by the support of Sponsors. If you would like to become a sponsor, please consider:
29
-
30
- - [Become a Sponsor on GitHub](https://github.com/sponsors/stijnvanhulle)
31
-
32
- <p align="center">
33
- <a href="https://github.com/sponsors/stijnvanhulle">
34
- <img src="https://raw.githubusercontent.com/stijnvanhulle/sponsors/main/sponsors.svg" alt="My sponsors" />
35
- </a>
36
- </p>
37
-
38
-
39
- <!-- Badges -->
40
-
41
- [npm-version-src]: https://img.shields.io/npm/v/@kubb/parser-ts?flat&colorA=18181B&colorB=f58517
42
- [npm-version-href]: https://npmjs.com/package/@kubb/parser-ts
43
- [npm-downloads-src]: https://img.shields.io/npm/dm/@kubb/parser-ts?flat&colorA=18181B&colorB=f58517
44
- [npm-downloads-href]: https://npmjs.com/package/@kubb/parser-ts
45
- [license-src]: https://img.shields.io/github/license/kubb-labs/kubb.svg?flat&colorA=18181B&colorB=f58517
46
- [license-href]: https://github.com/kubb-labs/kubb/blob/main/LICENSE
47
- [build-src]: https://img.shields.io/github/actions/workflow/status/kubb-labs/kubb/ci.yaml?style=flat&colorA=18181B&colorB=f58517
48
- [build-href]: https://www.npmjs.com/package/@kubb/parser-ts
49
- [minified-src]: https://img.shields.io/bundlephobia/min/@kubb/parser-ts?style=flat&colorA=18181B&colorB=f58517
50
- [minified-href]: https://www.npmjs.com/package/@kubb/parser-ts
51
- [coverage-src]: https://img.shields.io/codecov/c/github/kubb-labs/kubb?style=flat&colorA=18181B&colorB=f58517
52
- [coverage-href]: https://www.npmjs.com/package/@kubb/parser-ts
53
- [sponsors-src]: https://img.shields.io/github/sponsors/stijnvanhulle?style=flat&colorA=18181B&colorB=f58517
54
- [sponsors-href]: https://github.com/sponsors/stijnvanhulle/
@@ -1,13 +0,0 @@
1
- //#region rolldown:runtime
2
- var __defProp = Object.defineProperty;
3
- var __export = (all) => {
4
- let target = {};
5
- for (var name in all) __defProp(target, name, {
6
- get: all[name],
7
- enumerable: true
8
- });
9
- return target;
10
- };
11
-
12
- //#endregion
13
- export { __export };