@kubb/parser-ts 5.0.0-beta.9 → 5.0.0-beta.91

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
@@ -25,10 +25,29 @@ let _kubb_core = require("@kubb/core");
25
25
  let node_path = require("node:path");
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,48 +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
76
  //#region src/utils.ts
58
77
  const { factory } = typescript.default;
59
78
  /**
60
- * Normalises a file-system path to POSIX separators and strips any leading `../` segment.
61
- */
62
- function slash(path) {
63
- return (0, node_path.normalize)(path).replaceAll(WINDOWS_PATH_SEPARATOR, "/").replace("../", "");
64
- }
65
- /**
66
79
  * Resolves `filePath` relative to `rootDir` and returns a POSIX-style path
67
80
  * prefixed with `./` when the target sits inside the root, or `../` when it escapes it.
68
81
  */
69
82
  function getRelativePath(rootDir, filePath) {
70
- 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("../", "");
71
84
  return slashed.startsWith("../") ? slashed : `./${slashed}`;
72
85
  }
73
86
  /**
74
- * Strips the trailing file extension (for example `.ts`) from a path.
75
- * Preserves intermediate dots like `foo.bar.ts` → `foo.bar`.
76
- */
77
- function trimExtName(text) {
78
- return text.replace(FILE_EXTENSION_PATTERN, "");
79
- }
80
- /**
81
87
  * Rewrites an import/export path so its extension matches the caller-supplied
82
88
  * `options.extname`. When the source path has no extension the original is kept,
83
89
  * so virtual/module-only paths flow through unchanged.
@@ -88,25 +94,64 @@ function resolveOutputPath(path, options, rootAware) {
88
94
  return rootAware ? trimExtName(path) : path;
89
95
  }
90
96
  /**
91
- * Serializes the body / value content from a `nodes` array.
92
- *
93
- * Each element is either a raw string or a structured {@link CodeNode}
94
- * (recursively converted via {@link printCodeNode}).
95
- * Elements are joined with `\n`.
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.
96
101
  */
97
102
  function printNodes(nodes) {
98
103
  if (!nodes || nodes.length === 0) return "";
99
- return nodes.map(printCodeNode).join("\n");
104
+ let result = "";
105
+ let hasContent = false;
106
+ let pendingBreak = false;
107
+ for (const node of nodes) {
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;
118
+ }
119
+ return result;
100
120
  }
101
121
  /**
102
- * Indents every non-empty line of `text` by `spaces` spaces.
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.
103
124
  */
104
- function indentLines(text, spaces = 2) {
125
+ function indentLines(text, indent = INDENT) {
105
126
  if (!text) return "";
106
- const pad = " ".repeat(spaces);
127
+ const pad = typeof indent === "string" ? indent : " ".repeat(indent);
107
128
  return text.split("\n").map((line) => line.trim() ? `${pad}${line}` : "").join("\n");
108
129
  }
109
130
  /**
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
+ * ```
141
+ */
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");
153
+ }
154
+ /**
110
155
  * Renders the generic clause (`<T, U>`) shared by function and arrow-function nodes.
111
156
  * Accepts either a raw string (rendered verbatim) or an array of type-parameter names.
112
157
  */
@@ -123,34 +168,28 @@ function formatReturnType(returnType, isAsync) {
123
168
  return isAsync ? `: Promise<${returnType}>` : `: ${returnType}`;
124
169
  }
125
170
  /**
126
- * Validates TypeScript AST nodes before printing.
127
- * Throws an error if any node has SyntaxKind.Unknown which would cause the
128
- * TypeScript printer to crash.
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.
129
173
  */
