@kubb/parser-ts 5.0.0-beta.1 → 5.0.0-beta.100

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 CHANGED
@@ -21,14 +21,33 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
21
21
  enumerable: true
22
22
  }) : target, mod));
23
23
  //#endregion
24
+ let _kubb_kit = require("@kubb/kit");
24
25
  let node_path = require("node:path");
25
- let _kubb_core = require("@kubb/core");
26
26
  let typescript = require("typescript");
27
27
  typescript = __toESM(typescript, 1);
28
- //#region src/constants.ts
28
+ //#region ../../internals/utils/src/fs.ts
29
29
  /**
30
- * Matches the trailing `.<ext>` segment of a path (keeps segments like `foo.bar.ts`
31
- * intact by only trimming the last run of non-`/`/`.` characters).
30
+ * Strips the file extension from a path or file name.
31
+ * Only removes the last `.ext` segment when the dot is not part of a directory name.
32
+ *
33
+ * @example
34
+ * trimExtName('petStore.ts') // 'petStore'
35
+ * trimExtName('/src/models/pet.ts') // '/src/models/pet'
36
+ * trimExtName('/project.v2/gen/pet.ts') // '/project.v2/gen/pet'
37
+ * trimExtName('noExtension') // 'noExtension'
38
+ */
39
+ function trimExtName(text) {
40
+ const dotIndex = text.lastIndexOf(".");
41
+ if (dotIndex > 0 && !text.includes("/", dotIndex)) return text.slice(0, dotIndex);
42
+ return text;
43
+ }
44
+ /**
45
+ * Indentation unit prepended once per nesting level when pretty-printing.
46
+ */
47
+ const INDENT = " ".repeat(2);
48
+ /**
49
+ * Matches only the final `.<ext>` of a path, so a name like `foo.bar.ts` keeps
50
+ * `foo.bar` and loses just `.ts`.
32
51
  */
33
52
  const FILE_EXTENSION_PATTERN = /\.[^/.]+$/;
34
53
  /**
@@ -36,45 +55,35 @@ const FILE_EXTENSION_PATTERN = /\.[^/.]+$/;
36
55
  */
37
56
  const WINDOWS_PATH_SEPARATOR = /\\/g;
38
57
  /**
39
- * Matches `*\/` in free-form text so JSDoc bodies can neutralise premature
58
+ * Matches `*\/` in free-form text so JSDoc bodies can neutralize premature
40
59
  * comment terminators (`*\/` → `* /`).
41
60
  */
42
61
  const JSDOC_TERMINATOR_PATTERN = /\*\//g;
43
62
  /**
44
- * Matches carriage returns for normalising CRLF/CR line endings to LF.
63
+ * Matches carriage returns for normalizing CRLF/CR line endings to LF.
45
64
  */
46
65
  const CARRIAGE_RETURN_PATTERN = /\r/g;
47
66
  /**
48
- * Matches CRLF sequences used when normalising TypeScript printer output.
67
+ * Matches CRLF sequences used when normalizing TypeScript printer output.
49
68
  */
50
69
  const CRLF_PATTERN = /\r\n/g;
51
70
  /**
52
- * Matches an identifier that starts with a digit JavaScript disallows this
53
- * so the printer prefixes such names with `_`.
71
+ * Matches an identifier that starts with a digit. JavaScript disallows this,
72
+ * so the printer replaces the leading digit with `_`.
54
73
  */
55
74
  const LEADING_DIGIT_PATTERN = /^\d/;
56
75
  //#endregion
57
- //#region src/parserTs.ts
76
+ //#region src/utils.ts
58
77
  const { factory } = typescript.default;
59
- function slash(path) {
60
- return (0, node_path.normalize)(path).replaceAll(WINDOWS_PATH_SEPARATOR, "/").replace("../", "");
61
- }
62
78
  /**
63
79
  * Resolves `filePath` relative to `rootDir` and returns a POSIX-style path
64
80
  * prefixed with `./` when the target sits inside the root, or `../` when it escapes it.
65
81
  */
