@kubb/parser-ts
Advanced tools
| //#region \0rolldown/runtime.js | ||
| var __defProp = Object.defineProperty; | ||
| var __name = (target, value) => __defProp(target, "name", { | ||
| value, | ||
| configurable: true | ||
| }); | ||
| //#endregion | ||
| export { __name as t }; |
+1
-1
@@ -1,2 +0,2 @@ | ||
| import { t as __name } from "./chunk-C0LytTxp.js"; | ||
| import { t as __name } from "./rolldown-runtime-C0LytTxp.js"; | ||
| import * as ts$1 from "typescript"; | ||
@@ -3,0 +3,0 @@ import { FileNode } from "@kubb/ast"; |
+1
-1
@@ -1,2 +0,2 @@ | ||
| import "./chunk-C0LytTxp.js"; | ||
| import "./rolldown-runtime-C0LytTxp.js"; | ||
| import { defineParser } from "@kubb/core"; | ||
@@ -3,0 +3,0 @@ import { normalize, relative } from "node:path"; |
+3
-4
| { | ||
| "name": "@kubb/parser-ts", | ||
| "version": "5.0.0-beta.63", | ||
| "version": "5.0.0-beta.64", | ||
| "description": "TypeScript and TSX source file parser for Kubb. Converts AST nodes and raw TypeScript code into formatted source strings using the TypeScript compiler API.", | ||
@@ -20,3 +20,2 @@ "keywords": [ | ||
| "files": [ | ||
| "src", | ||
| "dist", | ||
@@ -45,7 +44,7 @@ "!/**/**.test.**", | ||
| "typescript": "^6.0.3", | ||
| "@kubb/core": "5.0.0-beta.63" | ||
| "@kubb/core": "5.0.0-beta.64" | ||
| }, | ||
| "devDependencies": { | ||
| "@internals/utils": "0.0.0", | ||
| "@kubb/ast": "5.0.0-beta.63" | ||
| "@kubb/ast": "5.0.0-beta.64" | ||
| }, | ||
@@ -52,0 +51,0 @@ "engines": { |
| //#region \0rolldown/runtime.js | ||
| var __defProp = Object.defineProperty; | ||
| var __name = (target, value) => __defProp(target, "name", { | ||
| value, | ||
| configurable: true | ||
| }); | ||
| //#endregion | ||
| export { __name as t }; |
| /** | ||
| * Character used for a single indent step. Set to `'\t'` to emit tab-indented output. | ||
| */ | ||
| export const INDENT_CHAR = ' ' | ||
| /** | ||
| * Number of {@link INDENT_CHAR} repeats that make up one nesting level. | ||
| */ | ||
| const INDENT_SIZE = 2 as const | ||
| /** | ||
| * Indentation unit prepended once per nesting level when pretty-printing. | ||
| */ | ||
| export const INDENT = INDENT_CHAR.repeat(INDENT_SIZE) | ||
| /** | ||
| * Matches only the final `.<ext>` of a path, so a name like `foo.bar.ts` keeps | ||
| * `foo.bar` and loses just `.ts`. | ||
| */ | ||
| export const FILE_EXTENSION_PATTERN = /\.[^/.]+$/ | ||
| /** | ||
| * Matches Windows-style backslash path separators. | ||
| */ | ||
| export const WINDOWS_PATH_SEPARATOR = /\\/g | ||
| /** | ||
| * Matches `*\/` in free-form text so JSDoc bodies can neutralize premature | ||
| * comment terminators (`*\/` → `* /`). | ||
| */ | ||
| export const JSDOC_TERMINATOR_PATTERN = /\*\//g | ||
| /** | ||
| * Matches carriage returns for normalizing CRLF/CR line endings to LF. | ||
| */ | ||
| export const CARRIAGE_RETURN_PATTERN = /\r/g | ||
| /** | ||
| * Matches CRLF sequences used when normalizing TypeScript printer output. | ||
| */ | ||
| export const CRLF_PATTERN = /\r\n/g | ||
| /** | ||
| * Matches an identifier that starts with a digit. JavaScript disallows this, | ||
| * so the printer replaces the leading digit with `_`. | ||
| */ | ||
| export const LEADING_DIGIT_PATTERN = /^\d/ | ||
| /** | ||
| * Relative path prefix used to detect traversal segments (`../`). | ||
| */ | ||
| export const PARENT_DIRECTORY_PREFIX = '../' as const | ||
| /** | ||
| * Relative path prefix used when resolving imports within the output root. | ||
| */ | ||
| export const CURRENT_DIRECTORY_PREFIX = './' as const |
| export { parserTs } from './parserTs.ts' | ||
| export { parserTsx } from './parserTsx.ts' |
| import type { FileNode, SourceNode } from '@kubb/ast' | ||
| import { defineParser } from '@kubb/core' | ||
| import type * as ts from 'typescript' | ||
| import { getRelativePath, print, printExport, printImport, printSource, resolveOutputPath } from './utils.ts' | ||
| /** | ||
| * Default Kubb parser for `.ts` and `.js` files. Takes the universal AST | ||
| * produced by an adapter and prints it as TypeScript source using the official | ||
| * TypeScript compiler. Imports and exports are rewritten based on each file's | ||
| * metadata. | ||
| * | ||
| * Used automatically when no `parsers` option is set on `defineConfig`. Use | ||
| * `parserTsx` instead for React projects that emit JSX. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * import { defineConfig } from 'kubb' | ||
| * import { adapterOas } from '@kubb/adapter-oas' | ||
| * import { parserTs } from '@kubb/parser-ts' | ||
| * | ||
| * export default defineConfig({ | ||
| * input: { path: './petStore.yaml' }, | ||
| * output: { path: './src/gen' }, | ||
| * adapter: adapterOas(), | ||
| * parsers: [parserTs], | ||
| * plugins: [], | ||
| * }) | ||
| * ``` | ||
| */ | ||
| export const parserTs = defineParser({ | ||
| name: 'typescript', | ||
| extNames: ['.ts', '.js'], | ||
| print(...nodes: Array<ts.Node>) { | ||
| return print(...nodes) | ||
| }, | ||
| parse(file, options = { extname: '.ts' }) { | ||
| const sourceParts: Array<string> = [] | ||
| for (const item of file.sources) { | ||
| const sourceStr = printSource(item as SourceNode) | ||
| if (sourceStr) { | ||
| sourceParts.push(sourceStr.trimEnd()) | ||
| } | ||
| } | ||
| const source = sourceParts.join('\n\n') | ||
| const importLines: Array<string> = [] | ||
| for (const item of (file as FileNode).imports) { | ||
| const importPath = item.root ? getRelativePath(item.root, item.path) : item.path | ||
| importLines.push( | ||
| printImport({ | ||
| name: item.name as string | Array<string | { propertyName: string; name?: string }>, | ||
| path: resolveOutputPath(importPath, options, Boolean(item.root)), | ||
| isTypeOnly: item.isTypeOnly, | ||
| isNameSpace: item.isNameSpace, | ||
| }), | ||
| ) | ||
| } | ||
| const exportLines: Array<string> = [] | ||
| for (const item of (file as FileNode).exports) { | ||
| exportLines.push( | ||
| printExport({ | ||
| name: item.name as string | Array<ts.Identifier | string> | null | undefined, | ||
| path: resolveOutputPath(item.path, options, true), | ||
| isTypeOnly: item.isTypeOnly, | ||
| asAlias: item.asAlias, | ||
| }), | ||
| ) | ||
| } | ||
| const importExportBlock = [...importLines, ...exportLines].join('\n') | ||
| const parts = [file.banner, importExportBlock, source, file.footer].filter((segment): segment is string => Boolean(segment)).map((s) => s.trimEnd()) | ||
| return parts.join('\n\n') | ||
| }, | ||
| }) |
| import { defineParser } from '@kubb/core' | ||
| import type * as ts from 'typescript' | ||
| import { parserTs } from './parserTs.ts' | ||
| import { print } from './utils.ts' | ||
| /** | ||
| * Kubb parser for `.tsx` and `.jsx` files. Delegates to `parserTs` because the | ||
| * TypeScript compiler handles JSX natively via `ScriptKind.TSX`. | ||
| * | ||
| * Add to the `parsers` array on `defineConfig` when generating components for | ||
| * React (or any framework that emits JSX). | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * import { defineConfig } from 'kubb' | ||
| * import { adapterOas } from '@kubb/adapter-oas' | ||
| * import { parserTsx } from '@kubb/parser-ts' | ||
| * | ||
| * export default defineConfig({ | ||
| * input: { path: './petStore.yaml' }, | ||
| * output: { path: './src/gen' }, | ||
| * adapter: adapterOas(), | ||
| * parsers: [parserTsx], | ||
| * plugins: [], | ||
| * }) | ||
| * ``` | ||
| */ | ||
| export const parserTsx = defineParser({ | ||
| name: 'tsx', | ||
| extNames: ['.tsx', '.jsx'], | ||
| print(...nodes: Array<ts.Node>) { | ||
| return print(...nodes) | ||
| }, | ||
| parse(file, options = { extname: '.tsx' }) { | ||
| return parserTs.parse(file, options) | ||
| }, | ||
| }) |
-486
| import { normalize, relative } from 'node:path' | ||
| import { trimExtName } from '@internals/utils' | ||
| import type { ArrowFunctionNode, CodeNode, ConstNode, FunctionNode, JSDocNode, JsxNode, SourceNode, TextNode, TypeNode } from '@kubb/ast' | ||
| import ts from 'typescript' | ||
| import { | ||
| CARRIAGE_RETURN_PATTERN, | ||
| CRLF_PATTERN, | ||
| CURRENT_DIRECTORY_PREFIX, | ||
| FILE_EXTENSION_PATTERN, | ||
| INDENT, | ||
| INDENT_CHAR, | ||
| JSDOC_TERMINATOR_PATTERN, | ||
| LEADING_DIGIT_PATTERN, | ||
| PARENT_DIRECTORY_PREFIX, | ||
| WINDOWS_PATH_SEPARATOR, | ||
| } from './constants.ts' | ||
| const { factory } = ts | ||
| /** | ||
| * Normalizes a file-system path to POSIX separators and strips any leading `../` segment. | ||
| */ | ||
| export function slash(path: string): string { | ||
| return normalize(path).replaceAll(WINDOWS_PATH_SEPARATOR, '/').replace(PARENT_DIRECTORY_PREFIX, '') | ||
| } | ||
| /** | ||
| * Resolves `filePath` relative to `rootDir` and returns a POSIX-style path | ||
| * prefixed with `./` when the target sits inside the root, or `../` when it escapes it. | ||
| */ | ||
| export function getRelativePath(rootDir: string, filePath: string): string { | ||
| const rel = relative(rootDir, filePath) | ||
| const slashed = slash(rel) | ||
| return slashed.startsWith(PARENT_DIRECTORY_PREFIX) ? slashed : `${CURRENT_DIRECTORY_PREFIX}${slashed}` | ||
| } | ||
| /** | ||
| * Rewrites an import/export path so its extension matches the caller-supplied | ||
| * `options.extname`. When the source path has no extension the original is kept, | ||
| * so virtual/module-only paths flow through unchanged. | ||
| */ | ||
| export function resolveOutputPath(path: string, options: { extname?: string } | undefined, rootAware: boolean): string { | ||
| const hasExtname = FILE_EXTENSION_PATTERN.test(path) | ||
| if (options?.extname && hasExtname) { | ||
| return `${trimExtName(path)}${options.extname}` | ||
| } | ||
| return rootAware ? trimExtName(path) : path | ||
| } | ||
| /** | ||
| * Serializes a `nodes` array into source text. Each entry is rendered via {@link printCodeNode} | ||
| * and joined with a single newline. A `Break` node (`<br/>`) inserts one blank line between | ||
| * statements. Consecutive breaks, and breaks at the very start or end, are folded into the | ||
| * separator, so a double `<br/>` never emits more than one blank line. | ||
| */ | ||
| export function printNodes(nodes: Array<CodeNode> | undefined): string { | ||
| if (!nodes || nodes.length === 0) return '' | ||
| let result = '' | ||
| let hasContent = false | ||
| let pendingBreak = false | ||
| for (const node of nodes) { | ||
| if (node.kind === 'Break') { | ||
| if (hasContent) pendingBreak = true | ||
| continue | ||
| } | ||
| const text = printCodeNode(node) | ||
| if (!text) continue | ||
| if (hasContent) result += pendingBreak ? '\n\n' : '\n' | ||
| result += text | ||
| hasContent = true | ||
| pendingBreak = false | ||
| } | ||
| return result | ||
| } | ||
| /** | ||
| * Indents every non-empty line of `text` by one indent unit. Pass a number to repeat | ||
| * {@link INDENT_CHAR} that many times, or a string to use as the indent verbatim. | ||
| */ | ||
| export function indentLines(text: string, indent: number | string = INDENT): string { | ||
| if (!text) return '' | ||
| const pad = typeof indent === 'string' ? indent : INDENT_CHAR.repeat(indent) | ||
| return text | ||
| .split('\n') | ||
| .map((line) => (line.trim() ? `${pad}${line}` : '')) | ||
| .join('\n') | ||
| } | ||
| /** | ||
| * Removes the common leading whitespace shared by every non-blank line and trims | ||
| * surrounding blank lines, so multi-line content authored inside an indented template | ||
| * literal lines up at a column-zero baseline. Leading whitespace is counted by | ||
| * character, so N tabs and N spaces are treated as the same depth. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * dedent('\n foo\n bar\n ') | ||
| * // 'foo\n bar' | ||
| * ``` | ||
| */ | ||
| export function dedent(text: string): string { | ||
| if (!text) return '' | ||
| const lines = text.split('\n') | ||
| const isBlank = (line: string) => line.trim() === '' | ||
| const start = lines.findIndex((line) => !isBlank(line)) | ||
| if (start === -1) return '' | ||
| const end = lines.findLastIndex((line) => !isBlank(line)) | ||
| const trimmed = lines.slice(start, end + 1) | ||
| const indents = trimmed.filter((line) => !isBlank(line)).map((line) => line.match(/^\s*/)?.[0].length ?? 0) | ||
| const min = indents.length ? Math.min(...indents) : 0 | ||
| return trimmed.map((line) => (isBlank(line) ? '' : line.slice(min))).join('\n') | ||
| } | ||
| /** | ||
| * Renders the generic clause (`<T, U>`) shared by function and arrow-function nodes. | ||
| * Accepts either a raw string (rendered verbatim) or an array of type-parameter names. | ||
| */ | ||
| export function formatGenerics(generics: FunctionNode['generics'] | ArrowFunctionNode['generics']): string { | ||
| if (!generics) return '' | ||
| return `<${Array.isArray(generics) ? generics.join(', ') : generics}>` | ||
| } | ||
| /** | ||
| * Renders the return-type suffix (`: T` or `: Promise<T>` when `isAsync` is true). | ||
| * Returns an empty string when no return type is provided. | ||
| */ | ||
| export function formatReturnType(returnType: string | null | undefined, isAsync: boolean | null | undefined): string { | ||
| if (!returnType) return '' | ||
| return isAsync ? `: Promise<${returnType}>` : `: ${returnType}` | ||
| } | ||
| /** | ||
| * Module-scoped TypeScript printer instance. A printer does not mutate the source file, so one | ||
| * instance is reused across every `print()` call instead of constructing a new printer each time. | ||
| */ | ||
| const TS_PRINTER = ts.createPrinter({ | ||
| omitTrailingSemicolon: true, | ||
| newLine: ts.NewLineKind.LineFeed, | ||
| removeComments: false, | ||
| noEmitHelpers: true, | ||
| }) | ||
| /** | ||
| * Module-scoped source file used as the print target. `printList` only reads the source | ||
| * file's compiler options / language version. It never mutates it. | ||
| */ | ||
| const PRINT_SOURCE_FILE = ts.createSourceFile('print.tsx', '', ts.ScriptTarget.ES2022, true, ts.ScriptKind.TSX) | ||
| // Pre-warm the printer at module load. The first `printList` call lazily initializes | ||
| // the printer's internal string-builder and identifier tables. Doing it once at import | ||
| // time keeps that cost off the critical path for short-lived CLI builds. | ||
| TS_PRINTER.printList(ts.ListFormat.MultiLine, factory.createNodeArray([]), PRINT_SOURCE_FILE) | ||
| /** | ||
| * Converts TypeScript/TSX AST nodes to a string using the TypeScript printer. | ||
| */ | ||
| export function print(...elements: Array<ts.Node>): string { | ||
| const filtered = elements.filter(Boolean) | ||
| if (filtered.length === 0) return '' | ||
| const output = TS_PRINTER.printList(ts.ListFormat.MultiLine, factory.createNodeArray(filtered), PRINT_SOURCE_FILE) | ||
| return output.replace(CRLF_PATTERN, '\n') | ||
| } | ||
| /** | ||
| * Converts a {@link JSDocNode} to a JSDoc comment block string. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * printJSDoc({ comments: ['@description A pet', '@deprecated'] }) | ||
| * // /** | ||
| * // * @description A pet | ||
| * // * @deprecated | ||
| * // *\/ | ||
| * ``` | ||
| */ | ||
| export function printJSDoc(jsDoc: JSDocNode): string { | ||
| const comments = (jsDoc.comments ?? []).filter((c) => c != null) | ||
| if (comments.length === 0) return '' | ||
| const lines = comments | ||
| .flatMap((c) => c.split(/\r?\n/)) | ||
| .map((l) => l.replace(JSDOC_TERMINATOR_PATTERN, '* /').replace(CARRIAGE_RETURN_PATTERN, '')) | ||
| .filter((l) => l.trim().length > 0) | ||
| if (lines.length === 0) return '' | ||
| return ['/**', ...lines.map((l) => ` * ${l}`), ' */'].join('\n') | ||
| } | ||
| /** | ||
| * Converts a {@link ConstNode} to a TypeScript `const` declaration string. | ||
| * | ||
| * Mirrors the `Const` component from `@kubb/renderer-jsx`. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * printConst(factory.createConst({ name: 'pet', export: true, nodes: ['{}'] })) | ||
| * // 'export const pet = {}' | ||
| * ``` | ||
| * | ||
| * @example With type and `as const` | ||
| * ```ts | ||
| * printConst(factory.createConst({ name: 'pets', export: true, type: 'Pet[]', asConst: true, nodes: ['[]'] })) | ||
| * // 'export const pets: Pet[] = [] as const' | ||
| * ``` | ||
| */ | ||
| export function printConst(node: ConstNode): string { | ||
| const { name, export: canExport, type, JSDoc, asConst, nodes } = node | ||
| const jsDocStr = JSDoc ? printJSDoc(JSDoc) : '' | ||
| const body = printNodes(nodes) | ||
| const parts: Array<string> = [] | ||
| if (canExport) parts.push('export ') | ||
| parts.push('const ') | ||
| parts.push(name) | ||
| if (type) { | ||
| parts.push(`: ${type}`) | ||
| } | ||
| parts.push(' = ') | ||
| parts.push(body) | ||
| if (asConst) parts.push(' as const') | ||
| const declaration = parts.join('') | ||
| return [jsDocStr, declaration].filter(Boolean).join('\n') | ||
| } | ||
| /** | ||
| * Converts a {@link TypeNode} to a TypeScript `type` alias declaration string. | ||
| * | ||
| * Mirrors the `Type` component from `@kubb/renderer-jsx`. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * printType(factory.createType({ name: 'Pet', export: true, nodes: ['{ id: number }'] })) | ||
| * // 'export type Pet = { id: number }' | ||
| * ``` | ||
| */ | ||
| export function printType(node: TypeNode): string { | ||
| const { name, export: canExport, JSDoc, nodes } = node | ||
| const jsDocStr = JSDoc ? printJSDoc(JSDoc) : '' | ||
| const body = printNodes(nodes) | ||
| const parts: Array<string> = [] | ||
| if (canExport) parts.push('export ') | ||
| parts.push('type ') | ||
| parts.push(name) | ||
| parts.push(' = ') | ||
| parts.push(body) | ||
| const declaration = parts.join('') | ||
| return [jsDocStr, declaration].filter(Boolean).join('\n') | ||
| } | ||
| /** | ||
| * Converts a {@link FunctionNode} to a TypeScript `function` declaration string. | ||
| * | ||
| * Mirrors the `Function` component from `@kubb/renderer-jsx`. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * printFunction(factory.createFunction({ name: 'getPet', export: true, params: 'id: string', returnType: 'Pet', nodes: ['return fetch(id)'] })) | ||
| * // 'export function getPet(id: string): Pet {\n return fetch(id)\n}' | ||
| * ``` | ||
| * | ||
| * @example Async with generics | ||
| * ```ts | ||
| * printFunction(factory.createFunction({ name: 'fetchPet', export: true, async: true, generics: ['T'], params: 'id: string', returnType: 'T' })) | ||
| * // 'export async function fetchPet<T>(id: string): Promise<T> {\n}' | ||
| * ``` | ||
| */ | ||
| export function printFunction(node: FunctionNode): string { | ||
| const { name, default: isDefault, export: canExport, async: isAsync, generics, params, returnType, JSDoc, nodes } = node | ||
| const jsDocStr = JSDoc ? printJSDoc(JSDoc) : '' | ||
| const body = printNodes(nodes) | ||
| const indented = body ? indentLines(body) : '' | ||
| const parts: Array<string> = [] | ||
| if (canExport) parts.push('export ') | ||
| if (isDefault) parts.push('default ') | ||
| if (isAsync) parts.push('async ') | ||
| parts.push('function ') | ||
| parts.push(name) | ||
| parts.push(formatGenerics(generics)) | ||
| parts.push(`(${params ?? ''})`) | ||
| parts.push(formatReturnType(returnType, isAsync)) | ||
| parts.push(' {') | ||
| if (indented) { | ||
| parts.push(`\n${indented}\n`) | ||
| } | ||
| parts.push('}') | ||
| const declaration = parts.join('') | ||
| return [jsDocStr, declaration].filter(Boolean).join('\n') | ||
| } | ||
| /** | ||
| * Converts an {@link ArrowFunctionNode} to a TypeScript arrow function declaration string. | ||
| * | ||
| * Mirrors the `Function.Arrow` component from `@kubb/renderer-jsx`. | ||
| * | ||
| * @example Multi-line arrow function | ||
| * ```ts | ||
| * printArrowFunction(factory.createArrowFunction({ name: 'getPet', export: true, params: 'id: string', nodes: ['return fetch(id)'] })) | ||
| * // 'export const getPet = (id: string) => {\n return fetch(id)\n}' | ||
| * ``` | ||
| * | ||
| * @example Single-line arrow function | ||
| * ```ts | ||
| * printArrowFunction(factory.createArrowFunction({ name: 'double', params: 'n: number', singleLine: true, nodes: ['n * 2'] })) | ||
| * // 'const double = (n: number) => n * 2' | ||
| * ``` | ||
| */ | ||
| export function printArrowFunction(node: ArrowFunctionNode): string { | ||
| const { name, default: isDefault, export: canExport, async: isAsync, generics, params, returnType, JSDoc, nodes, singleLine } = node | ||
| const jsDocStr = JSDoc ? printJSDoc(JSDoc) : '' | ||
| const body = printNodes(nodes) | ||
| const arrowBody = singleLine ? ` => ${body}` : body ? ` => {\n${indentLines(body)}\n}` : ' => {}' | ||
| const parts: Array<string> = [] | ||
| if (canExport) parts.push('export ') | ||
| if (isDefault) parts.push('default ') | ||
| parts.push('const ') | ||
| parts.push(name) | ||
| parts.push(' = ') | ||
| if (isAsync) parts.push('async ') | ||
| parts.push(formatGenerics(generics)) | ||
| parts.push(`(${params ?? ''})`) | ||
| parts.push(formatReturnType(returnType, isAsync)) | ||
| parts.push(arrowBody) | ||
| const declaration = parts.join('') | ||
| return [jsDocStr, declaration].filter(Boolean).join('\n') | ||
| } | ||
| /** | ||
| * Converts a {@link CodeNode} to its TypeScript string representation. | ||
| * | ||
| * Dispatches to the appropriate printer based on the node's `kind`. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * printCodeNode(factory.createConst({ name: 'x', nodes: ['1'] })) | ||
| * // 'const x = 1' | ||
| * ``` | ||
| */ | ||
| export function printCodeNode(node: CodeNode): string { | ||
| if (node.kind === 'Break') return '' | ||
| if (node.kind === 'Text') return dedent((node as TextNode).value) | ||
| if (node.kind === 'Jsx') return dedent((node as JsxNode).value) | ||
| if (node.kind === 'Const') return printConst(node) | ||
| if (node.kind === 'Type') return printType(node) | ||
| if (node.kind === 'Function') return printFunction(node) | ||
| if (node.kind === 'ArrowFunction') return printArrowFunction(node) | ||
| return '' | ||
| } | ||
| /** | ||
| * Converts a {@link SourceNode} to its TypeScript string representation. | ||
| * | ||
| * Iterates `nodes` in DOM order, rendering each {@link CodeNode} via | ||
| * {@link printCodeNode}. | ||
| * | ||
| * Top-level declarations are separated by a blank line so the source reads | ||
| * cleanly without an external formatter. | ||
| * | ||
| * @example From nodes | ||
| * ```ts | ||
| * printSource({ kind: 'Source', nodes: [factory.createConst({ name: 'x', nodes: [factory.createText('1')] }), factory.createText('x.toString()')] }) | ||
| * // 'const x = 1\n\nx.toString()' | ||
| * ``` | ||
| */ | ||
| export function printSource(node: SourceNode): string { | ||
| const nodes = node.nodes | ||
| if (!nodes || nodes.length === 0) return '' | ||
| return nodes | ||
| .map((child) => printCodeNode(child as CodeNode)) | ||
| .filter(Boolean) | ||
| .join('\n\n') | ||
| } | ||
| /** | ||
| * Wraps a module specifier in single quotes, escaping any embedded backslash or quote so the emitted | ||
| * statement stays valid even for unusual paths. | ||
| */ | ||
| function quoteModulePath(path: string): string { | ||
| return `'${path.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'` | ||
| } | ||
| /** | ||
| * Renders an import declaration string in the repo style (single quotes, no semicolons), covering | ||
| * default, namespace (`* as`), and named imports with `{ a as b }` aliases, each optionally | ||
| * `type`-only. `path` is used verbatim, so resolve it first. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * printImport({ name: ['z'], path: './zod.ts' }) | ||
| * // "import { z } from './zod.ts'" | ||
| * ``` | ||
| */ | ||
| export function printImport({ | ||
| name, | ||
| path, | ||
| isTypeOnly = false, | ||
| isNameSpace = false, | ||
| }: { | ||
| name: string | Array<string | { propertyName: string; name?: string }> | ||
| path: string | ||
| isTypeOnly?: boolean | null | ||
| isNameSpace?: boolean | null | ||
| }): string { | ||
| const typePrefix = isTypeOnly ? 'type ' : '' | ||
| const from = quoteModulePath(path) | ||
| if (!Array.isArray(name)) { | ||
| if (isNameSpace) return `import ${typePrefix}* as ${name} from ${from}` | ||
| return `import ${typePrefix}${name} from ${from}` | ||
| } | ||
| const specifiers = name.map((item) => { | ||
| if (typeof item === 'object') { | ||
| return item.name ? `${item.propertyName} as ${item.name}` : item.propertyName | ||
| } | ||
| return item | ||
| }) | ||
| return `import ${typePrefix}{ ${specifiers.join(', ')} } from ${from}` | ||
| } | ||
| /** | ||
| * Renders an export declaration string in the repo style (single quotes, no semicolons), covering | ||
| * named re-exports, namespace alias (`* as name`), and wildcard, each optionally `type`-only. | ||
| * `path` is used verbatim, so resolve it first. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * printExport({ name: ['Pet', 'Order'], path: './models.ts' }) | ||
| * // "export { Pet, Order } from './models.ts'" | ||
| * ``` | ||
| */ | ||
| export function printExport({ | ||
| path, | ||
| name, | ||
| isTypeOnly = false, | ||
| asAlias = false, | ||
| }: { | ||
| path: string | ||
| name?: string | Array<ts.Identifier | string> | null | ||
| isTypeOnly?: boolean | null | ||
| asAlias?: boolean | null | ||
| }): string { | ||
| const typePrefix = isTypeOnly ? 'type ' : '' | ||
| const from = quoteModulePath(path) | ||
| if (Array.isArray(name)) { | ||
| const specifiers = name.map((item) => (typeof item === 'string' ? item : item.text)) | ||
| return `export ${typePrefix}{ ${specifiers.join(', ')} } from ${from}` | ||
| } | ||
| if (asAlias && name) { | ||
| const parsedName = LEADING_DIGIT_PATTERN.test(name) ? `_${name.slice(1)}` : name | ||
| return `export ${typePrefix}* as ${parsedName} from ${from}` | ||
| } | ||
| if (name) { | ||
| console.warn(`When using name as string, asAlias should be true: ${name}`) | ||
| } | ||
| return `export ${typePrefix}* from ${from}` | ||
| } |
121916
-14.87%9
-35.71%1132
-34.03%+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
Updated