130
- function validateNodes(...nodes) {
131
- for (const node of nodes) {
132
- if (!node) throw new Error("Attempted to print undefined or null TypeScript node");
133
- 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)}`);
134
- }
135
- }
174
+ const TS_PRINTER = typescript.default.createPrinter({
175
+ omitTrailingSemicolon: true,
176
+ newLine: typescript.default.NewLineKind.LineFeed,
177
+ removeComments: false,
178
+ noEmitHelpers: true
179
+ });
136
180
  /**
137
- * Converts TypeScript/TSX AST nodes to a string using the TypeScript printer.
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.
138
183
  */
139
- function print(...elements) {
140
- const sourceFile = typescript.default.createSourceFile("print.tsx", "", typescript.default.ScriptTarget.ES2022, true, typescript.default.ScriptKind.TSX);
141
- return typescript.default.createPrinter({
142
- omitTrailingSemicolon: true,
143
- newLine: typescript.default.NewLineKind.LineFeed,
144
- removeComments: false,
145
- noEmitHelpers: true
146
- }).printList(typescript.default.ListFormat.MultiLine, factory.createNodeArray(elements.filter(Boolean)), sourceFile).replace(CRLF_PATTERN, "\n");
147
- }
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);
148
186
  /**
149
- * Like `print` but validates nodes first to surface issues early.
187
+ * Converts TypeScript/TSX AST nodes to a string using the TypeScript printer.
150
188
  */
151
- function safePrint(...elements) {
152
- validateNodes(...elements);
153
- return print(...elements);
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");
154
193
  }
155
194
  /**
156
195
  * Converts a {@link JSDocNode} to a JSDoc comment block string.
@@ -182,13 +221,13 @@ function printJSDoc(jsDoc) {
182
221
  *
183
222
  * @example
184
223
  * ```ts
185
- * printConst(createConst({ name: 'pet', export: true, nodes: ['{}'] }))
224
+ * printConst(factory.createConst({ name: 'pet', export: true, nodes: ['{}'] }))
186
225
  * // 'export const pet = {}'
187
226
  * ```
188
227
  *
189
228
  * @example With type and `as const`
190
229
  * ```ts
191
- * printConst(createConst({ name: 'pets', export: true, type: 'Pet[]', asConst: true, nodes: ['[]'] }))
230
+ * printConst(factory.createConst({ name: 'pets', export: true, type: 'Pet[]', asConst: true, nodes: ['[]'] }))
192
231
  * // 'export const pets: Pet[] = [] as const'
193
232
  * ```
194
233
  */
@@ -213,7 +252,7 @@ function printConst(node) {
213
252
  *
214
253
  * @example
215
254
  * ```ts
216
- * printType(createType({ name: 'Pet', export: true, nodes: ['{ id: number }'] }))
255
+ * printType(factory.createType({ name: 'Pet', export: true, nodes: ['{ id: number }'] }))
217
256
  * // 'export type Pet = { id: number }'
218
257
  * ```
219
258
  */
@@ -236,13 +275,13 @@ function printType(node) {
236
275
  *
237
276
  * @example
238
277
  * ```ts
239
- * 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)'] }))
240
279
  * // 'export function getPet(id: string): Pet {\n return fetch(id)\n}'
241
280
  * ```
242
281
  *
243
282
  * @example Async with generics
244
283
  * ```ts
245
- * 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' }))
246
285
  * // 'export async function fetchPet<T>(id: string): Promise<T> {\n}'
247
286
  * ```
248
287
  */
@@ -272,13 +311,13 @@ function printFunction(node) {
272
311
  *
273
312
  * @example Multi-line arrow function
274
313
  * ```ts
275
- * 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)'] }))
276
315
  * // 'export const getPet = (id: string) => {\n return fetch(id)\n}'
277
316
  * ```
278
317
  *
279
318
  * @example Single-line arrow function
280
319
  * ```ts
281
- * 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'] }))
282
321
  * // 'const double = (n: number) => n * 2'
283
322
  * ```
284
323
  */
@@ -307,20 +346,19 @@ function printArrowFunction(node) {
307
346
  *
308
347
  * @example
309
348
  * ```ts
310
- * printCodeNode(createConst({ name: 'x', nodes: ['1'] }))
349
+ * printCodeNode(factory.createConst({ name: 'x', nodes: ['1'] }))
311
350
  * // 'const x = 1'