66
82
  function getRelativePath(rootDir, filePath) {
67
- const slashed = slash((0, node_path.relative)(rootDir, filePath));
83
+ const slashed = (0, node_path.normalize)((0, node_path.relative)(rootDir, filePath)).replaceAll(WINDOWS_PATH_SEPARATOR, "/").replace("../", "");
68
84
  return slashed.startsWith("../") ? slashed : `./${slashed}`;
69
85
  }
70
86
  /**
71
- * Strips the trailing file extension (for example `.ts`) from a path.
72
- * Preserves intermediate dots like `foo.bar.ts` → `foo.bar`.
73
- */
74
- function trimExtName(text) {
75
- return text.replace(FILE_EXTENSION_PATTERN, "");
76
- }
77
- /**
78
87
  * Rewrites an import/export path so its extension matches the caller-supplied
79
88
  * `options.extname`. When the source path has no extension the original is kept,
80
89
  * so virtual/module-only paths flow through unchanged.
@@ -85,60 +94,105 @@ function resolveOutputPath(path, options, rootAware) {
85
94
  return rootAware ? trimExtName(path) : path;
86
95
  }
87
96
  /**
88
- * Validates TypeScript AST nodes before printing.
89
- * Throws an error if any node has SyntaxKind.Unknown which would cause the
90
- * TypeScript printer to crash.
97
+ * Serializes a `nodes` array into source text. Each entry is rendered via {@link printCodeNode}
98
+ * and joined with a single newline. A `Break` node (`<br/>`) inserts one blank line between
99
+ * statements. Consecutive breaks, and breaks at the very start or end, are folded into the
100
+ * separator, so a double `<br/>` never emits more than one blank line.
91
101
  */
