diff --git a/fixtures/output/0002.d.ts b/fixtures/output/0002.d.ts index da76b29..3c16612 100644 --- a/fixtures/output/0002.d.ts +++ b/fixtures/output/0002.d.ts @@ -2,8 +2,9 @@ import type { BunPlugin } from 'bun'; import type { DtsGenerationOption } from '@stacksjs/dtsx'; import { generate } from '@stacksjs/dtsx'; -export { generate } -export type { DtsGenerationOption }; +export type { DtsGenerationOption } export declare function dts(options?: DtsGenerationOption): BunPlugin; +export { generate } + export default dts; \ No newline at end of file diff --git a/fixtures/output/0006.d.ts b/fixtures/output/0006.d.ts index c8d13cf..06fb936 100644 --- a/fixtures/output/0006.d.ts +++ b/fixtures/output/0006.d.ts @@ -1,5 +1,7 @@ import type { Collection } from '@stacksjs/collections'; +export declare let quotes: Collection; + export { align, box, @@ -59,6 +61,5 @@ export { white, yellow, } from 'kolorist' -export declare let quotes: Collection; export * as kolorist from 'kolorist' \ No newline at end of file diff --git a/fixtures/output/0007.d.ts b/fixtures/output/0007.d.ts index 1956ffc..6b9b140 100644 --- a/fixtures/output/0007.d.ts +++ b/fixtures/output/0007.d.ts @@ -1,7 +1,6 @@ import type { CAOptions, CertificateOptions, GenerateCertReturn, TlsOption } from './types'; import { default as forge, pki, tls } from 'node-forge'; -export { forge, pki, tls } export declare interface Cert { certificate: string privateKey: string @@ -13,4 +12,6 @@ export declare function createRootCA(options: CAOptions): Promise; export declare function addCertToSystemTrustStoreAndSaveCert(cert: Cert, caCert: string, options?: TlsOption): Promise; export declare function storeCertificate(cert: Cert, options?: TlsOption): string; -export declare function storeCACertificate(caCert: string, options?: TlsOption): string; \ No newline at end of file +export declare function storeCACertificate(caCert: string, options?: TlsOption): string; + +export { forge, pki, tls } \ No newline at end of file diff --git a/fixtures/output/0009.d.ts b/fixtures/output/0009.d.ts index a10e49c..9c189f1 100644 --- a/fixtures/output/0009.d.ts +++ b/fixtures/output/0009.d.ts @@ -1,6 +1,5 @@ import { actionsPath } from '@stacksjs/path'; -export { basename, delimiter, dirname, extname, isAbsolute, join, normalize, relative, resolve, sep, toNamespacedPath } declare type ParsedPath, relative, resolve, @@ -16,4 +15,6 @@ export declare function userActionsPath(path?: string, options?: { relative: , t export declare function builtUserActionsPath(path?: string, options?: { relative: , boolean }): string; export declare function homeDir(path?: string): string; export declare function libraryEntryPath(type: LibraryType): string; -export declare function examplesPath(type?: 'vue-components' | 'web-components'): string; \ No newline at end of file +export declare function examplesPath(type?: 'vue-components' | 'web-components'): string; + +export { basename, delimiter, dirname, extname, isAbsolute, join, normalize, relative, resolve, sep, toNamespacedPath } \ No newline at end of file diff --git a/fixtures/output/exports.d.ts b/fixtures/output/exports.d.ts index 2ddd824..786fd3a 100644 --- a/fixtures/output/exports.d.ts +++ b/fixtures/output/exports.d.ts @@ -3,9 +3,10 @@ import type { SomeOtherType } from '@stacksjs/types'; import { dtsConfig } from './config'; import { generate, something as dts } from './generate'; +export type { SomeOtherType } +export type { BunRegisterPlugin } from 'bun' + export { generate, dtsConfig, type BunPlugin } -export type { SomeOtherType }; -export type { BunRegisterPlugin } from 'bun'; export { config } from './config' export * from './extract' diff --git a/fixtures/output/type.d.ts b/fixtures/output/type.d.ts index 6478eae..b6e61b5 100644 --- a/fixtures/output/type.d.ts +++ b/fixtures/output/type.d.ts @@ -1,6 +1,6 @@ import type { DtsGenerationOption } from '@stacksjs/dtsx'; -export type { DtsGenerationOption }; +export type { DtsGenerationOption } export declare type AuthStatus = 'authenticated' | 'unauthenticated' export type ComplexUnionIntersection = diff --git a/src/extract.ts b/src/extract.ts index ec0b32e..f1f0306 100644 --- a/src/extract.ts +++ b/src/extract.ts @@ -616,93 +616,75 @@ function extractCompleteObjectContent(value: string): string | null { */ function formatOutput(state: ProcessingState): string { const imports = new Set() + const declarations: string[] = [] + const exports: string[] = [] + const defaultExports: string[] = [] + const exportAllStatements: string[] = [] - // Deduplicate and format imports - state.dtsLines - .filter(line => line.startsWith('import')) - .forEach(imp => imports.add(imp.replace(/;+$/, ''))) - - // Get all non-import lines, clean up semicolons and comments - const declarations = state.dtsLines - .filter(line => !line.startsWith('import')) - .map((line) => { - // Remove any standalone comment lines - if (line.trim().startsWith('/*') || line.trim().startsWith('*') || line.trim().startsWith('//')) { - return '' - } - - // Clean up any multiple semicolons and ensure all declarations end with one - const trimmed = line.trim() - if (!trimmed) - return '' - - // Don't add semicolons to export * statements or when one already exists - if (trimmed.startsWith('export *') || trimmed.endsWith(';')) { - return trimmed - } - - // Add semicolon to type exports that don't have one - if (trimmed.startsWith('export type')) { - return `${trimmed};` - } - - return trimmed.replace(/;+$/, ';') - }) - .filter(line => line.trim()) // Remove empty lines after comment removal + // Process all lines and categorize them + state.dtsLines.forEach((line) => { + const trimmed = line.trim() + if (!trimmed) + return - // Add default exports from state.defaultExports - const defaultExports = Array.from(state.defaultExports) - .map(exp => exp.trim().replace(/;+$/, ';')) + if (trimmed.startsWith('import')) { + imports.add(trimmed.replace(/;+$/, '')) + } + else if (trimmed.startsWith('export {')) { + exports.push(trimmed) + } + else if (trimmed.startsWith('export default')) { + defaultExports.push(trimmed) + } + else if (trimmed.startsWith('export *')) { + exportAllStatements.push(trimmed) + } + else { + declarations.push(trimmed) + } + }) - // Organize declarations by type - const exportAllStatements = declarations.filter(line => line.trim().startsWith('export *')) - const otherDeclarations = declarations.filter(line => !line.trim().startsWith('export *')) + // Add default exports from state + Array.from(state.defaultExports) + .forEach(exp => defaultExports.push(exp.trim().replace(/;+$/, ';'))) - // Construct the output with proper spacing + // Construct the output with proper ordering const parts: string[] = [] - // Add imports with a line break after if there are any + // 1. Add imports if (imports.size > 0) { - parts.push( - ...Array.from(imports).map(imp => `${imp};`), - '', - ) + parts.push(...Array.from(imports).map(imp => `${imp};`), '') + } + + // 2. Add declarations + if (declarations.length > 0) { + parts.push(...declarations) } - // Add other declarations - if (otherDeclarations.length > 0) { - parts.push(...otherDeclarations) + // 3. Add regular exports + if (exports.length > 0) { + if (parts.length > 0) + parts.push('') + parts.push(...exports) } - // Position export * statements based on whether there's a default export + // 4. Add export * statements if (exportAllStatements.length > 0) { - if (defaultExports.length > 0) { - // Add export * statements before default export - if (parts.length > 0) { - parts.push('') - } - parts.push(...exportAllStatements) - } - else { - // Add export * statements at the end - if (parts.length > 0) { - parts.push('') - } - parts.push(...exportAllStatements) - } + if (parts.length > 0) + parts.push('') + parts.push(...exportAllStatements) } - // Add default exports last if there are any + // 5. Add default exports last if (defaultExports.length > 0) { - if (parts.length > 0) { + if (parts.length > 0) parts.push('') - } parts.push(...defaultExports) } - // Clean up and return + // Clean up comments and join return parts - .map(line => line.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, '')) // Remove comments + .map(line => line.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, '')) .join('\n') .trim() } @@ -2032,14 +2014,27 @@ function handleTypeImports(types: string, module: string, state: ImportTrackingS state.typeImports.set(module, new Set()) } - types.split(',').forEach((type) => { - const [original, alias] = type.trim().split(/\s+as\s+/).map(t => t.trim()) - // Only track the import source, don't mark as used yet - state.typeImports.get(module)!.add(original) - state.typeExportSources.set(original, module) + // Split types by comma and handle multiline + const typesList = types.split(',').map(type => type.trim()).filter(Boolean).map((type) => { + // Remove line breaks and normalize whitespace + return type.replace(/\s+/g, ' ').trim() + }) + + typesList.forEach((type) => { + const [original, alias] = type.split(/\s+as\s+/).map(t => t.trim()) + + // Skip if empty or invalid + if (!original || original === '{' || original === '}') + return + + // Remove 'type ' prefix if present + const cleanType = original.replace(/^type\s+/, '') + + state.typeImports.get(module)!.add(cleanType) + state.typeExportSources.set(cleanType, module) if (alias) { - state.valueAliases.set(alias, original) + state.valueAliases.set(alias, cleanType) } }) }