@kubb/parser-ts 5.5.6 → 5.6.1
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 +152 -319
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +153 -320
- package/dist/index.js.map +1 -1
- package/package.json +3 -7
package/dist/index.js
CHANGED
|
@@ -1,9 +1,26 @@
|
|
|
1
1
|
import "./rolldown-runtime-CRm0XQPb.js";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { ast, defineParser } from "@kubb/kit";
|
|
4
|
-
import { dirname,
|
|
4
|
+
import { dirname, relative, resolve } from "node:path";
|
|
5
5
|
//#region ../../internals/utils/src/fs.ts
|
|
6
6
|
/**
|
|
7
|
+
* Converts a filesystem path to use POSIX (`/`) separators.
|
|
8
|
+
*
|
|
9
|
+
* Most of the codebase compares and composes paths as strings (prefix matching, joining for
|
|
10
|
+
* import specifiers, splitting on `/`). On POSIX `path.resolve` already returns `/`-separated
|
|
11
|
+
* paths, but on Windows it returns `\`-separated paths, which breaks every such comparison.
|
|
12
|
+
*
|
|
13
|
+
* Routing every path that crosses a module boundary through `toPosixPath` keeps the rest of the
|
|
14
|
+
* code platform-agnostic. The conversion runs unconditionally so Windows-specific behavior is
|
|
15
|
+
* exercisable from POSIX CI.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* toPosixPath('C:\\repo\\src\\pet.ts') // 'C:/repo/src/pet.ts'
|
|
19
|
+
*/
|
|
20
|
+
function toPosixPath(filePath) {
|
|
21
|
+
return filePath.replaceAll("\\", "/");
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
7
24
|
* Strips the file extension from a path or file name.
|
|
8
25
|
* Only removes the last `.ext` segment when the dot is not part of a directory name.
|
|
9
26
|
*
|
|
@@ -18,61 +35,42 @@ function trimExtName(text) {
|
|
|
18
35
|
if (dotIndex > 0 && !text.includes("/", dotIndex)) return text.slice(0, dotIndex);
|
|
19
36
|
return text;
|
|
20
37
|
}
|
|
21
|
-
/**
|
|
22
|
-
* Indentation unit prepended once per nesting level when pretty-printing.
|
|
23
|
-
*/
|
|
24
|
-
const INDENT = " ".repeat(2);
|
|
25
|
-
/**
|
|
26
|
-
* Matches only the final `.<ext>` of a path, so a name like `foo.bar.ts` keeps
|
|
27
|
-
* `foo.bar` and loses just `.ts`.
|
|
28
|
-
*/
|
|
29
|
-
const FILE_EXTENSION_PATTERN = /\.[^/.]+$/;
|
|
30
|
-
/**
|
|
31
|
-
* Matches Windows-style backslash path separators.
|
|
32
|
-
*/
|
|
33
|
-
const WINDOWS_PATH_SEPARATOR = /\\/g;
|
|
34
|
-
/**
|
|
35
|
-
* Matches `*\/` in free-form text so JSDoc bodies can neutralize premature
|
|
36
|
-
* comment terminators (`*\/` → `* /`).
|
|
37
|
-
*/
|
|
38
|
-
const JSDOC_TERMINATOR_PATTERN = /\*\//g;
|
|
39
|
-
/**
|
|
40
|
-
* Matches carriage returns for normalizing CRLF/CR line endings to LF.
|
|
41
|
-
*/
|
|
42
|
-
const CARRIAGE_RETURN_PATTERN = /\r/g;
|
|
43
|
-
/**
|
|
44
|
-
* Matches CRLF sequences used when normalizing TypeScript printer output.
|
|
45
|
-
*/
|
|
46
|
-
const CRLF_PATTERN = /\r\n/g;
|
|
47
|
-
/**
|
|
48
|
-
* Matches an identifier that starts with a digit. JavaScript disallows this,
|
|
49
|
-
* so the printer replaces the leading digit with `_`.
|
|
50
|
-
*/
|
|
51
|
-
const LEADING_DIGIT_PATTERN = /^\d/;
|
|
52
38
|
//#endregion
|
|
53
39
|
//#region src/utils.ts
|
|
40
|
+
let compiler;
|
|
54
41
|
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
42
|
+
* Loads TypeScript on the first `print` or `copy`; `parse` is string templating and never needs it.
|
|
43
|
+
*
|
|
44
|
+
* Loaded with `require`, not `import`: when ESM imports a CommonJS package, Node keeps a second copy of its source
|
|
45
|
+
* to detect named exports, and for TypeScript that copy is about 9 MB.
|
|
46
|
+
*/
|
|
47
|
+
function loadCompiler() {
|
|
48
|
+
if (compiler) return compiler;
|
|
49
|
+
const typescript = createRequire(import.meta.url)("typescript");
|
|
50
|
+
compiler = {
|
|
51
|
+
typescript,
|
|
52
|
+
printer: typescript.createPrinter({
|
|
53
|
+
omitTrailingSemicolon: true,
|
|
54
|
+
newLine: typescript.NewLineKind.LineFeed,
|
|
55
|
+
removeComments: false,
|
|
56
|
+
noEmitHelpers: true
|
|
57
|
+
}),
|
|
58
|
+
sourceFile: typescript.createSourceFile("print.tsx", "", typescript.ScriptTarget.ES2022, true, typescript.ScriptKind.TSX)
|
|
59
|
+
};
|
|
60
|
+
return compiler;
|
|
61
|
+
}
|
|
60
62
|
/**
|
|
61
|
-
*
|
|
62
|
-
* prefixed with `./` when the target sits inside the root, or `../` when it escapes it.
|
|
63
|
+
* Module specifier that imports `filePath` from the file at `root`: POSIX, relative to that file's directory, `./`-prefixed unless it climbs with `../`.
|
|
63
64
|
*/
|
|
64
|
-
function getRelativePath(
|
|
65
|
-
const
|
|
66
|
-
const slashed = normalize(rel).replaceAll(WINDOWS_PATH_SEPARATOR, "/").replace("../", "");
|
|
65
|
+
function getRelativePath(root, filePath) {
|
|
66
|
+
const slashed = toPosixPath(relative(dirname(root), filePath));
|
|
67
67
|
return slashed.startsWith("../") ? slashed : `./${slashed}`;
|
|
68
68
|
}
|
|
69
69
|
/**
|
|
70
|
-
*
|
|
71
|
-
* `options.extname`. When the source path has no extension the original is kept,
|
|
72
|
-
* so virtual/module-only paths flow through unchanged.
|
|
70
|
+
* Swaps the extension of a relative import/export path for `options.extname`; package specifiers and extension-less paths stay as-is.
|
|
73
71
|
*/
|
|
74
72
|
function resolveOutputPath(path, options, rootAware) {
|
|
75
|
-
const hasExtname =
|
|
73
|
+
const hasExtname = /\.[^/.]+$/.test(path);
|
|
76
74
|
if (rootAware && options?.extname && hasExtname) return `${trimExtName(path)}${options.extname}`;
|
|
77
75
|
return rootAware ? trimExtName(path) : path;
|
|
78
76
|
}
|
|
@@ -81,6 +79,7 @@ function resolveOutputPath(path, options, rootAware) {
|
|
|
81
79
|
*/
|
|
82
80
|
function toImportName(element) {
|
|
83
81
|
if (!element.propertyName) return element.name.text;
|
|
82
|
+
const { typescript } = loadCompiler();
|
|
84
83
|
const { propertyName } = element;
|
|
85
84
|
return {
|
|
86
85
|
propertyName: typescript.isStringLiteral(propertyName) ? quoteModulePath(propertyName.text) : propertyName.text,
|
|
@@ -91,6 +90,7 @@ function toImportName(element) {
|
|
|
91
90
|
* Converts an `import` declaration into `ImportNode`s. Side-effect imports and imports with attributes stay as written.
|
|
92
91
|
*/
|
|
93
92
|
function toImportNodes(statement, filePath) {
|
|
93
|
+
const { typescript } = loadCompiler();
|
|
94
94
|
if (!typescript.isImportDeclaration(statement) || !statement.importClause || !typescript.isStringLiteral(statement.moduleSpecifier) || statement.attributes) return [];
|
|
95
95
|
const { name, namedBindings, phaseModifier } = statement.importClause;
|
|
96
96
|
const specifier = statement.moduleSpecifier.text;
|
|
@@ -131,6 +131,7 @@ function toImportNodes(statement, filePath) {
|
|
|
131
131
|
* Converts an `export … from` declaration into `ExportNode`s. Forms `printExport` cannot print stay as written.
|
|
132
132
|
*/
|
|
133
133
|
function toExportNodes(statement) {
|
|
134
|
+
const { typescript } = loadCompiler();
|
|
134
135
|
if (!typescript.isExportDeclaration(statement) || !statement.moduleSpecifier || !typescript.isStringLiteral(statement.moduleSpecifier) || statement.attributes) return [];
|
|
135
136
|
const { exportClause, isTypeOnly } = statement;
|
|
136
137
|
const path = statement.moduleSpecifier.text;
|
|
@@ -151,14 +152,11 @@ function toExportNodes(statement) {
|
|
|
151
152
|
isTypeOnly
|
|
152
153
|
})];
|
|
153
154
|
}
|
|
154
|
-
function isModuleDeclaration(statement) {
|
|
155
|
-
return typescript.isImportDeclaration(statement) || typescript.isExportDeclaration(statement) && Boolean(statement.moduleSpecifier);
|
|
156
|
-
}
|
|
157
155
|
/**
|
|
158
|
-
* Splits `source` into its
|
|
159
|
-
* (shebang, directives, comments) and the remaining `body`.
|
|
156
|
+
* Splits `source` into its lifted `import`/`export … from` nodes, the `header` above the first one (shebang, directives, comments) and the rest as `body`.
|
|
160
157
|
*/
|
|
161
158
|
function splitModuleDeclarations(source, filePath) {
|
|
159
|
+
const { typescript } = loadCompiler();
|
|
162
160
|
const sourceFile = typescript.createSourceFile(filePath, source, typescript.ScriptTarget.Latest);
|
|
163
161
|
const imports = [];
|
|
164
162
|
const exports = [];
|
|
@@ -169,7 +167,7 @@ function splitModuleDeclarations(source, filePath) {
|
|
|
169
167
|
const importNodes = toImportNodes(statement, filePath);
|
|
170
168
|
const exportNodes = toExportNodes(statement);
|
|
171
169
|
if (!importNodes.length && !exportNodes.length) {
|
|
172
|
-
if (
|
|
170
|
+
if (typescript.isImportDeclaration(statement) || typescript.isExportDeclaration(statement) && statement.moduleSpecifier) break;
|
|
173
171
|
continue;
|
|
174
172
|
}
|
|
175
173
|
const text = source.slice(cursor, statement.getStart(sourceFile));
|
|
@@ -187,50 +185,35 @@ function splitModuleDeclarations(source, filePath) {
|
|
|
187
185
|
};
|
|
188
186
|
}
|
|
189
187
|
/**
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
* separator, so a double `<br/>` never emits more than one blank line.
|
|
188
|
+
* Joins the printed `nodes` with `separator`; a `Break` widens the next separator to a blank line, and leading, trailing and repeated breaks fold into one.
|
|
189
|
+
*
|
|
190
|
+
* Imperative on purpose: this runs once per source fragment and `map().filter().join()` showed up in deopt traces.
|
|
194
191
|
*/
|
|
195
|
-
function printNodes(nodes) {
|
|
196
|
-
if (!nodes || nodes.length === 0) return "";
|
|
192
|
+
function printNodes({ nodes, separator }) {
|
|
197
193
|
let result = "";
|
|
198
|
-
let hasContent = false;
|
|
199
194
|
let pendingBreak = false;
|
|
200
|
-
for (const node of nodes) {
|
|
195
|
+
for (const node of nodes ?? []) {
|
|
201
196
|
if (node.kind === "Break") {
|
|
202
|
-
|
|
197
|
+
pendingBreak = result !== "";
|
|
203
198
|
continue;
|
|
204
199
|
}
|
|
205
200
|
const text = printCodeNode(node);
|
|
206
201
|
if (!text) continue;
|
|
207
|
-
if (
|
|
202
|
+
if (result) result += pendingBreak ? "\n\n" : separator;
|
|
208
203
|
result += text;
|
|
209
|
-
hasContent = true;
|
|
210
204
|
pendingBreak = false;
|
|
211
205
|
}
|
|
212
206
|
return result;
|
|
213
207
|
}
|
|
214
208
|
/**
|
|
215
|
-
* Indents every non-empty line of `text` by
|
|
216
|
-
* {@link INDENT_CHAR} that many times, or a string to use as the indent verbatim.
|
|
209
|
+
* Indents every non-empty line of `text` by two spaces.
|
|
217
210
|
*/
|
|
218
|
-
function indentLines(text
|
|
211
|
+
function indentLines(text) {
|
|
219
212
|
if (!text) return "";
|
|
220
|
-
|
|
221
|
-
return text.split("\n").map((line) => line.trim() ? `${pad}${line}` : "").join("\n");
|
|
213
|
+
return text.split("\n").map((line) => line.trim() ? ` ${line}` : "").join("\n");
|
|
222
214
|
}
|
|
223
215
|
/**
|
|
224
|
-
*
|
|
225
|
-
* surrounding blank lines, so multi-line content authored inside an indented template
|
|
226
|
-
* literal lines up at a column-zero baseline. Leading whitespace is counted by
|
|
227
|
-
* character, so N tabs and N spaces are treated as the same depth.
|
|
228
|
-
*
|
|
229
|
-
* @example
|
|
230
|
-
* ```ts
|
|
231
|
-
* dedent('\n foo\n bar\n ')
|
|
232
|
-
* // 'foo\n bar'
|
|
233
|
-
* ```
|
|
216
|
+
* Strips the common leading whitespace and the surrounding blank lines, counting tabs and spaces alike, so indented template literals start at column zero.
|
|
234
217
|
*/
|
|
235
218
|
function dedent(text) {
|
|
236
219
|
if (!text) return "";
|
|
@@ -245,61 +228,37 @@ function dedent(text) {
|
|
|
245
228
|
return trimmed.map((line) => isBlank(line) ? "" : line.slice(min)).join("\n");
|
|
246
229
|
}
|
|
247
230
|
/**
|
|
248
|
-
* Renders
|
|
249
|
-
* Accepts either a raw string (rendered verbatim) or an array of type-parameter names.
|
|
231
|
+
* Renders `<T>(params): ReturnType`; an async function wraps its return type in `Promise<>`.
|
|
250
232
|
*/
|
|
251
|
-
function
|
|
252
|
-
|
|
253
|
-
|
|
233
|
+
function signature({ async: isAsync, generics, params, returnType }) {
|
|
234
|
+
const genericsStr = generics ? `<${Array.isArray(generics) ? generics.join(", ") : generics}>` : "";
|
|
235
|
+
const wrapped = isAsync ? `Promise<${returnType}>` : returnType;
|
|
236
|
+
const returnTypeStr = returnType ? `: ${wrapped}` : "";
|
|
237
|
+
return `${genericsStr}(${params ?? ""})${returnTypeStr}`;
|
|
254
238
|
}
|
|
255
239
|
/**
|
|
256
|
-
*
|
|
257
|
-
* Returns an empty string when no return type is provided.
|
|
240
|
+
* Puts the JSDoc block, when it renders to something, above `declaration`.
|
|
258
241
|
*/
|
|
259
|
-
function
|
|
260
|
-
|
|
261
|
-
return
|
|
242
|
+
function withJSDoc({ jsDoc, declaration }) {
|
|
243
|
+
const jsDocStr = jsDoc ? printJSDoc(jsDoc) : "";
|
|
244
|
+
return jsDocStr ? `${jsDocStr}\n${declaration}` : declaration;
|
|
262
245
|
}
|
|
263
246
|
/**
|
|
264
|
-
* Module-scoped TypeScript printer instance. A printer does not mutate the source file, so one
|
|
265
|
-
* instance is reused across every `print()` call instead of constructing a new printer each time.
|
|
266
|
-
*/
|
|
267
|
-
const TS_PRINTER = typescript.createPrinter({
|
|
268
|
-
omitTrailingSemicolon: true,
|
|
269
|
-
newLine: typescript.NewLineKind.LineFeed,
|
|
270
|
-
removeComments: false,
|
|
271
|
-
noEmitHelpers: true
|
|
272
|
-
});
|
|
273
|
-
/**
|
|
274
|
-
* Module-scoped source file used as the print target. `printList` only reads the source
|
|
275
|
-
* file's compiler options / language version. It never mutates it.
|
|
276
|
-
*/
|
|
277
|
-
const PRINT_SOURCE_FILE = typescript.createSourceFile("print.tsx", "", typescript.ScriptTarget.ES2022, true, typescript.ScriptKind.TSX);
|
|
278
|
-
TS_PRINTER.printList(typescript.ListFormat.MultiLine, factory.createNodeArray([]), PRINT_SOURCE_FILE);
|
|
279
|
-
/**
|
|
280
247
|
* Converts TypeScript/TSX AST nodes to a string using the TypeScript printer.
|
|
281
248
|
*/
|
|
282
249
|
function print(...elements) {
|
|
283
250
|
const filtered = elements.filter(Boolean);
|
|
284
251
|
if (filtered.length === 0) return "";
|
|
285
|
-
|
|
252
|
+
const { typescript, printer, sourceFile } = loadCompiler();
|
|
253
|
+
return printer.printList(typescript.ListFormat.MultiLine, typescript.factory.createNodeArray(filtered), sourceFile).replace(/\r\n/g, "\n");
|
|
286
254
|
}
|
|
287
255
|
/**
|
|
288
|
-
* Converts a {@link ast.JSDocNode} to a
|
|
289
|
-
*
|
|
290
|
-
* @example
|
|
291
|
-
* ```ts
|
|
292
|
-
* printJSDoc({ comments: ['@description A pet', '@deprecated'] })
|
|
293
|
-
* // /**
|
|
294
|
-
* // * @description A pet
|
|
295
|
-
* // * @deprecated
|
|
296
|
-
* // *\/
|
|
297
|
-
* ```
|
|
256
|
+
* Converts a {@link ast.JSDocNode} to a `/** … *\/` block, dropping blank comments.
|
|
298
257
|
*/
|
|
299
258
|
function printJSDoc(jsDoc) {
|
|
300
259
|
const comments = (jsDoc.comments ?? []).filter((c) => c != null);
|
|
301
260
|
if (comments.length === 0) return "";
|
|
302
|
-
const lines = comments.flatMap((c) => c.split(/\r?\n/)).map((l) => l.replace(
|
|
261
|
+
const lines = comments.flatMap((c) => c.split(/\r?\n/)).map((l) => l.replace(/\*\//g, "* /").replace(/\r/g, "")).filter((l) => l.trim().length > 0);
|
|
303
262
|
if (lines.length === 0) return "";
|
|
304
263
|
return [
|
|
305
264
|
"/**",
|
|
@@ -308,194 +267,101 @@ function printJSDoc(jsDoc) {
|
|
|
308
267
|
].join("\n");
|
|
309
268
|
}
|
|
310
269
|
/**
|
|
311
|
-
* Converts a {@link ast.ConstNode} to a
|
|
312
|
-
*
|
|
313
|
-
* Mirrors the `Const` component from `@kubb/renderer-jsx`.
|
|
314
|
-
*
|
|
315
|
-
* @example
|
|
316
|
-
* ```ts
|
|
317
|
-
* printConst(factory.createConst({ name: 'pet', export: true, nodes: ['{}'] }))
|
|
318
|
-
* // 'export const pet = {}'
|
|
319
|
-
* ```
|
|
320
|
-
*
|
|
321
|
-
* @example With type and `as const`
|
|
322
|
-
* ```ts
|
|
323
|
-
* printConst(factory.createConst({ name: 'pets', export: true, type: 'Pet[]', asConst: true, nodes: ['[]'] }))
|
|
324
|
-
* // 'export const pets: Pet[] = [] as const'
|
|
325
|
-
* ```
|
|
270
|
+
* Converts a {@link ast.ConstNode} to a `const` declaration, mirroring the `Const` component from `@kubb/renderer-jsx`.
|
|
326
271
|
*/
|
|
327
272
|
function printConst(node) {
|
|
328
273
|
const { name, export: canExport, type, JSDoc, asConst, nodes } = node;
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
parts.push(" = ");
|
|
337
|
-
parts.push(body);
|
|
338
|
-
if (asConst) parts.push(" as const");
|
|
339
|
-
return [jsDocStr, parts.join("")].filter(Boolean).join("\n");
|
|
274
|
+
return withJSDoc({
|
|
275
|
+
jsDoc: JSDoc,
|
|
276
|
+
declaration: `${canExport ? "export " : ""}const ${name}${type ? `: ${type}` : ""} = ${printNodes({
|
|
277
|
+
nodes,
|
|
278
|
+
separator: "\n"
|
|
279
|
+
})}${asConst ? " as const" : ""}`
|
|
280
|
+
});
|
|
340
281
|
}
|
|
341
282
|
/**
|
|
342
|
-
* Converts a {@link ast.TypeNode} to a
|
|
343
|
-
*
|
|
344
|
-
* Mirrors the `Type` component from `@kubb/renderer-jsx`.
|
|
345
|
-
*
|
|
346
|
-
* @example
|
|
347
|
-
* ```ts
|
|
348
|
-
* printType(factory.createType({ name: 'Pet', export: true, nodes: ['{ id: number }'] }))
|
|
349
|
-
* // 'export type Pet = { id: number }'
|
|
350
|
-
* ```
|
|
283
|
+
* Converts a {@link ast.TypeNode} to a `type` alias, mirroring the `Type` component from `@kubb/renderer-jsx`.
|
|
351
284
|
*/
|
|
352
285
|
function printType(node) {
|
|
353
286
|
const { name, export: canExport, JSDoc, nodes } = node;
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
parts.push(body);
|
|
362
|
-
return [jsDocStr, parts.join("")].filter(Boolean).join("\n");
|
|
287
|
+
return withJSDoc({
|
|
288
|
+
jsDoc: JSDoc,
|
|
289
|
+
declaration: `${canExport ? "export " : ""}type ${name} = ${printNodes({
|
|
290
|
+
nodes,
|
|
291
|
+
separator: "\n"
|
|
292
|
+
})}`
|
|
293
|
+
});
|
|
363
294
|
}
|
|
364
295
|
/**
|
|
365
|
-
* Converts a {@link ast.FunctionNode} to a
|
|
366
|
-
*
|
|
367
|
-
* Mirrors the `Function` component from `@kubb/renderer-jsx`.
|
|
368
|
-
*
|
|
369
|
-
* @example
|
|
370
|
-
* ```ts
|
|
371
|
-
* printFunction(factory.createFunction({ name: 'getPet', export: true, params: 'id: string', returnType: 'Pet', nodes: ['return fetch(id)'] }))
|
|
372
|
-
* // 'export function getPet(id: string): Pet {\n return fetch(id)\n}'
|
|
373
|
-
* ```
|
|
374
|
-
*
|
|
375
|
-
* @example Async with generics
|
|
376
|
-
* ```ts
|
|
377
|
-
* printFunction(factory.createFunction({ name: 'fetchPet', export: true, async: true, generics: ['T'], params: 'id: string', returnType: 'T' }))
|
|
378
|
-
* // 'export async function fetchPet<T>(id: string): Promise<T> {\n}'
|
|
379
|
-
* ```
|
|
296
|
+
* Converts a {@link ast.FunctionNode} to a `function` declaration, mirroring the `Function` component from `@kubb/renderer-jsx`.
|
|
380
297
|
*/
|
|
381
298
|
function printFunction(node) {
|
|
382
299
|
const { name, default: isDefault, export: canExport, async: isAsync, generics, params, returnType, JSDoc, nodes } = node;
|
|
383
|
-
const
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
if (indented) parts.push(`\n${indented}\n`);
|
|
397
|
-
parts.push("}");
|
|
398
|
-
return [jsDocStr, parts.join("")].filter(Boolean).join("\n");
|
|
300
|
+
const body = indentLines(printNodes({
|
|
301
|
+
nodes,
|
|
302
|
+
separator: "\n"
|
|
303
|
+
}));
|
|
304
|
+
return withJSDoc({
|
|
305
|
+
jsDoc: JSDoc,
|
|
306
|
+
declaration: `${`${canExport ? "export " : ""}${isDefault ? "default " : ""}${isAsync ? "async " : ""}`}function ${name}${signature({
|
|
307
|
+
async: isAsync,
|
|
308
|
+
generics,
|
|
309
|
+
params,
|
|
310
|
+
returnType
|
|
311
|
+
})} {${body ? `\n${body}\n` : ""}}`
|
|
312
|
+
});
|
|
399
313
|
}
|
|
400
314
|
/**
|
|
401
|
-
* Converts an {@link ast.ArrowFunctionNode} to a
|
|
402
|
-
*
|
|
403
|
-
* Mirrors the `Function.Arrow` component from `@kubb/renderer-jsx`.
|
|
404
|
-
*
|
|
405
|
-
* @example Multi-line arrow function
|
|
406
|
-
* ```ts
|
|
407
|
-
* printArrowFunction(factory.createArrowFunction({ name: 'getPet', export: true, params: 'id: string', nodes: ['return fetch(id)'] }))
|
|
408
|
-
* // 'export const getPet = (id: string) => {\n return fetch(id)\n}'
|
|
409
|
-
* ```
|
|
410
|
-
*
|
|
411
|
-
* @example Single-line arrow function
|
|
412
|
-
* ```ts
|
|
413
|
-
* printArrowFunction(factory.createArrowFunction({ name: 'double', params: 'n: number', singleLine: true, nodes: ['n * 2'] }))
|
|
414
|
-
* // 'const double = (n: number) => n * 2'
|
|
415
|
-
* ```
|
|
315
|
+
* Converts an {@link ast.ArrowFunctionNode} to a `const` arrow function, mirroring the `Function.Arrow` component from `@kubb/renderer-jsx`.
|
|
416
316
|
*/
|
|
417
317
|
function printArrowFunction(node) {
|
|
418
318
|
const { name, default: isDefault, export: canExport, async: isAsync, generics, params, returnType, JSDoc, nodes, singleLine } = node;
|
|
419
|
-
const
|
|
420
|
-
|
|
319
|
+
const body = printNodes({
|
|
320
|
+
nodes,
|
|
321
|
+
separator: "\n"
|
|
322
|
+
});
|
|
421
323
|
const arrowBody = singleLine ? ` => ${body}` : body ? ` => {\n${indentLines(body)}\n}` : " => {}";
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
parts.push(formatReturnType(returnType, isAsync));
|
|
432
|
-
parts.push(arrowBody);
|
|
433
|
-
return [jsDocStr, parts.join("")].filter(Boolean).join("\n");
|
|
324
|
+
return withJSDoc({
|
|
325
|
+
jsDoc: JSDoc,
|
|
326
|
+
declaration: `${`${canExport ? "export " : ""}${isDefault ? "default " : ""}`}const ${name} = ${isAsync ? "async " : ""}${signature({
|
|
327
|
+
async: isAsync,
|
|
328
|
+
generics,
|
|
329
|
+
params,
|
|
330
|
+
returnType
|
|
331
|
+
})}${arrowBody}`
|
|
332
|
+
});
|
|
434
333
|
}
|
|
435
334
|
/**
|
|
436
|
-
*
|
|
437
|
-
*
|
|
438
|
-
* Dispatches to the appropriate printer based on the node's `kind`.
|
|
439
|
-
*
|
|
440
|
-
* @example
|
|
441
|
-
* ```ts
|
|
442
|
-
* printCodeNode(factory.createConst({ name: 'x', nodes: ['1'] }))
|
|
443
|
-
* // 'const x = 1'
|
|
444
|
-
* ```
|
|
335
|
+
* Dispatches a {@link ast.CodeNode} to the printer for its `kind`.
|
|
445
336
|
*/
|
|
446
337
|
function printCodeNode(node) {
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
338
|
+
switch (node.kind) {
|
|
339
|
+
case "Break": return "";
|
|
340
|
+
case "Text":
|
|
341
|
+
case "Jsx": return dedent(node.value);
|
|
342
|
+
case "Const": return printConst(node);
|
|
343
|
+
case "Type": return printType(node);
|
|
344
|
+
case "Function": return printFunction(node);
|
|
345
|
+
case "ArrowFunction": return printArrowFunction(node);
|
|
346
|
+
}
|
|
455
347
|
}
|
|
456
348
|
/**
|
|
457
|
-
*
|
|
458
|
-
*
|
|
459
|
-
* Iterates `nodes` in DOM order, rendering each {@link ast.CodeNode} via
|
|
460
|
-
* {@link printCodeNode}.
|
|
461
|
-
*
|
|
462
|
-
* Top-level declarations are separated by a blank line so the source reads
|
|
463
|
-
* cleanly without an external formatter.
|
|
464
|
-
*
|
|
465
|
-
* @example From nodes
|
|
466
|
-
* ```ts
|
|
467
|
-
* printSource({ kind: 'Source', nodes: [factory.createConst({ name: 'x', nodes: [factory.createText('1')] }), factory.createText('x.toString()')] })
|
|
468
|
-
* // 'const x = 1\n\nx.toString()'
|
|
469
|
-
* ```
|
|
349
|
+
* Prints a {@link ast.SourceNode} with a blank line between its top-level nodes.
|
|
470
350
|
*/
|
|
471
351
|
function printSource(node) {
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
const text = printCodeNode(child);
|
|
477
|
-
if (!text) continue;
|
|
478
|
-
result = result ? `${result}\n\n${text}` : text;
|
|
479
|
-
}
|
|
480
|
-
return result;
|
|
352
|
+
return printNodes({
|
|
353
|
+
nodes: node.nodes,
|
|
354
|
+
separator: "\n\n"
|
|
355
|
+
});
|
|
481
356
|
}
|
|
482
357
|
/**
|
|
483
|
-
* Wraps a module specifier in single quotes, escaping
|
|
484
|
-
* statement stays valid even for unusual paths.
|
|
358
|
+
* Wraps a module specifier in single quotes, escaping embedded backslashes and quotes.
|
|
485
359
|
*/
|
|
486
360
|
function quoteModulePath(path) {
|
|
487
361
|
return `'${path.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
488
362
|
}
|
|
489
363
|
/**
|
|
490
|
-
* Renders an import declaration
|
|
491
|
-
* default, namespace (`* as`), and named imports with `{ a as b }` aliases, each optionally
|
|
492
|
-
* `type`-only. `path` is used verbatim, so resolve it first.
|
|
493
|
-
*
|
|
494
|
-
* @example
|
|
495
|
-
* ```ts
|
|
496
|
-
* printImport({ name: ['z'], path: './zod.ts' })
|
|
497
|
-
* // "import { z } from './zod.ts'"
|
|
498
|
-
* ```
|
|
364
|
+
* Renders an `import` declaration (default, `* as`, or named with `{ a as b }`, each optionally `type`-only). `path` is used verbatim, so resolve it first.
|
|
499
365
|
*/
|
|
500
366
|
function printImport({ name, path, isTypeOnly = false, isNameSpace = false }) {
|
|
501
367
|
const typePrefix = isTypeOnly ? "type " : "";
|
|
@@ -510,21 +376,13 @@ function printImport({ name, path, isTypeOnly = false, isNameSpace = false }) {
|
|
|
510
376
|
}).join(", ")} } from ${from}`;
|
|
511
377
|
}
|
|
512
378
|
/**
|
|
513
|
-
* Renders an export declaration
|
|
514
|
-
* named re-exports, namespace alias (`* as name`), and wildcard, each optionally `type`-only.
|
|
515
|
-
* `path` is used verbatim, so resolve it first.
|
|
516
|
-
*
|
|
517
|
-
* @example
|
|
518
|
-
* ```ts
|
|
519
|
-
* printExport({ name: ['Pet', 'Order'], path: './models.ts' })
|
|
520
|
-
* // "export { Pet, Order } from './models.ts'"
|
|
521
|
-
* ```
|
|
379
|
+
* Renders an `export … from` declaration (named, `* as name`, or wildcard, each optionally `type`-only). `path` is used verbatim, so resolve it first.
|
|
522
380
|
*/
|
|
523
381
|
function printExport({ path, name, isTypeOnly = false, asAlias = false }) {
|
|
524
382
|
const typePrefix = isTypeOnly ? "type " : "";
|
|
525
383
|
const from = quoteModulePath(path);
|
|
526
|
-
if (Array.isArray(name)) return `export ${typePrefix}{ ${name.
|
|
527
|
-
if (asAlias && name) return `export ${typePrefix}* as ${
|
|
384
|
+
if (Array.isArray(name)) return `export ${typePrefix}{ ${name.join(", ")} } from ${from}`;
|
|
385
|
+
if (asAlias && name) return `export ${typePrefix}* as ${/^\d/.test(name) ? `_${name.slice(1)}` : name} from ${from}`;
|
|
528
386
|
if (name) return `export ${typePrefix}{ ${name} } from ${from}`;
|
|
529
387
|
return `export ${typePrefix}* from ${from}`;
|
|
530
388
|
}
|
|
@@ -559,33 +417,17 @@ const parserTs = defineParser(({ extension = DEFAULT_EXTENSION } = {}) => {
|
|
|
559
417
|
return {
|
|
560
418
|
name: "typescript",
|
|
561
419
|
extNames: [".ts", ".js"],
|
|
562
|
-
print
|
|
563
|
-
return print(...nodes);
|
|
564
|
-
},
|
|
420
|
+
print,
|
|
565
421
|
parse(file) {
|
|
566
422
|
const extname = extension[file.extname] || void 0;
|
|
567
|
-
const
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
}
|
|
572
|
-
const
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
const importPath = item.root ? getRelativePath(item.root, item.path) : item.path;
|
|
576
|
-
importLines.push(printImport({
|
|
577
|
-
name: item.name,
|
|
578
|
-
path: resolveOutputPath(importPath, { extname }, Boolean(item.root)),
|
|
579
|
-
isTypeOnly: item.isTypeOnly,
|
|
580
|
-
isNameSpace: item.isNameSpace
|
|
581
|
-
}));
|
|
582
|
-
}
|
|
583
|
-
const exportLines = [];
|
|
584
|
-
for (const item of file.exports) exportLines.push(printExport({
|
|
585
|
-
name: item.name,
|
|
586
|
-
path: resolveOutputPath(item.path, { extname }, item.path.startsWith(".")),
|
|
587
|
-
isTypeOnly: item.isTypeOnly,
|
|
588
|
-
asAlias: item.asAlias
|
|
423
|
+
const source = file.sources.map((item) => printSource(item)).filter(Boolean).map((text) => text.trimEnd()).join("\n\n");
|
|
424
|
+
const importLines = file.imports.map((item) => printImport({
|
|
425
|
+
...item,
|
|
426
|
+
path: resolveOutputPath(item.root ? getRelativePath(item.root, item.path) : item.path, { extname }, Boolean(item.root))
|
|
427
|
+
}));
|
|
428
|
+
const exportLines = file.exports.map((item) => printExport({
|
|
429
|
+
...item,
|
|
430
|
+
path: resolveOutputPath(item.path, { extname }, item.path.startsWith("."))
|
|
589
431
|
}));
|
|
590
432
|
const importExportBlock = [...importLines, ...exportLines].join("\n");
|
|
591
433
|
return [
|
|
@@ -632,20 +474,11 @@ const parserTs = defineParser(({ extension = DEFAULT_EXTENSION } = {}) => {
|
|
|
632
474
|
* })
|
|
633
475
|
* ```
|
|
634
476
|
*/
|
|
635
|
-
const parserTsx = defineParser((options
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
print(...nodes) {
|
|
641
|
-
return print(...nodes);
|
|
642
|
-
},
|
|
643
|
-
parse(file) {
|
|
644
|
-
return parser.parse(file);
|
|
645
|
-
},
|
|
646
|
-
copy: parser.copy
|
|
647
|
-
};
|
|
648
|
-
});
|
|
477
|
+
const parserTsx = defineParser((options) => ({
|
|
478
|
+
...parserTs(options),
|
|
479
|
+
name: "tsx",
|
|
480
|
+
extNames: [".tsx", ".jsx"]
|
|
481
|
+
}));
|
|
649
482
|
//#endregion
|
|
650
483
|
export { parserTs, parserTsx };
|
|
651
484
|
|