92
- function validateNodes(...nodes) {
102
+ function printNodes(nodes) {
103
+ if (!nodes || nodes.length === 0) return "";
104
+ let result = "";
105
+ let hasContent = false;
106
+ let pendingBreak = false;
93
107
  for (const node of nodes) {
94
- if (!node) throw new Error("Attempted to print undefined or null TypeScript node");
95
- if (node.kind === typescript.default.SyntaxKind.Unknown) throw new Error(`Invalid TypeScript AST node detected with SyntaxKind.Unknown. This typically indicates a schema pattern that could not be properly converted to TypeScript. Node: ${JSON.stringify(node, null, 2)}`);
108
+ if (node.kind === "Break") {
109
+ if (hasContent) pendingBreak = true;
110
+ continue;
111
+ }
112
+ const text = printCodeNode(node);
113
+ if (!text) continue;
114
+ if (hasContent) result += pendingBreak ? "\n\n" : "\n";
115
+ result += text;
116
+ hasContent = true;
117
+ pendingBreak = false;
96
118
  }
119
+ return result;
97
120
  }
98
121
  /**
99
- * Converts TypeScript/TSX AST nodes to a string using the TypeScript printer.
122
+ * Indents every non-empty line of `text` by one indent unit. Pass a number to repeat
123
+ * {@link INDENT_CHAR} that many times, or a string to use as the indent verbatim.
100
124
  */
101
- function print(...elements) {
102
- const sourceFile = typescript.default.createSourceFile("print.tsx", "", typescript.default.ScriptTarget.ES2022, true, typescript.default.ScriptKind.TSX);
103
- return typescript.default.createPrinter({
104
- omitTrailingSemicolon: true,
105
- newLine: typescript.default.NewLineKind.LineFeed,
106
- removeComments: false,
107
- noEmitHelpers: true
108
- }).printList(typescript.default.ListFormat.MultiLine, factory.createNodeArray(elements.filter(Boolean)), sourceFile).replace(CRLF_PATTERN, "\n");
125
+ function indentLines(text, indent = INDENT) {
126
+ if (!text) return "";
127
+ const pad = typeof indent === "string" ? indent : " ".repeat(indent);
128
+ return text.split("\n").map((line) => line.trim() ? `${pad}${line}` : "").join("\n");
109
129
  }
110
130
  /**
111
- * Like `print` but validates nodes first to surface issues early.
131
+ * Removes the common leading whitespace shared by every non-blank line and trims
132
+ * surrounding blank lines, so multi-line content authored inside an indented template
133
+ * literal lines up at a column-zero baseline. Leading whitespace is counted by
134
+ * character, so N tabs and N spaces are treated as the same depth.
135
+ *
136
+ * @example
137
+ * ```ts
138
+ * dedent('\n foo\n bar\n ')
139
+ * // 'foo\n bar'
140
+ * ```
112
141
  */
113
- function safePrint(...elements) {
114
- validateNodes(...elements);
115
- return print(...elements);
142
+ function dedent(text) {
143
+ if (!text) return "";
144
+ const lines = text.split("\n");
145
+ const isBlank = (line) => line.trim() === "";
146
+ const start = lines.findIndex((line) => !isBlank(line));
147
+ if (start === -1) return "";
148
+ const end = lines.findLastIndex((line) => !isBlank(line));
149
+ const trimmed = lines.slice(start, end + 1);
150
+ const indents = trimmed.filter((line) => !isBlank(line)).map((line) => line.match(/^\s*/)?.[0].length ?? 0);
151
+ const min = indents.length ? Math.min(...indents) : 0;
152
+ return trimmed.map((line) => isBlank(line) ? "" : line.slice(min)).join("\n");
116
153
  }
117
- function createImport({ name, path, root, isTypeOnly = false, isNameSpace = false }) {
118
- const resolvePath = root ? getRelativePath(root, path) : path;
119
- if (!Array.isArray(name)) {
120
- if (isNameSpace) return factory.createImportDeclaration(void 0, factory.createImportClause(isTypeOnly, void 0, factory.createNamespaceImport(factory.createIdentifier(name))), factory.createStringLiteral(resolvePath), void 0);
121
- return factory.createImportDeclaration(void 0, factory.createImportClause(isTypeOnly, factory.createIdentifier(name), void 0), factory.createStringLiteral(resolvePath), void 0);
122
- }
123
- const specifiers = name.map((item) => {
124
- if (typeof item === "object") {
125
- const { propertyName, name: alias } = item;
126
- return factory.createImportSpecifier(false, alias ? factory.createIdentifier(propertyName) : void 0, factory.createIdentifier(alias ?? propertyName));
127
- }
128
- return factory.createImportSpecifier(false, void 0, factory.createIdentifier(item));
129
- });
130
- return factory.createImportDeclaration(void 0, factory.createImportClause(isTypeOnly, void 0, factory.createNamedImports(specifiers)), factory.createStringLiteral(resolvePath), void 0);
154
+ /**
155
+ * Renders the generic clause (`<T, U>`) shared by function and arrow-function nodes.
156
+ * Accepts either a raw string (rendered verbatim) or an array of type-parameter names.
157
+ */
158
+ function formatGenerics(generics) {
159
+ if (!generics) return "";
160
+ return `<${Array.isArray(generics) ? generics.join(", ") : generics}>`;
131
161
  }
132
- function createExport({ path, asAlias, isTypeOnly = false, name }) {
133
- if (name && !Array.isArray(name) && !asAlias) console.warn(`When using name as string, asAlias should be true: ${name}`);
134
- if (!Array.isArray(name)) {
135
- const parsedName = name && LEADING_DIGIT_PATTERN.test(name) ? `_${name.slice(1)}` : name;
136
- return factory.createExportDeclaration(void 0, isTypeOnly, asAlias && parsedName ? factory.createNamespaceExport(factory.createIdentifier(parsedName)) : void 0, factory.createStringLiteral(path), void 0);
137
- }
138
- return factory.createExportDeclaration(void 0, isTypeOnly, factory.createNamedExports(name.map((propertyName) => factory.createExportSpecifier(false, void 0, typeof propertyName === "string" ? factory.createIdentifier(propertyName) : propertyName))), factory.createStringLiteral(path), void 0);
162
+ /**
163
+ * Renders the return-type suffix (`: T` or `: Promise<T>` when `isAsync` is true).
164
+ * Returns an empty string when no return type is provided.
165
+ */
166
+ function formatReturnType(returnType, isAsync) {
167
+ if (!returnType) return "";
168
+ return isAsync ? `: Promise<${returnType}>` : `: ${returnType}`;
169
+ }
170
+ /**
171
+ * Module-scoped TypeScript printer instance. A printer does not mutate the source file, so one
172
+ * instance is reused across every `print()` call instead of constructing a new printer each time.
173
+ */
174
+ const TS_PRINTER = typescript.default.createPrinter({
175
+ omitTrailingSemicolon: true,
176
+ newLine: typescript.default.NewLineKind.LineFeed,
177
+ removeComments: false,
178
+ noEmitHelpers: true
179
+ });
180
+ /**
181
+ * Module-scoped source file used as the print target. `printList` only reads the source
182
+ * file's compiler options / language version. It never mutates it.
183
+ */
184
+ const PRINT_SOURCE_FILE = typescript.default.createSourceFile("print.tsx", "", typescript.default.ScriptTarget.ES2022, true, typescript.default.ScriptKind.TSX);
185
+ TS_PRINTER.printList(typescript.default.ListFormat.MultiLine, factory.createNodeArray([]), PRINT_SOURCE_FILE);
186
+ /**
187
+ * Converts TypeScript/TSX AST nodes to a string using the TypeScript printer.
188
+ */
189
+ function print(...elements) {
190
+ const filtered = elements.filter(Boolean);
191
+ if (filtered.length === 0) return "";
192
+ return TS_PRINTER.printList(typescript.default.ListFormat.MultiLine, factory.createNodeArray(filtered), PRINT_SOURCE_FILE).replace(CRLF_PATTERN, "\n");
139
193
  }
140
194
  /**
141
- * Converts a {@link JSDocNode} to a JSDoc comment block string.
195
+ * Converts a {@link ast.JSDocNode} to a JSDoc comment block string.
142
196
  *
143
197
  * @example
144
198
  * ```ts
@@ -161,54 +215,19 @@ function printJSDoc(jsDoc) {
161
215
  ].join("\n");
162
216
  }
163
217
  /**
164
- * Serializes the body / value content from a `nodes` array.
165
- *
166
- * Each element is either a raw string or a structured {@link CodeNode}
167
- * (recursively converted via {@link printCodeNode}).
168
- * Elements are joined with `\n`.
169
- */
170
- function printNodes(nodes) {
171
- if (!nodes || nodes.length === 0) return "";
172
- return nodes.map(printCodeNode).join("\n");
173
- }
174
- /**
175
- * Indents every non-empty line of `text` by `spaces` spaces.
176
- */
177
- function indentLines(text, spaces = 2) {
178
- if (!text) return "";
179
- const pad = " ".repeat(spaces);
180
- return text.split("\n").map((line) => line.trim() ? `${pad}${line}` : "").join("\n");
181
- }
182
- /**
183
- * Renders the generic clause (`<T, U>`) shared by function and arrow-function nodes.
184
- * Accepts either a raw string (rendered verbatim) or an array of type-parameter names.
185
- */
186
- function formatGenerics(generics) {
187
- if (!generics) return "";
188
- return `<${Array.isArray(generics) ? generics.join(", ") : generics}>`;
189
- }
190
- /**
191
- * Renders the return-type suffix (`: T` or `: Promise<T>` when `isAsync` is true).
192
- * Returns an empty string when no return type is provided.
193
- */
194
- function formatReturnType(returnType, isAsync) {
195
- if (!returnType) return "";
196
- return isAsync ? `: Promise<${returnType}>` : `: ${returnType}`;
197
- }
198
- /**
199
- * Converts a {@link ConstNode} to a TypeScript `const` declaration string.
218
+ * Converts a {@link ast.ConstNode} to a TypeScript `const` declaration string.
200
219
  *
201
220
  * Mirrors the `Const` component from `@kubb/renderer-jsx`.
202
221
  *
203
222
  * @example
204
223
  * ```ts
205
- * printConst(createConst({ name: 'pet', export: true, nodes: ['{}'] }))
224
+ * printConst(factory.createConst({ name: 'pet', export: true, nodes: ['{}'] }))
206
225
  * // 'export const pet = {}'
207
226
  * ```
208
227
  *
209
228
  * @example With type and `as const`
210
229
  * ```ts
211
- * printConst(createConst({ name: 'pets', export: true, type: 'Pet[]', asConst: true, nodes: ['[]'] }))
230
+ * printConst(factory.createConst({ name: 'pets', export: true, type: 'Pet[]', asConst: true, nodes: ['[]'] }))
212
231
  * // 'export const pets: Pet[] = [] as const'
213
232
  * ```
214
233
  */
@@ -227,13 +246,13 @@ function printConst(node) {
227
246
  return [jsDocStr, parts.join("")].filter(Boolean).join("\n");
228
247
  }
229
248
  /**
230
- * Converts a {@link TypeNode} to a TypeScript `type` alias declaration string.
249
+ * Converts a {@link ast.TypeNode} to a TypeScript `type` alias declaration string.
231
250
  *
232
251
  * Mirrors the `Type` component from `@kubb/renderer-jsx`.
233
252
  *
234
253
  * @example
235
254
  * ```ts
236
- * printType(createType({ name: 'Pet', export: true, nodes: ['{ id: number }'] }))
255
+ * printType(factory.createType({ name: 'Pet', export: true, nodes: ['{ id: number }'] }))
237
256
  * // 'export type Pet = { id: number }'
238
257
  * ```
239
258
  */
@@ -250,19 +269,19 @@ function printType(node) {
250
269
  return [jsDocStr, parts.join("")].filter(Boolean).join("\n");
251
270
  }
252
271
  /**
253
- * Converts a {@link FunctionNode} to a TypeScript `function` declaration string.
272
+ * Converts a {@link ast.FunctionNode} to a TypeScript `function` declaration string.
254
273
  *
255
274
  * Mirrors the `Function` component from `@kubb/renderer-jsx`.
256
275
  *
257
276
  * @example
258
277
  * ```ts
259
- * printFunction(createFunction({ name: 'getPet', export: true, params: 'id: string', returnType: 'Pet', nodes: ['return fetch(id)'] }))
278
+ * printFunction(factory.createFunction({ name: 'getPet', export: true, params: 'id: string', returnType: 'Pet', nodes: ['return fetch(id)'] }))
260
279
  * // 'export function getPet(id: string): Pet {\n return fetch(id)\n}'
261
280
  * ```
262
281
  *
263
282
  * @example Async with generics
264
283
  * ```ts
265
- * printFunction(createFunction({ name: 'fetchPet', export: true, async: true, generics: ['T'], params: 'id: string', returnType: 'T' }))
284
+ * printFunction(factory.createFunction({ name: 'fetchPet', export: true, async: true, generics: ['T'], params: 'id: string', returnType: 'T' }))
266
285
  * // 'export async function fetchPet<T>(id: string): Promise<T> {\n}'
267
286
  * ```
268
287
  */
@@ -286,19 +305,19 @@ function printFunction(node) {
286
305
  return [jsDocStr, parts.join("")].filter(Boolean).join("\n");
287
306
  }
288
307
  /**
289
- * Converts an {@link ArrowFunctionNode} to a TypeScript arrow function declaration string.
308
+ * Converts an {@link ast.ArrowFunctionNode} to a TypeScript arrow function declaration string.
290
309
  *
291
310
  * Mirrors the `Function.Arrow` component from `@kubb/renderer-jsx`.
292
311
  *
293
312
  * @example Multi-line arrow function
294
313
  * ```ts
295
- * printArrowFunction(createArrowFunction({ name: 'getPet', export: true, params: 'id: string', nodes: ['return fetch(id)'] }))
314
+ * printArrowFunction(factory.createArrowFunction({ name: 'getPet', export: true, params: 'id: string', nodes: ['return fetch(id)'] }))
296
315
  * // 'export const getPet = (id: string) => {\n return fetch(id)\n}'
297
316
  * ```
298
317
  *
299
318
  * @example Single-line arrow function
300
319
  * ```ts
301
- * printArrowFunction(createArrowFunction({ name: 'double', params: 'n: number', singleLine: true, nodes: ['n * 2'] }))
320
+ * printArrowFunction(factory.createArrowFunction({ name: 'double', params: 'n: number', singleLine: true, nodes: ['n * 2'] }))
302
321
  * // 'const double = (n: number) => n * 2'
303
322
  * ```
304
323
  */
@@ -321,109 +340,210 @@ function printArrowFunction(node) {
321
340
  return [jsDocStr, parts.join("")].filter(Boolean).join("\n");
322
341
  }
323
342
  /**
324
- * Converts a {@link CodeNode} to its TypeScript string representation.
343
+ * Converts a {@link ast.CodeNode} to its TypeScript string representation.
325
344
  *
326
345
  * Dispatches to the appropriate printer based on the node's `kind`.
327
346
  *
328
347
  * @example
329
348
  * ```ts
330
- * printCodeNode(createConst({ name: 'x', nodes: ['1'] }))
349
+ * printCodeNode(factory.createConst({ name: 'x', nodes: ['1'] }))
331
350
  * // 'const x = 1'
332
351
  * ```
333
352
  */
334
353
  function printCodeNode(node) {
335
- switch (node.kind) {
336
- case "Break": return "";
337
- case "Text": return node.value;
338
- case "Jsx": return node.value;
339
- case "Const": return printConst(node);
340
- case "Type": return printType(node);
341
- case "Function": return printFunction(node);
342
- case "ArrowFunction": return printArrowFunction(node);
343
- }
354
+ if (node.kind === "Break") return "";
355
+ if (node.kind === "Text") return dedent(node.value);
356
+ if (node.kind === "Jsx") return dedent(node.value);
357
+ if (node.kind === "Const") return printConst(node);
358
+ if (node.kind === "Type") return printType(node);
359
+ if (node.kind === "Function") return printFunction(node);
360
+ if (node.kind === "ArrowFunction") return printArrowFunction(node);
361
+ return "";
344
362
  }
345
363
  /**
346
- * Converts a {@link SourceNode} to its TypeScript string representation.
364
+ * Converts a {@link ast.SourceNode} to its TypeScript string representation.
347
365
  *
348
- * Iterates `nodes` in DOM order, rendering each {@link CodeNode} via
366
+ * Iterates `nodes` in DOM order, rendering each {@link ast.CodeNode} via
349
367
  * {@link printCodeNode}.
350
368
  *
369
+ * Top-level declarations are separated by a blank line so the source reads
370
+ * cleanly without an external formatter.
371
+ *
351
372
  * @example From nodes
352
373
  * ```ts
353
- * printSource({ kind: 'Source', nodes: [createConst({ name: 'x', nodes: [createText('1')] }), createText('x.toString()')] })
354
- * // 'const x = 1\nx.toString()'
374
+ * printSource({ kind: 'Source', nodes: [factory.createConst({ name: 'x', nodes: [factory.createText('1')] }), factory.createText('x.toString()')] })
375
+ * // 'const x = 1\n\nx.toString()'
355
376
  * ```
356
377
  */
357
378
  function printSource(node) {
358
- if (node.nodes && node.nodes.length > 0) return node.nodes.map(printCodeNode).join("\n");
359
- return "";
379
+ const nodes = node.nodes;
380
+ if (!nodes || nodes.length === 0) return "";
381
+ let result = "";
382
+ for (const child of nodes) {
383
+ const text = printCodeNode(child);
384
+ if (!text) continue;
385
+ result = result ? `${result}\n\n${text}` : text;
386
+ }
387
+ return result;
360
388
  }
361
389
  /**
362
- * Parser that converts `.ts` and `.js` files to strings using the TypeScript
363
- * compiler. Handles import/export statement generation from file metadata.
390
+ * Wraps a module specifier in single quotes, escaping any embedded backslash or quote so the emitted
391
+ * statement stays valid even for unusual paths.
392
+ */
393
+ function quoteModulePath(path) {
394
+ return `'${path.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
395
+ }
396
+ /**
397
+ * Renders an import declaration string in the repo style (single quotes, no semicolons), covering
398
+ * default, namespace (`* as`), and named imports with `{ a as b }` aliases, each optionally
399
+ * `type`-only. `path` is used verbatim, so resolve it first.
364
400
  *
365
- * @default Used automatically when no `parsers` option is set in `defineConfig`.
401
+ * @example
402
+ * ```ts
403
+ * printImport({ name: ['z'], path: './zod.ts' })
404
+ * // "import { z } from './zod.ts'"
405
+ * ```
366
406
  */
367
- const parserTs = (0, _kubb_core.defineParser)({
368
- name: "typescript",
369
- extNames: [".ts", ".js"],
370
- async parse(file, options = { extname: ".ts" }) {
371
- const sourceParts = [];
372
- for (const item of file.sources) {
373
- const sourceStr = printSource(item);
374
- if (sourceStr) sourceParts.push(sourceStr.trimEnd());
375
- }
376
- const source = sourceParts.join("\n\n");
377
- const importNodes = [];
378
- for (const item of file.imports) {
379
- const importPath = item.root ? getRelativePath(item.root, item.path) : item.path;
380
- importNodes.push(createImport({
407
+ function printImport({ name, path, isTypeOnly = false, isNameSpace = false }) {
408
+ const typePrefix = isTypeOnly ? "type " : "";
409
+ const from = quoteModulePath(path);
410
+ if (!Array.isArray(name)) {
411
+ if (isNameSpace) return `import ${typePrefix}* as ${name} from ${from}`;
412
+ return `import ${typePrefix}${name} from ${from}`;
413
+ }
414
+ return `import ${typePrefix}{ ${name.map((item) => {
415
+ if (typeof item === "object") return item.name ? `${item.propertyName} as ${item.name}` : item.propertyName;
416
+ return item;
417
+ }).join(", ")} } from ${from}`;
418
+ }
419
+ /**
420
+ * Renders an export declaration string in the repo style (single quotes, no semicolons), covering
421
+ * named re-exports, namespace alias (`* as name`), and wildcard, each optionally `type`-only.
422
+ * `path` is used verbatim, so resolve it first.
423
+ *
424
+ * @example
425
+ * ```ts
426
+ * printExport({ name: ['Pet', 'Order'], path: './models.ts' })
427
+ * // "export { Pet, Order } from './models.ts'"
428
+ * ```
429
+ */
430
+ function printExport({ path, name, isTypeOnly = false, asAlias = false }) {
431
+ const typePrefix = isTypeOnly ? "type " : "";
432
+ const from = quoteModulePath(path);
433
+ if (Array.isArray(name)) return `export ${typePrefix}{ ${name.map((item) => typeof item === "string" ? item : item.text).join(", ")} } from ${from}`;
434
+ if (asAlias && name) return `export ${typePrefix}* as ${LEADING_DIGIT_PATTERN.test(name) ? `_${name.slice(1)}` : name} from ${from}`;
435
+ if (name) return `export ${typePrefix}{ ${name} } from ${from}`;
436
+ return `export ${typePrefix}* from ${from}`;
437
+ }
438
+ //#endregion
439
+ //#region src/parserTs.ts
440
+ const DEFAULT_EXTENSION = { ".ts": "" };
441
+ /**
442
+ * Default Kubb parser for `.ts` and `.js` files. Takes the universal AST
443
+ * produced by an adapter and prints it as TypeScript source using the official
444
+ * TypeScript compiler. Imports and exports are rewritten based on each file's
445
+ * metadata and the `extension` option.
446
+ *
447
+ * Used automatically when no `parsers` option is set on `defineConfig`. Use
448
+ * `parserTsx` instead for React projects that emit JSX.
449
+ *
450
+ * @example
451
+ * ```ts
452
+ * import { defineConfig } from 'kubb'
453
+ * import { adapterOas } from '@kubb/adapter-oas'
454
+ * import { parserTs } from '@kubb/parser-ts'
455
+ *
456
+ * export default defineConfig({
457
+ * input: './petStore.yaml',
458
+ * output: { path: './src/gen' },
459
+ * adapter: adapterOas(),
460
+ * parsers: [parserTs()],
461
+ * plugins: [],
462
+ * })
463
+ * ```
464
+ */
465
+ const parserTs = (0, _kubb_kit.defineParser)(({ extension = DEFAULT_EXTENSION } = {}) => {
466
+ return {
467
+ name: "typescript",
468
+ extNames: [".ts", ".js"],
469
+ print(...nodes) {
470
+ return print(...nodes);
471
+ },
472
+ parse(file) {
473
+ const extname = extension[file.extname] || void 0;
474
+ const sourceParts = [];
475
+ for (const item of file.sources) {
476
+ const sourceStr = printSource(item);
477
+ if (sourceStr) sourceParts.push(sourceStr.trimEnd());
478
+ }
479
+ const source = sourceParts.join("\n\n");
480
+ const importLines = [];
481
+ for (const item of file.imports) {
482
+ const importPath = item.root ? getRelativePath(item.root, item.path) : item.path;
483
+ importLines.push(printImport({
484
+ name: item.name,
485
+ path: resolveOutputPath(importPath, { extname }, Boolean(item.root)),
486
+ isTypeOnly: item.isTypeOnly,
487
+ isNameSpace: item.isNameSpace
488
+ }));
489
+ }
490
+ const exportLines = [];
491
+ for (const item of file.exports) exportLines.push(printExport({
381
492
  name: item.name,
382
- path: resolveOutputPath(importPath, options, Boolean(item.root)),
493
+ path: resolveOutputPath(item.path, { extname }, true),
383
494
  isTypeOnly: item.isTypeOnly,
384
- isNameSpace: item.isNameSpace
495
+ asAlias: item.asAlias
385
496
  }));
497
+ const importExportBlock = [...importLines, ...exportLines].join("\n");
498
+ return [
499
+ file.banner,
500
+ importExportBlock,
501
+ source,
502
+ file.footer
503
+ ].filter((segment) => Boolean(segment)).map((s) => s.trimEnd()).join("\n\n");
386
504
  }
387
- const exportNodes = [];
388
- for (const item of file.exports) exportNodes.push(createExport({
389
- name: item.name,
390
- path: resolveOutputPath(item.path, options, true),
391
- isTypeOnly: item.isTypeOnly,
392
- asAlias: item.asAlias
393
- }));
394
- return [
395
- file.banner,
396
- print(...importNodes, ...exportNodes),
397
- source,
398
- file.footer
399
- ].filter((segment) => Boolean(segment)).map((s) => s.trimEnd()).join("\n\n");
400
- }
505
+ };
401
506
  });
402
507
  //#endregion
403
508
  //#region src/parserTsx.ts
404
509
  /**
405
- * Parser that converts `.tsx` and `.jsx` files to strings.
406
- * Delegates to `typescriptParser` since the TypeScript compiler natively
407
- * supports JSX/TSX syntax via `ScriptKind.TSX`.
510
+ * Kubb parser for `.tsx` and `.jsx` files. Delegates to `parserTs` because the
511
+ * TypeScript compiler handles JSX natively via `ScriptKind.TSX`, so it shares the
512
+ * same `extension` option.
513
+ *
514
+ * Add to the `parsers` array on `defineConfig` when generating components for
515
+ * React (or any framework that emits JSX).
408
516
  *
409
- * Add this parser to the `parsers` option in `defineConfig` when generating `.tsx`/`.jsx` files.
517
+ * @example
518
+ * ```ts
519
+ * import { defineConfig } from 'kubb'
520
+ * import { adapterOas } from '@kubb/adapter-oas'
521
+ * import { parserTsx } from '@kubb/parser-ts'
410
522
  *
411
- * @default extname '.tsx'
523
+ * export default defineConfig({
524
+ * input: './petStore.yaml',
525
+ * output: { path: './src/gen' },
526
+ * adapter: adapterOas(),
527
+ * parsers: [parserTsx()],
528
+ * plugins: [],
529
+ * })
530
+ * ```
412
531
  */
413
- const parserTsx = (0, _kubb_core.defineParser)({
414
- name: "tsx",
415
- extNames: [".tsx", ".jsx"],
416
- async parse(file, options = { extname: ".tsx" }) {
417
- return parserTs.parse(file, options);
418
- }
532
+ const parserTsx = (0, _kubb_kit.defineParser)((options = {}) => {
533
+ const parser = parserTs(options);
534
+ return {
535
+ name: "tsx",
536
+ extNames: [".tsx", ".jsx"],
537
+ print(...nodes) {
538
+ return print(...nodes);
539
+ },
540
+ parse(file) {
541
+ return parser.parse(file);
542
+ }
543
+ };
419
544
  });
420
545
  //#endregion
421
- exports.createExport = createExport;
422
- exports.createImport = createImport;
423
546
  exports.parserTs = parserTs;
424
547
  exports.parserTsx = parserTsx;
425
- exports.print = print;
426
- exports.safePrint = safePrint;
427
- exports.validateNodes = validateNodes;
428
548
 
429
549
  //# sourceMappingURL=index.cjs.map