312
351
  * ```
313
352
  */
314
353
  function printCodeNode(node) {
315
- switch (node.kind) {
316
- case "Break": return "";
317
- case "Text": return node.value;
318
- case "Jsx": return node.value;
319
- case "Const": return printConst(node);
320
- case "Type": return printType(node);
321
- case "Function": return printFunction(node);
322
- case "ArrowFunction": return printArrowFunction(node);
323
- }
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 "";
324
362
  }
325
363
  /**
326
364
  * Converts a {@link SourceNode} to its TypeScript string representation.
@@ -328,107 +366,184 @@ function printCodeNode(node) {
328
366
  * Iterates `nodes` in DOM order, rendering each {@link CodeNode} via
329
367
  * {@link printCodeNode}.
330
368
  *
369
+ * Top-level declarations are separated by a blank line so the source reads
370
+ * cleanly without an external formatter.
371
+ *
331
372
  * @example From nodes
332
373
  * ```ts
333
- * printSource({ kind: 'Source', nodes: [createConst({ name: 'x', nodes: [createText('1')] }), createText('x.toString()')] })
334
- * // '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()'
335
376
  * ```
336
377
  */
337
378
  function printSource(node) {
338
- if (node.nodes && node.nodes.length > 0) return node.nodes.map(printCodeNode).join("\n");
339
- return "";
340
- }
341
- function createImport({ name, path, root, isTypeOnly = false, isNameSpace = false }) {
342
- const resolvePath = root ? getRelativePath(root, path) : path;
343
- if (!Array.isArray(name)) {
344
- if (isNameSpace) return factory.createImportDeclaration(void 0, factory.createImportClause(isTypeOnly, void 0, factory.createNamespaceImport(factory.createIdentifier(name))), factory.createStringLiteral(resolvePath), void 0);
345
- return factory.createImportDeclaration(void 0, factory.createImportClause(isTypeOnly, factory.createIdentifier(name), void 0), factory.createStringLiteral(resolvePath), void 0);
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;
346
386
  }
347
- const specifiers = name.map((item) => {
348
- if (typeof item === "object") {
349
- const { propertyName, name: alias } = item;
350
- return factory.createImportSpecifier(false, alias ? factory.createIdentifier(propertyName) : void 0, factory.createIdentifier(alias ?? propertyName));
351
- }
352
- return factory.createImportSpecifier(false, void 0, factory.createIdentifier(item));
353
- });
354
- return factory.createImportDeclaration(void 0, factory.createImportClause(isTypeOnly, void 0, factory.createNamedImports(specifiers)), factory.createStringLiteral(resolvePath), void 0);
387
+ return result;
388
+ }
389
+ /**
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, "\\'")}'`;
355
395
  }
356
- function createExport({ path, asAlias, isTypeOnly = false, name }) {
357
- if (name && !Array.isArray(name) && !asAlias) console.warn(`When using name as string, asAlias should be true: ${name}`);
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.
400
+ *
401
+ * @example
402
+ * ```ts
403
+ * printImport({ name: ['z'], path: './zod.ts' })
404
+ * // "import { z } from './zod.ts'"
405
+ * ```
406
+ */
407
+ function printImport({ name, path, isTypeOnly = false, isNameSpace = false }) {
408
+ const typePrefix = isTypeOnly ? "type " : "";
409
+ const from = quoteModulePath(path);
358
410
  if (!Array.isArray(name)) {
359
- const parsedName = name && LEADING_DIGIT_PATTERN.test(name) ? `_${name.slice(1)}` : name;
360
- return factory.createExportDeclaration(void 0, isTypeOnly, asAlias && parsedName ? factory.createNamespaceExport(factory.createIdentifier(parsedName)) : void 0, factory.createStringLiteral(path), void 0);
411
+ if (isNameSpace) return `import ${typePrefix}* as ${name} from ${from}`;
412
+ return `import ${typePrefix}${name} from ${from}`;
361
413
  }
362
- 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);
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) console.warn(`When using name as string, asAlias should be true: ${name}`);
436
+ return `export ${typePrefix}* from ${from}`;
363
437
  }
364
438
  //#endregion
365
439
  //#region src/parserTs.ts
