@kubb/parser-ts 5.0.0-beta.5 → 5.0.0-beta.50

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/src/utils.ts ADDED
@@ -0,0 +1,592 @@
1
+ import { normalize, relative } from 'node:path'
2
+ import type { ArrowFunctionNode, CodeNode, ConstNode, FunctionNode, JSDocNode, JsxNode, SourceNode, TextNode, TypeNode } from '@kubb/ast'
3
+ import ts from 'typescript'
4
+ import {
5
+ CARRIAGE_RETURN_PATTERN,
6
+ CRLF_PATTERN,
7
+ CURRENT_DIRECTORY_PREFIX,
8
+ FILE_EXTENSION_PATTERN,
9
+ INDENT,
10
+ INDENT_CHAR,
11
+ JSDOC_TERMINATOR_PATTERN,
12
+ LEADING_DIGIT_PATTERN,
13
+ PARENT_DIRECTORY_PREFIX,
14
+ WINDOWS_PATH_SEPARATOR,
15
+ } from './constants.ts'
16
+
17
+ const { factory } = ts
18
+
19
+ /**
20
+ * Normalizes a file-system path to POSIX separators and strips any leading `../` segment.
21
+ */
22
+ export function slash(path: string): string {
23
+ return normalize(path).replaceAll(WINDOWS_PATH_SEPARATOR, '/').replace(PARENT_DIRECTORY_PREFIX, '')
24
+ }
25
+
26
+ /**
27
+ * Resolves `filePath` relative to `rootDir` and returns a POSIX-style path
28
+ * prefixed with `./` when the target sits inside the root, or `../` when it escapes it.
29
+ */
30
+ export function getRelativePath(rootDir: string, filePath: string): string {
31
+ const rel = relative(rootDir, filePath)
32
+ const slashed = slash(rel)
33
+ return slashed.startsWith(PARENT_DIRECTORY_PREFIX) ? slashed : `${CURRENT_DIRECTORY_PREFIX}${slashed}`
34
+ }
35
+
36
+ /**
37
+ * Strips the trailing file extension (for example `.ts`) from a path.
38
+ * Preserves intermediate dots like `foo.bar.ts` → `foo.bar`.
39
+ */
40
+ export function trimExtName(text: string): string {
41
+ return text.replace(FILE_EXTENSION_PATTERN, '')
42
+ }
43
+
44
+ /**
45
+ * Rewrites an import/export path so its extension matches the caller-supplied
46
+ * `options.extname`. When the source path has no extension the original is kept,
47
+ * so virtual/module-only paths flow through unchanged.
48
+ */
49
+ export function resolveOutputPath(path: string, options: { extname?: string } | undefined, rootAware: boolean): string {
50
+ const hasExtname = FILE_EXTENSION_PATTERN.test(path)
51
+ if (options?.extname && hasExtname) {
52
+ return `${trimExtName(path)}${options.extname}`
53
+ }
54
+ return rootAware ? trimExtName(path) : path
55
+ }
56
+
57
+ /**
58
+ * Serializes a `nodes` array into source text. Each entry is rendered via {@link printCodeNode}
59
+ * and joined with a single newline; a `Break` node (`<br/>`) inserts one blank line between
60
+ * statements. Consecutive breaks, and breaks at the very start or end, are folded into the
61
+ * separator, so a double `<br/>` never emits more than one blank line.
62
+ */
63
+ export function printNodes(nodes: Array<CodeNode> | undefined): string {
64
+ if (!nodes || nodes.length === 0) return ''
65
+
66
+ let result = ''
67
+ let hasContent = false
68
+ let pendingBreak = false
69
+
70
+ for (const node of nodes) {
71
+ if (node.kind === 'Break') {
72
+ if (hasContent) pendingBreak = true
73
+ continue
74
+ }
75
+
76
+ const text = printCodeNode(node)
77
+ if (!text) continue
78
+
79
+ if (hasContent) result += pendingBreak ? '\n\n' : '\n'
80
+ result += text
81
+ hasContent = true
82
+ pendingBreak = false
83
+ }
84
+
85
+ return result
86
+ }
87
+
88
+ /**
89
+ * Indents every non-empty line of `text` by one indent unit. Pass a number to repeat
90
+ * {@link INDENT_CHAR} that many times, or a string to use as the indent verbatim.
91
+ */
92
+ export function indentLines(text: string, indent: number | string = INDENT): string {
93
+ if (!text) return ''
94
+ const pad = typeof indent === 'string' ? indent : INDENT_CHAR.repeat(indent)
95
+ return text
96
+ .split('\n')
97
+ .map((line) => (line.trim() ? `${pad}${line}` : ''))
98
+ .join('\n')
99
+ }
100
+
101
+ /**
102
+ * Removes the common leading whitespace shared by every non-blank line and trims
103
+ * surrounding blank lines, normalizing multi-line content authored inside an
104
+ * indented template literal back to a column-zero baseline. Leading whitespace is
105
+ * counted by character, so N tabs and N spaces are treated as the same depth.
106
+ *
107
+ * @example
108
+ * ```ts
109
+ * dedent('\n foo\n bar\n ')
110
+ * // 'foo\n bar'
111
+ * ```
112
+ */
113
+ export function dedent(text: string): string {
114
+ if (!text) return ''
115
+
116
+ const lines = text.split('\n')
117
+ const isBlank = (line: string) => line.trim() === ''
118
+
119
+ const start = lines.findIndex((line) => !isBlank(line))
120
+ if (start === -1) return ''
121
+ const end = lines.findLastIndex((line) => !isBlank(line))
122
+
123
+ const trimmed = lines.slice(start, end + 1)
124
+ const indents = trimmed.filter((line) => !isBlank(line)).map((line) => line.match(/^\s*/)?.[0].length ?? 0)
125
+ const min = indents.length ? Math.min(...indents) : 0
126
+
127
+ return trimmed.map((line) => (isBlank(line) ? '' : line.slice(min))).join('\n')
128
+ }
129
+
130
+ /**
131
+ * Renders the generic clause (`<T, U>`) shared by function and arrow-function nodes.
132
+ * Accepts either a raw string (rendered verbatim) or an array of type-parameter names.
133
+ */
134
+ export function formatGenerics(generics: FunctionNode['generics'] | ArrowFunctionNode['generics']): string {
135
+ if (!generics) return ''
136
+ return `<${Array.isArray(generics) ? generics.join(', ') : generics}>`
137
+ }
138
+
139
+ /**
140
+ * Renders the return-type suffix (`: T` or `: Promise<T>` when `isAsync` is true).
141
+ * Returns an empty string when no return type is provided.
142
+ */
143
+ export function formatReturnType(returnType: string | null | undefined, isAsync: boolean | null | undefined): string {
144
+ if (!returnType) return ''
145
+ return isAsync ? `: Promise<${returnType}>` : `: ${returnType}`
146
+ }
147
+
148
+ /**
149
+ * Module-scoped TypeScript printer instance. `ts.createPrinter()` is stateless across calls
150
+ * (it does not mutate the source file) so a single instance can be safely reused for every
151
+ * `print()` call. Hoisting it out of `print()` avoids re-running the printer initialization
152
+ * for each file's import/export section.
153
+ */
154
+ const TS_PRINTER = ts.createPrinter({
155
+ omitTrailingSemicolon: true,
156
+ newLine: ts.NewLineKind.LineFeed,
157
+ removeComments: false,
158
+ noEmitHelpers: true,
159
+ })
160
+
161
+ /**
162
+ * Module-scoped source file used as the print target. `printList` only reads the source
163
+ * file's compiler options / language version. It never mutates it.
164
+ */
165
+ const PRINT_SOURCE_FILE = ts.createSourceFile('print.tsx', '', ts.ScriptTarget.ES2022, true, ts.ScriptKind.TSX)
166
+
167
+ // Pre-warm the printer at module load. The first `printList` call lazily initializes
168
+ // the printer's internal string-builder and identifier tables. Doing it once at import
169
+ // time keeps that cost off the critical path for short-lived CLI builds.
170
+ TS_PRINTER.printList(ts.ListFormat.MultiLine, factory.createNodeArray([]), PRINT_SOURCE_FILE)
171
+
172
+ /**
173
+ * Converts TypeScript/TSX AST nodes to a string using the TypeScript printer.
174
+ */
175
+ export function print(...elements: Array<ts.Node>): string {
176
+ const filtered = elements.filter(Boolean)
177
+ if (filtered.length === 0) return ''
178
+
179
+ const output = TS_PRINTER.printList(ts.ListFormat.MultiLine, factory.createNodeArray(filtered), PRINT_SOURCE_FILE)
180
+
181
+ return output.replace(CRLF_PATTERN, '\n')
182
+ }
183
+
184
+ /**
185
+ * Converts a {@link JSDocNode} to a JSDoc comment block string.
186
+ *
187
+ * @example
188
+ * ```ts
189
+ * printJSDoc({ comments: ['@description A pet', '@deprecated'] })
190
+ * // /**
191
+ * // * @description A pet
192
+ * // * @deprecated
193
+ * // *\/
194
+ * ```
195
+ */
196
+ export function printJSDoc(jsDoc: JSDocNode): string {
197
+ const comments = (jsDoc.comments ?? []).filter((c) => c != null)
198
+ if (comments.length === 0) return ''
199
+
200
+ const lines = comments
201
+ .flatMap((c) => c.split(/\r?\n/))
202
+ .map((l) => l.replace(JSDOC_TERMINATOR_PATTERN, '* /').replace(CARRIAGE_RETURN_PATTERN, ''))
203
+ .filter((l) => l.trim().length > 0)
204
+
205
+ if (lines.length === 0) return ''
206
+
207
+ return ['/**', ...lines.map((l) => ` * ${l}`), ' */'].join('\n')
208
+ }
209
+
210
+ /**
211
+ * Converts a {@link ConstNode} to a TypeScript `const` declaration string.
212
+ *
213
+ * Mirrors the `Const` component from `@kubb/renderer-jsx`.
214
+ *
215
+ * @example
216
+ * ```ts
217
+ * printConst(createConst({ name: 'pet', export: true, nodes: ['{}'] }))
218
+ * // 'export const pet = {}'
219
+ * ```
220
+ *
221
+ * @example With type and `as const`
222
+ * ```ts
223
+ * printConst(createConst({ name: 'pets', export: true, type: 'Pet[]', asConst: true, nodes: ['[]'] }))
224
+ * // 'export const pets: Pet[] = [] as const'
225
+ * ```
226
+ */
227
+ export function printConst(node: ConstNode): string {
228
+ const { name, export: canExport, type, JSDoc, asConst, nodes } = node
229
+
230
+ const jsDocStr = JSDoc ? printJSDoc(JSDoc) : ''
231
+ const body = printNodes(nodes)
232
+
233
+ const parts: Array<string> = []
234
+ if (canExport) parts.push('export ')
235
+ parts.push('const ')
236
+ parts.push(name)
237
+ if (type) {
238
+ parts.push(`: ${type}`)
239
+ }
240
+ parts.push(' = ')
241
+ parts.push(body)
242
+ if (asConst) parts.push(' as const')
243
+
244
+ const declaration = parts.join('')
245
+ return [jsDocStr, declaration].filter(Boolean).join('\n')
246
+ }
247
+
248
+ /**
249
+ * Converts a {@link TypeNode} to a TypeScript `type` alias declaration string.
250
+ *
251
+ * Mirrors the `Type` component from `@kubb/renderer-jsx`.
252
+ *
253
+ * @example
254
+ * ```ts
255
+ * printType(createType({ name: 'Pet', export: true, nodes: ['{ id: number }'] }))
256
+ * // 'export type Pet = { id: number }'
257
+ * ```
258
+ */
259
+ export function printType(node: TypeNode): string {
260
+ const { name, export: canExport, JSDoc, nodes } = node
261
+
262
+ const jsDocStr = JSDoc ? printJSDoc(JSDoc) : ''
263
+ const body = printNodes(nodes)
264
+
265
+ const parts: Array<string> = []
266
+ if (canExport) parts.push('export ')
267
+ parts.push('type ')
268
+ parts.push(name)
269
+ parts.push(' = ')
270
+ parts.push(body)
271
+
272
+ const declaration = parts.join('')
273
+ return [jsDocStr, declaration].filter(Boolean).join('\n')
274
+ }
275
+
276
+ /**
277
+ * Converts a {@link FunctionNode} to a TypeScript `function` declaration string.
278
+ *
279
+ * Mirrors the `Function` component from `@kubb/renderer-jsx`.
280
+ *
281
+ * @example
282
+ * ```ts
283
+ * printFunction(createFunction({ name: 'getPet', export: true, params: 'id: string', returnType: 'Pet', nodes: ['return fetch(id)'] }))
284
+ * // 'export function getPet(id: string): Pet {\n return fetch(id)\n}'
285
+ * ```
286
+ *
287
+ * @example Async with generics
288
+ * ```ts
289
+ * printFunction(createFunction({ name: 'fetchPet', export: true, async: true, generics: ['T'], params: 'id: string', returnType: 'T' }))
290
+ * // 'export async function fetchPet<T>(id: string): Promise<T> {\n}'
291
+ * ```
292
+ */
293
+ export function printFunction(node: FunctionNode): string {
294
+ const { name, default: isDefault, export: canExport, async: isAsync, generics, params, returnType, JSDoc, nodes } = node
295
+
296
+ const jsDocStr = JSDoc ? printJSDoc(JSDoc) : ''
297
+ const body = printNodes(nodes)
298
+ const indented = body ? indentLines(body) : ''
299
+
300
+ const parts: Array<string> = []
301
+ if (canExport) parts.push('export ')
302
+ if (isDefault) parts.push('default ')
303
+ if (isAsync) parts.push('async ')
304
+ parts.push('function ')
305
+ parts.push(name)
306
+ parts.push(formatGenerics(generics))
307
+ parts.push(`(${params ?? ''})`)
308
+ parts.push(formatReturnType(returnType, isAsync))
309
+ parts.push(' {')
310
+ if (indented) {
311
+ parts.push(`\n${indented}\n`)
312
+ }
313
+ parts.push('}')
314
+
315
+ const declaration = parts.join('')
316
+ return [jsDocStr, declaration].filter(Boolean).join('\n')
317
+ }
318
+
319
+ /**
320
+ * Converts an {@link ArrowFunctionNode} to a TypeScript arrow function declaration string.
321
+ *
322
+ * Mirrors the `Function.Arrow` component from `@kubb/renderer-jsx`.
323
+ *
324
+ * @example Multi-line arrow function
325
+ * ```ts
326
+ * printArrowFunction(createArrowFunction({ name: 'getPet', export: true, params: 'id: string', nodes: ['return fetch(id)'] }))
327
+ * // 'export const getPet = (id: string) => {\n return fetch(id)\n}'
328
+ * ```
329
+ *
330
+ * @example Single-line arrow function
331
+ * ```ts
332
+ * printArrowFunction(createArrowFunction({ name: 'double', params: 'n: number', singleLine: true, nodes: ['n * 2'] }))
333
+ * // 'const double = (n: number) => n * 2'
334
+ * ```
335
+ */
336
+ export function printArrowFunction(node: ArrowFunctionNode): string {
337
+ const { name, default: isDefault, export: canExport, async: isAsync, generics, params, returnType, JSDoc, nodes, singleLine } = node
338
+
339
+ const jsDocStr = JSDoc ? printJSDoc(JSDoc) : ''
340
+ const body = printNodes(nodes)
341
+ const arrowBody = singleLine ? ` => ${body}` : body ? ` => {\n${indentLines(body)}\n}` : ' => {}'
342
+
343
+ const parts: Array<string> = []
344
+ if (canExport) parts.push('export ')
345
+ if (isDefault) parts.push('default ')
346
+ parts.push('const ')
347
+ parts.push(name)
348
+ parts.push(' = ')
349
+ if (isAsync) parts.push('async ')
350
+ parts.push(formatGenerics(generics))
351
+ parts.push(`(${params ?? ''})`)
352
+ parts.push(formatReturnType(returnType, isAsync))
353
+ parts.push(arrowBody)
354
+
355
+ const declaration = parts.join('')
356
+ return [jsDocStr, declaration].filter(Boolean).join('\n')
357
+ }
358
+
359
+ /**
360
+ * Converts a {@link CodeNode} to its TypeScript string representation.
361
+ *
362
+ * Dispatches to the appropriate printer based on the node's `kind`.
363
+ *
364
+ * @example
365
+ * ```ts
366
+ * printCodeNode(createConst({ name: 'x', nodes: ['1'] }))
367
+ * // 'const x = 1'
368
+ * ```
369
+ */
370
+ export function printCodeNode(node: CodeNode): string {
371
+ if (node.kind === 'Break') return ''
372
+ if (node.kind === 'Text') return dedent((node as TextNode).value)
373
+ if (node.kind === 'Jsx') return dedent((node as JsxNode).value)
374
+ if (node.kind === 'Const') return printConst(node)
375
+ if (node.kind === 'Type') return printType(node)
376
+ if (node.kind === 'Function') return printFunction(node)
377
+ if (node.kind === 'ArrowFunction') return printArrowFunction(node)
378
+ return ''
379
+ }
380
+
381
+ /**
382
+ * Converts a {@link SourceNode} to its TypeScript string representation.
383
+ *
384
+ * Iterates `nodes` in DOM order, rendering each {@link CodeNode} via
385
+ * {@link printCodeNode}.
386
+ *
387
+ * Top-level declarations are separated by a blank line so the source reads
388
+ * cleanly without an external formatter.
389
+ *
390
+ * @example From nodes
391
+ * ```ts
392
+ * printSource({ kind: 'Source', nodes: [createConst({ name: 'x', nodes: [createText('1')] }), createText('x.toString()')] })
393
+ * // 'const x = 1\n\nx.toString()'
394
+ * ```
395
+ */
396
+ export function printSource(node: SourceNode): string {
397
+ const nodes = node.nodes
398
+
399
+ if (!nodes || nodes.length === 0) return ''
400
+
401
+ return nodes
402
+ .map((child) => printCodeNode(child as CodeNode))
403
+ .filter(Boolean)
404
+ .join('\n\n')
405
+ }
406
+
407
+ export function createImport({
408
+ name,
409
+ path,
410
+ root,
411
+ isTypeOnly: isTypeOnlyRaw = false,
412
+ isNameSpace: isNameSpaceRaw = false,
413
+ }: {
414
+ name: string | Array<string | { propertyName: string; name?: string }>
415
+ path: string
416
+ root?: string | null
417
+ /** @default false */
418
+ isTypeOnly?: boolean | null
419
+ /** @default false */
420
+ isNameSpace?: boolean | null
421
+ }): ts.ImportDeclaration {
422
+ const isTypeOnly = isTypeOnlyRaw ?? false
423
+ const isNameSpace = isNameSpaceRaw ?? false
424
+ const resolvePath = root ? getRelativePath(root, path) : path
425
+
426
+ if (!Array.isArray(name)) {
427
+ if (isNameSpace) {
428
+ return factory.createImportDeclaration(
429
+ undefined,
430
+ factory.createImportClause(isTypeOnly, undefined, factory.createNamespaceImport(factory.createIdentifier(name))),
431
+ factory.createStringLiteral(resolvePath),
432
+ undefined,
433
+ )
434
+ }
435
+
436
+ return factory.createImportDeclaration(
437
+ undefined,
438
+ factory.createImportClause(isTypeOnly, factory.createIdentifier(name), undefined),
439
+ factory.createStringLiteral(resolvePath),
440
+ undefined,
441
+ )
442
+ }
443
+
444
+ const specifiers = name.map((item) => {
445
+ if (typeof item === 'object') {
446
+ const { propertyName, name: alias } = item
447
+ return factory.createImportSpecifier(false, alias ? factory.createIdentifier(propertyName) : undefined, factory.createIdentifier(alias ?? propertyName))
448
+ }
449
+ return factory.createImportSpecifier(false, undefined, factory.createIdentifier(item))
450
+ })
451
+
452
+ return factory.createImportDeclaration(
453
+ undefined,
454
+ factory.createImportClause(isTypeOnly, undefined, factory.createNamedImports(specifiers)),
455
+ factory.createStringLiteral(resolvePath),
456
+ undefined,
457
+ )
458
+ }
459
+
460
+ export function createExport({
461
+ path,
462
+ asAlias: asAliasRaw,
463
+ isTypeOnly: isTypeOnlyRaw = false,
464
+ name,
465
+ }: {
466
+ path: string
467
+ /** @default false */
468
+ asAlias?: boolean | null
469
+ /** @default false */
470
+ isTypeOnly?: boolean | null
471
+ name?: string | Array<ts.Identifier | string> | null
472
+ }): ts.ExportDeclaration {
473
+ const asAlias = asAliasRaw ?? false
474
+ const isTypeOnly = isTypeOnlyRaw ?? false
475
+ if (name && !Array.isArray(name) && !asAlias) {
476
+ console.warn(`When using name as string, asAlias should be true: ${name}`)
477
+ }
478
+
479
+ if (!Array.isArray(name)) {
480
+ const parsedName = name && LEADING_DIGIT_PATTERN.test(name) ? `_${name.slice(1)}` : name
481
+
482
+ return factory.createExportDeclaration(
483
+ undefined,
484
+ isTypeOnly,
485
+ asAlias && parsedName ? factory.createNamespaceExport(factory.createIdentifier(parsedName)) : undefined,
486
+ factory.createStringLiteral(path),
487
+ undefined,
488
+ )
489
+ }
490
+
491
+ return factory.createExportDeclaration(
492
+ undefined,
493
+ isTypeOnly,
494
+ factory.createNamedExports(
495
+ name.map((propertyName) =>
496
+ factory.createExportSpecifier(false, undefined, typeof propertyName === 'string' ? factory.createIdentifier(propertyName) : propertyName),
497
+ ),
498
+ ),
499
+ factory.createStringLiteral(path),
500
+ undefined,
501
+ )
502
+ }
503
+
504
+ /**
505
+ * Wraps a module specifier in single quotes, escaping any embedded backslash or quote so the emitted
506
+ * statement stays valid even for unusual paths.
507
+ */
508
+ function quoteModulePath(path: string): string {
509
+ return `'${path.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`
510
+ }
511
+
512
+ /**
513
+ * Renders an import declaration string in the repo style (single quotes, no semicolons), mirroring
514
+ * the shapes that {@link createImport} builds: default, namespace (`* as`), and named imports with
515
+ * `{ a as b }` aliases, each optionally `type`-only. `path` is used verbatim, so resolve it first.
516
+ *
517
+ * @example
518
+ * ```ts
519
+ * printImport({ name: ['z'], path: './zod.ts' })
520
+ * // "import { z } from './zod.ts'"
521
+ * ```
522
+ */
523
+ export function printImport({
524
+ name,
525
+ path,
526
+ isTypeOnly = false,
527
+ isNameSpace = false,
528
+ }: {
529
+ name: string | Array<string | { propertyName: string; name?: string }>
530
+ path: string
531
+ isTypeOnly?: boolean | null
532
+ isNameSpace?: boolean | null
533
+ }): string {
534
+ const typePrefix = isTypeOnly ? 'type ' : ''
535
+ const from = quoteModulePath(path)
536
+
537
+ if (!Array.isArray(name)) {
538
+ if (isNameSpace) return `import ${typePrefix}* as ${name} from ${from}`
539
+ return `import ${typePrefix}${name} from ${from}`
540
+ }
541
+
542
+ const specifiers = name.map((item) => {
543
+ if (typeof item === 'object') {
544
+ return item.name ? `${item.propertyName} as ${item.name}` : item.propertyName
545
+ }
546
+ return item
547
+ })
548
+
549
+ return `import ${typePrefix}{ ${specifiers.join(', ')} } from ${from}`
550
+ }
551
+
552
+ /**
553
+ * Renders an export declaration string in the repo style (single quotes, no semicolons), mirroring
554
+ * the shapes that {@link createExport} builds: named re-exports, namespace alias (`* as name`), and
555
+ * wildcard, each optionally `type`-only. `path` is used verbatim, so resolve it first.
556
+ *
557
+ * @example
558
+ * ```ts
559
+ * printExport({ name: ['Pet', 'Order'], path: './models.ts' })
560
+ * // "export { Pet, Order } from './models.ts'"
561
+ * ```
562
+ */
563
+ export function printExport({
564
+ path,
565
+ name,
566
+ isTypeOnly = false,
567
+ asAlias = false,
568
+ }: {
569
+ path: string
570
+ name?: string | Array<ts.Identifier | string> | null
571
+ isTypeOnly?: boolean | null
572
+ asAlias?: boolean | null
573
+ }): string {
574
+ const typePrefix = isTypeOnly ? 'type ' : ''
575
+ const from = quoteModulePath(path)
576
+
577
+ if (Array.isArray(name)) {
578
+ const specifiers = name.map((item) => (typeof item === 'string' ? item : item.text))
579
+ return `export ${typePrefix}{ ${specifiers.join(', ')} } from ${from}`
580
+ }
581
+
582
+ if (asAlias && name) {
583
+ const parsedName = LEADING_DIGIT_PATTERN.test(name) ? `_${name.slice(1)}` : name
584
+ return `export ${typePrefix}* as ${parsedName} from ${from}`
585
+ }
586
+
587
+ if (name) {
588
+ console.warn(`When using name as string, asAlias should be true: ${name}`)
589
+ }
590
+
591
+ return `export ${typePrefix}* from ${from}`
592
+ }
File without changes