From 161dc9a902f7cbcedd0a97965facbb481a430d56 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 25 Oct 2024 21:44:45 +0200 Subject: [PATCH] chore: wip --- fixtures/output/example-0001.d.ts | 68 +++++----- src/extract.ts | 203 +++++++++++++----------------- 2 files changed, 120 insertions(+), 151 deletions(-) diff --git a/fixtures/output/example-0001.d.ts b/fixtures/output/example-0001.d.ts index 4deb818..39bbdd6 100644 --- a/fixtures/output/example-0001.d.ts +++ b/fixtures/output/example-0001.d.ts @@ -2,12 +2,13 @@ import type { BunPlugin } from 'bun' import type { DtsGenerationConfig, DtsGenerationOption } from '@stacksjs/dtsx' import { generate } from '@stacksjs/dtsx' - /** * Example of const declaration */ -export declare const conf: { [key: string]: string }; - +export const conf: { [key: string]: string } = { + apiUrl: 'https://api.stacksjs.org', + timeout: '5000', // as string +} export declare const someObject: { someString: 'Stacks'; someNumber: 1000; @@ -37,91 +38,93 @@ export declare const someObject: { someInlineCall2: (...args: any[]) => void; someInlineCall3: (...args: any[]) => void; }; - /** * Example of interface declaration * with another comment in an extra line */ -export declare interface User { +export interface User { id: number name: string email: string } - /** * Example of type declaration * * with multiple lines of comments, including an empty line */ -export declare interface ResponseData { +export interface ResponseData { success: boolean data: User[] } - /** * Example of function declaration * * * with multiple empty lines, including an empty lines */ -export declare function fetchUsers(): Promise; - +export function fetchUsers(): Promise { + return fetch(conf.apiUrl) + .then(response => response.json()) as Promise +} export declare interface ApiResponse { status: number message: string data: T } - /** * Example of another const declaration * * with multiple empty lines, including being poorly formatted */ -declare const settings: { [key: string]: any }; - +const settings: { [key: string]: any } = { + theme: 'dark', + language: 'en', +} export declare interface Product { id: number name: string price: number } - /** * Example of function declaration */ -export declare function getProduct(id: number): Promise>; - +export function getProduct(id: number): Promise> { + return fetch(`${settings.apiUrl}/products/${id}`) + .then(response => response.json()) as Promise> +} export declare interface AuthResponse { token: string expiresIn: number } - -export declare type AuthStatus = 'authenticated' | 'unauthenticated'; - -export declare function authenticate(user: string, password: string): Promise; - +export declare type AuthStatus = 'authenticated' | 'unauthenticated' +export function authenticate(user: string, password: string): Promise { + return fetch('/auth/login', { + method: 'POST', + body: JSON.stringify({ user, password }), + }).then(response => response.json()) as Promise +} export declare const defaultHeaders: { 'Content-Type': 'application/json'; }; - export declare function dts(options?: DtsGenerationOption): BunPlugin; - declare interface Options { name: string cwd?: string defaultConfig: T } - export declare function loadConfig>(options: Options): Promise; - - declare const dtsConfig: DtsGenerationConfig; export { generate, dtsConfig } - export type { DtsGenerationOption } export { config } from './config' +export * from './extract' +export * from './generate' + +export * from './types' +export * from './utils' export declare interface ComplexGeneric, K extends keyof T> { data: T @@ -130,21 +133,10 @@ export declare interface ComplexGeneric, K ext transform: (input: T[K]) => string nested: Array> } - - export declare type ComplexUnionIntersection = - | (User & { role: 'admin' }) - | (Product & { category: string }) - & { metadata: Record } - -export * from './extract' -export * from './generate' -export * from './types' -export * from './utils' - export default dts diff --git a/src/extract.ts b/src/extract.ts index e4f5fed..102d633 100644 --- a/src/extract.ts +++ b/src/extract.ts @@ -889,20 +889,30 @@ function isDeclarationStart(line: string): boolean { ) } -function isDeclarationComplete(lines: string[]): boolean { +function isDeclarationComplete(content: string): boolean { + // Split into lines while preserving empty lines + const lines = content.split('\n') let bracketCount = 0 let inString = false let stringChar = '' + let lastNonEmptyLine = '' for (const line of lines) { - for (const char of line) { - // Handle string content - if ((char === '"' || char === '\'') && !inString) { - inString = true - stringChar = char - } - else if (inString && char === stringChar) { - inString = false + if (line.trim()) + lastNonEmptyLine = line.trim() + + for (let i = 0; i < line.length; i++) { + const char = line[i] + + // Handle strings + if ((char === '"' || char === '\'') && (i === 0 || line[i - 1] !== '\\')) { + if (!inString) { + inString = true + stringChar = char + } + else if (char === stringChar) { + inString = false + } continue } @@ -915,9 +925,18 @@ function isDeclarationComplete(lines: string[]): boolean { } } - // Also check for single-line declarations - const lastLine = lines[lines.length - 1].trim() - return bracketCount === 0 && (lastLine.endsWith(';') || lastLine.endsWith('}')) + // Consider a declaration complete if: + return ( + bracketCount === 0 && ( + // Ends with semicolon or closing brace + lastNonEmptyLine.endsWith(';') + || lastNonEmptyLine.endsWith('}') + // Is a type export + || lastNonEmptyLine.startsWith('export type {') + // Is a module export + || lastNonEmptyLine.startsWith('export * from') + ) + ) } /** @@ -1199,12 +1218,12 @@ function processTypeDeclaration(declaration: string, isExported = true): string const lines = declaration.split('\n') const baseIndent = getIndentation(lines[0]) - // Handle type exports (e.g., "export type { DtsGenerationOption }") + // Handle type exports if (lines[0].includes('type {')) { return declaration } - // Extract type name and process content + // Extract type name and initial content const typeMatch = lines[0].match(/^(?:export\s+)?type\s+([^=\s]+)\s*=\s*(.*)/) if (!typeMatch) return declaration @@ -1212,20 +1231,21 @@ function processTypeDeclaration(declaration: string, isExported = true): string const [, name, initialContent] = typeMatch const prefix = isExported ? 'export declare' : 'declare' - // Handle single-line type declarations - if (lines.length === 1 && initialContent) { + // If it's a simple single-line type + if (lines.length === 1) { return `${baseIndent}${prefix} type ${name} = ${initialContent};` } - // Handle multi-line type declarations - const processedLines = [`${baseIndent}${prefix} type ${name} = ${initialContent}`] + // For multi-line types, properly format with line breaks + const processedLines = [`${baseIndent}${prefix} type ${name} = ${initialContent.trim()}`] + const remainingLines = lines.slice(1) - for (let i = 1; i < lines.length; i++) { - const line = lines[i] - const lineIndent = getIndentation(line) - const content = line.trim() - if (content) { - processedLines.push(`${lineIndent}${content}`) + for (const line of remainingLines) { + const trimmed = line.trim() + if (trimmed) { + // Keep original indentation for the line + const lineIndent = getIndentation(line) + processedLines.push(`${lineIndent}${trimmed}`) } } @@ -1234,121 +1254,78 @@ function processTypeDeclaration(declaration: string, isExported = true): string function processSourceFile(content: string, state: ProcessingState): void { const lines = content.split('\n') - const importLines: string[] = [] - const exportDefaultLines: string[] = [] - const exportStarLines: string[] = [] let currentBlock: string[] = [] let currentComments: string[] = [] let isInMultilineDeclaration = false - // First pass: collect imports and exports - for (const line of lines) { - const trimmedLine = line.trim() - if (trimmedLine.startsWith('import')) { - importLines.push(line) - continue - } - if (trimmedLine.startsWith('export default')) { - exportDefaultLines.push(line) - continue - } - if (trimmedLine.startsWith('export *')) { - exportStarLines.push(line) - continue - } - } + function flushBlock() { + if (currentBlock.length > 0) { + const fullBlock = currentBlock.join('\n') + + // If we have multiple declarations in one block, split them + if (!fullBlock.includes('type ') && fullBlock.includes('\nexport')) { + const declarations = fullBlock.split(/(?=\nexport)/) + declarations.forEach((declaration) => { + const trimmed = declaration.trim() + if (trimmed) { + if (currentComments.length > 0) { + processDeclarationBlock([...currentComments], [], state) + } + processDeclarationBlock([declaration], [], state) + } + }) + } + else { + processDeclarationBlock([...currentComments, ...currentBlock], [], state) + } - // Process imports - if (importLines.length > 0) { - state.dtsLines.push(...cleanImports(importLines)) - state.dtsLines.push('') // Add single line break after imports + currentBlock = [] + currentComments = [] + isInMultilineDeclaration = false + } } - // Process main content - for (const line of lines) { + for (let i = 0; i < lines.length; i++) { + const line = lines[i] const trimmedLine = line.trim() - // Skip already processed lines - if (trimmedLine.startsWith('import') - || trimmedLine.startsWith('export default') - || trimmedLine.startsWith('export *')) { - continue - } - - // Skip empty lines between declarations - if (!trimmedLine && !isInMultilineDeclaration) { - if (currentBlock.length > 0) { - processDeclarationBlock(currentBlock, currentComments, state) - currentBlock = [] - currentComments = [] - state.dtsLines.push('') // Add line break after each declaration - } - continue - } - // Handle comments if (isCommentLine(trimmedLine)) { if (!isInMultilineDeclaration) { - if (trimmedLine.startsWith('/**')) { - currentComments = [] - } currentComments.push(line) + continue } - else { - currentBlock.push(line) - } - continue } - // Track multiline declarations - if (!isInMultilineDeclaration && (trimmedLine.includes('{') || trimmedLine.includes('('))) { + // Check for declaration start + if (!isInMultilineDeclaration && isDeclarationStart(trimmedLine)) { + flushBlock() + currentBlock.push(line) isInMultilineDeclaration = true + continue } - currentBlock.push(line) - - if (isInMultilineDeclaration) { - const openCount = (line.match(/[{(]/g) || []).length - const closeCount = (line.match(/[})]/g) || []).length - state.bracketCount += openCount - closeCount - - if (state.bracketCount === 0) { - isInMultilineDeclaration = false - processDeclarationBlock(currentBlock, currentComments, state) - currentBlock = [] - currentComments = [] - state.dtsLines.push('') // Add line break after each declaration + // Handle empty lines + if (!trimmedLine) { + if (!isInMultilineDeclaration) { + flushBlock() + continue } } - else if (!trimmedLine.endsWith(',')) { - processDeclarationBlock(currentBlock, currentComments, state) - currentBlock = [] - currentComments = [] - state.dtsLines.push('') // Add line break after each declaration - } - } - // Process any remaining block - if (currentBlock.length > 0) { - processDeclarationBlock(currentBlock, currentComments, state) - state.dtsLines.push('') - } + if (isInMultilineDeclaration) { + currentBlock.push(line) - // Add export * statements with proper spacing - if (exportStarLines.length > 0) { - if (state.dtsLines[state.dtsLines.length - 1] !== '') { - state.dtsLines.push('') + // Check if declaration is complete + const currentContent = currentBlock.join('\n') + if (isDeclarationComplete(currentContent)) { + flushBlock() + } } - state.dtsLines.push(...exportStarLines) } - // Add export default at the end with proper spacing - if (exportDefaultLines.length > 0) { - if (state.dtsLines[state.dtsLines.length - 1] !== '') { - state.dtsLines.push('') - } - state.dtsLines.push(...exportDefaultLines) - } + // Process any remaining block + flushBlock() } /**