440
+ const DEFAULT_EXTENSION = { ".ts": ".ts" };
366
441
  /**
367
- * Parser that converts `.ts` and `.js` files to strings using the TypeScript
368
- * compiler. Handles import/export statement generation from file metadata.
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'
369
455
  *
370
- * @default Used automatically when no `parsers` option is set in `defineConfig`.
456
+ * export default defineConfig({
457
+ * input: './petStore.yaml',
458
+ * output: { path: './src/gen' },
459
+ * adapter: adapterOas(),
460
+ * parsers: [parserTs()],
461
+ * plugins: [],
462
+ * })
463
+ * ```
371
464
  */
372
- const parserTs = (0, _kubb_core.defineParser)({
373
- name: "typescript",
374
- extNames: [".ts", ".js"],
375
- async parse(file, options = { extname: ".ts" }) {
376
- const sourceParts = [];
377
- for (const item of file.sources) {
378
- const sourceStr = printSource(item);
379
- if (sourceStr) sourceParts.push(sourceStr.trimEnd());
380
- }
381
- const source = sourceParts.join("\n\n");
382
- const importNodes = [];
383
- for (const item of file.imports) {
384
- const importPath = item.root ? getRelativePath(item.root, item.path) : item.path;
385
- importNodes.push(createImport({
465
+ const parserTs = (0, _kubb_core.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({
386
492
  name: item.name,
387
- path: resolveOutputPath(importPath, options, Boolean(item.root)),
493
+ path: resolveOutputPath(item.path, { extname }, true),
388
494
  isTypeOnly: item.isTypeOnly,
389
- isNameSpace: item.isNameSpace
495
+ asAlias: item.asAlias
390
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");
391
504
  }
392
- const exportNodes = [];
393
- for (const item of file.exports) exportNodes.push(createExport({
394
- name: item.name,
395
- path: resolveOutputPath(item.path, options, true),
396
- isTypeOnly: item.isTypeOnly,
397
- asAlias: item.asAlias
398
- }));
399
- return [
400
- file.banner,
401
- print(...importNodes, ...exportNodes),
402
- source,
403
- file.footer
404
- ].filter((segment) => Boolean(segment)).map((s) => s.trimEnd()).join("\n\n");
405
- }
505
+ };
406
506
  });
407
507
  //#endregion
408
508
  //#region src/parserTsx.ts
409
509
  /**
410
- * Parser that converts `.tsx` and `.jsx` files to strings.
411
- * Delegates to `typescriptParser` since the TypeScript compiler natively
412
- * 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.
413
513
  *
414
- * Add this parser to the `parsers` option in `defineConfig` when generating `.tsx`/`.jsx` files.
514
+ * Add to the `parsers` array on `defineConfig` when generating components for
515
+ * React (or any framework that emits JSX).
415
516
  *
416
- * @default extname '.tsx'
517
+ * @example
518
+ * ```ts
519
+ * import { defineConfig } from 'kubb'
520
+ * import { adapterOas } from '@kubb/adapter-oas'
521
+ * import { parserTsx } from '@kubb/parser-ts'
522
+ *
523
+ * export default defineConfig({
524
+ * input: './petStore.yaml',
525
+ * output: { path: './src/gen' },
526
+ * adapter: adapterOas(),
527
+ * parsers: [parserTsx()],
528
+ * plugins: [],
529
+ * })
530
+ * ```
417
531
  */
418
- const parserTsx = (0, _kubb_core.defineParser)({
419
- name: "tsx",
420
- extNames: [".tsx", ".jsx"],
421
- async parse(file, options = { extname: ".tsx" }) {
422
- return parserTs.parse(file, options);
423
- }
532
+ const parserTsx = (0, _kubb_core.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
+ };
424
544
  });
425
545
  //#endregion
426
- exports.createExport = createExport;
427
- exports.createImport = createImport;
428
546
  exports.parserTs = parserTs;
429
547
  exports.parserTsx = parserTsx;
430
- exports.print = print;
431
- exports.safePrint = safePrint;
432
- exports.validateNodes = validateNodes;
433
548
 
434
549
  //# sourceMappingURL=index.cjs.map