@@ -101,6 +101,14 @@ export function extractDtsTypes(sourceCode: string): string {
101101
102102 const lines = sourceCode . split ( '\n' )
103103 for ( const line of lines ) {
104+ // Add explicitly re-exported types to usedTypes
105+ if ( line . trim ( ) . startsWith ( 'export type {' ) ) {
106+ const typeMatch = line . match ( / e x p o r t \s + t y p e \s * \{ ( [ ^ } ] + ) \} / )
107+ if ( typeMatch ) {
108+ const types = typeMatch [ 1 ] . split ( ',' ) . map ( t => t . trim ( ) )
109+ types . forEach ( type => state . usedTypes . add ( type ) )
110+ }
111+ }
104112 processLine ( line , state )
105113 }
106114
@@ -159,8 +167,9 @@ export function processImport(importLine: string, typeSources: Map<string, strin
159167 */
160168export function processImports ( imports : string [ ] , usedTypes : Set < string > ) : string [ ] {
161169 const importMap = new Map < string , Set < string > > ( )
170+ const reExportedTypes = new Set < string > ( )
162171
163- // Process each import line to extract module and types
172+ // First pass: process each import line
164173 for ( const line of imports ) {
165174 const typeImportMatch = line . match ( REGEX . typeImport )
166175 const regularImportMatch = line . match ( REGEX . regularImport )
@@ -170,38 +179,48 @@ export function processImports(imports: string[], usedTypes: Set<string>): strin
170179 const types = match [ 1 ] . split ( ',' ) . map ( t => t . trim ( ) )
171180 const module = match [ 2 ]
172181
173- // Only track types that are actually used
174- const usedImports = types . filter ( ( type ) => {
182+ // Track all imported types, including those that might be re-exported
183+ types . forEach ( ( type ) => {
175184 const baseName = type . split ( ' as ' ) [ 0 ] . trim ( )
176- return usedTypes . has ( baseName )
177- } )
178-
179- if ( usedImports . length > 0 ) {
180- if ( ! importMap . has ( module ) ) {
185+ if ( ! importMap . has ( module ) )
181186 importMap . set ( module , new Set ( ) )
182- }
183- usedImports . forEach ( type => importMap . get ( module ) ! . add ( type ) )
184- }
187+
188+ importMap . get ( module ) ! . add ( type )
189+
190+ // If this is a type that's re-exported, mark it
191+ if ( usedTypes . has ( baseName ) )
192+ reExportedTypes . add ( baseName )
193+ } )
185194 }
186195 }
187196
188197 // These are the only modules we want to keep imports from
189198 const allowedModules = [ 'bun' , '@stacksjs/dtsx' ]
190199
191200 // Format imports, filtering to allowed modules only
201+ // Now include both used types and re-exported types
192202 return Array . from ( importMap . entries ( ) )
193203 . filter ( ( [ module ] ) => allowedModules . includes ( module ) )
194204 . map ( ( [ module , types ] ) => {
195- const sortedTypes = Array . from ( types ) . sort ( )
205+ const relevantTypes = Array . from ( types ) . filter ( ( type ) => {
206+ const baseName = type . split ( ' as ' ) [ 0 ] . trim ( )
207+ return usedTypes . has ( baseName ) || reExportedTypes . has ( baseName )
208+ } )
209+
210+ if ( relevantTypes . length === 0 )
211+ return ''
212+
213+ const sortedTypes = relevantTypes . sort ( )
196214 return `import type { ${ sortedTypes . join ( ', ' ) } } from '${ module } ';`
197215 } )
198- . sort ( ) // Sort imports alphabetically
216+ . filter ( Boolean )
217+ . sort ( )
199218}
200219
201220/**
202221 * Process declarations (const, interface, type, function)
203222 */
204- export function processDeclaration ( declaration : string , usedTypes : Set < string > ) : string {
223+ export function processDeclaration ( declaration : string , state : ProcessingState ) : string {
205224 const trimmed = declaration . trim ( )
206225
207226 if ( trimmed . startsWith ( 'export const' ) )
@@ -217,10 +236,10 @@ export function processDeclaration(declaration: string, usedTypes: Set<string>):
217236 return processInterfaceDeclaration ( trimmed , false )
218237
219238 if ( trimmed . startsWith ( 'export type {' ) )
220- return processTypeOnlyExport ( trimmed )
239+ return processTypeOnlyExport ( trimmed , state )
221240
222241 if ( trimmed . startsWith ( 'type {' ) )
223- return processTypeOnlyExport ( trimmed , false )
242+ return processTypeOnlyExport ( trimmed , state , false )
224243
225244 if ( trimmed . startsWith ( 'export type' ) )
226245 return processTypeDeclaration ( trimmed )
@@ -229,10 +248,10 @@ export function processDeclaration(declaration: string, usedTypes: Set<string>):
229248 return processTypeDeclaration ( trimmed , false )
230249
231250 if ( trimmed . startsWith ( 'export function' ) || trimmed . startsWith ( 'export async function' ) )
232- return processFunctionDeclaration ( trimmed , usedTypes )
251+ return processFunctionDeclaration ( trimmed , state . usedTypes )
233252
234253 if ( trimmed . startsWith ( 'function' ) || trimmed . startsWith ( 'async function' ) )
235- return processFunctionDeclaration ( trimmed , usedTypes , false )
254+ return processFunctionDeclaration ( trimmed , state . usedTypes , false )
236255
237256 if ( trimmed . startsWith ( 'export default' ) )
238257 return `${ trimmed } ;`
@@ -739,7 +758,14 @@ export function processInterfaceDeclaration(declaration: string, isExported = tr
739758/**
740759 * Process type-only exports
741760 */
742- export function processTypeOnlyExport ( declaration : string , isExported = true ) : string {
761+ export function processTypeOnlyExport ( declaration : string , state : ProcessingState , isExported = true ) : string {
762+ // When processing "export type { X }", add X to usedTypes
763+ const typeMatch = declaration . match ( / e x p o r t \s + t y p e \s * \{ ( [ ^ } ] + ) \} / )
764+ if ( typeMatch ) {
765+ const types = typeMatch [ 1 ] . split ( ',' ) . map ( t => t . trim ( ) )
766+ types . forEach ( type => state . usedTypes . add ( type ) )
767+ }
768+
743769 return declaration
744770 . replace ( 'export type' , `${ isExported ? 'export ' : '' } declare type` )
745771 . replace ( / ; $ / , '' )
@@ -760,59 +786,39 @@ export function processTypeDeclaration(declaration: string, isExported = true):
760786/**
761787 * Extract complete function signature handling multi-line declarations
762788 */
763- export function extractFunctionSignature ( declaration : string ) : string {
764- // First, normalize line breaks and whitespace
765- const normalized = declaration
766- . split ( '\n' )
767- . map ( line => line . trim ( ) )
768- . join ( ' ' )
769-
770- // Match function declaration including parameters and return type
771- const match = normalized . match ( / ^ ( e x p o r t \s + ) ? ( a s y n c \s + ) ? f u n c t i o n \s + ( [ ^ { ] + ) / )
772- if ( ! match )
773- return ''
774-
775- let signature = match [ 0 ]
776- let depth = 0
777- let genericDepth = 0
778- let foundBody = false
789+ export function extractFunctionSignature ( declaration : string ) : {
790+ name : string
791+ params : string
792+ returnType : string
793+ isAsync : boolean
794+ generics : string
795+ } {
796+ const isAsync = declaration . includes ( 'async' )
797+ const cleanDeclaration = declaration
798+ . replace ( 'export ' , '' )
799+ . replace ( 'async ' , '' )
800+ . replace ( 'function ' , '' )
801+ . trim ( )
779802
780- // Process character by character to find the complete signature
781- for ( let i = match [ 0 ] . length ; i < normalized . length && ! foundBody ; i ++ ) {
782- const char = normalized [ i ]
803+ const nameMatch = cleanDeclaration . match ( / ^ ( [ ^ < ( \s ] + ) / )
804+ const name = nameMatch ? nameMatch [ 1 ] : ''
783805
784- if ( char === '<' ) {
785- genericDepth ++
786- }
787- else if ( char === '>' ) {
788- genericDepth --
789- }
790- else if ( genericDepth === 0 ) {
791- if ( char === '(' ) {
792- depth ++
793- }
794- else if ( char === ')' ) {
795- depth --
796- }
797- else if ( char === '{' ) {
798- foundBody = true
799- break
800- }
801- }
806+ const genericsMatch = cleanDeclaration . match ( / < ( [ ^ > ] + ) > / )
807+ const generics = genericsMatch ? `<${ genericsMatch [ 1 ] } >` : ''
802808
803- if ( ! foundBody ) {
804- signature += char
805- }
806- }
809+ const paramsMatch = cleanDeclaration . match ( / \( ( .* ?) \) / )
810+ const params = paramsMatch ? paramsMatch [ 1 ] : ''
807811
808- // Clean up the signature
809- signature = signature
810- . replace ( / \s + / g, ' ' )
811- . replace ( / : \s + / g, ': ' )
812- . trim ( )
812+ const returnTypeMatch = cleanDeclaration . match ( / \) : \s * ( [ ^ { ; ] + ) / )
813+ const returnType = returnTypeMatch ? returnTypeMatch [ 1 ] . trim ( ) : 'void'
813814
814- console . log ( 'Extracted raw signature:' , signature )
815- return signature
815+ return {
816+ name,
817+ params,
818+ returnType,
819+ isAsync,
820+ generics,
821+ }
816822}
817823
818824/**
@@ -823,61 +829,76 @@ export function processFunctionDeclaration(
823829 usedTypes : Set < string > ,
824830 isExported = true ,
825831) : string {
826- console . log ( 'Processing declaration:' , declaration )
827-
828- const signature = extractFunctionSignature ( declaration )
829- if ( ! signature ) {
830- console . log ( 'No valid signature found' )
831- return declaration
832- }
832+ const functionSignature = declaration . split ( '{' ) [ 0 ] . trim ( )
833+ const asyncKeyword = functionSignature . includes ( 'async' ) ? 'async ' : ''
833834
834- console . log ( 'Using signature:' , signature )
835- const parseResult = parseFunctionDeclaration ( signature )
836- console . log ( 'Parse result:' , parseResult )
835+ // Extract function name and generic parameters
836+ const nameAndGenerics = functionSignature
837+ . replace ( 'export ' , '' )
838+ . replace ( 'async ' , '' )
839+ . replace ( 'function ' , '' )
840+ . split ( '(' ) [ 0 ]
841+ . trim ( )
837842
838- // Add types to usedTypes set
839- const addTypeToUsed = ( type : string ) => {
840- if ( ! type )
841- return
843+ // Handle generic type parameters
844+ const genericMatch = nameAndGenerics . match ( / < ( [ ^ > ] + ) > / ) ?. [ 1 ]
845+ const functionName = nameAndGenerics . split ( '<' ) [ 0 ] . trim ( )
846+ const genericParams = genericMatch ? `< ${ genericMatch } >` : ''
842847
843- const typeMatches = type . match ( / ( [ A - Z _ ] \w * ) / gi) || [ ]
844- typeMatches . forEach ( ( t ) => {
845- if ( ! t . match ( / ^ ( v o i d | a n y | n u m b e r | s t r i n g | b o o l e a n | n u l l | u n d e f i n e d | n e v e r | u n k n o w n | P r o m i s e ) $ / ) ) {
846- usedTypes . add ( t )
847- console . log ( 'Added type to used:' , t )
848- }
848+ // Extract parameters
849+ const paramsMatch = functionSignature . match ( / \( ( .* ?) \) / ) ?. [ 1 ] || ''
850+
851+ // Get return type
852+ const returnTypeMatch = functionSignature . match ( / \) : \s * ( [ ^ { ; ] + ) / ) ?. [ 1 ] ?. trim ( )
853+ const returnType = returnTypeMatch || 'void'
854+
855+ // Add used types
856+ if ( genericMatch ) {
857+ genericMatch . split ( ',' ) . forEach ( ( type ) => {
858+ const cleanType = type . split ( 'extends' ) [ 0 ] . trim ( )
859+ if ( cleanType )
860+ usedTypes . add ( cleanType )
849861 } )
850862 }
851863
852- // Process all types
853- addTypeToUsed ( parseResult . returnType )
854- addTypeToUsed ( parseResult . parameters )
855-
856- if ( parseResult . genericParams ) {
857- const genericContent = parseResult . genericParams . slice ( 1 , - 1 )
858- genericContent . split ( ',' ) . forEach ( ( param ) => {
859- const [ , constraint ] = param . split ( ' extends ' )
860- if ( constraint ) {
861- addTypeToUsed ( constraint )
862- console . log ( 'Added generic constraint:' , constraint )
863- }
864- } )
864+ if ( returnType && returnType !== 'void' ) {
865+ // Add base type and any generic parameters to usedTypes
866+ const baseType = returnType . split ( '<' ) [ 0 ] . trim ( )
867+ usedTypes . add ( baseType )
868+
869+ // Extract types from generic parameters if present
870+ const returnGenericMatch = returnType . match ( / < ( [ ^ > ] + ) > / ) ?. [ 1 ]
871+ if ( returnGenericMatch ) {
872+ returnGenericMatch . split ( ',' ) . forEach ( ( type ) => {
873+ const cleanType = type . trim ( ) . split ( '<' ) [ 0 ] . trim ( )
874+ if ( cleanType )
875+ usedTypes . add ( cleanType )
876+ } )
877+ }
865878 }
866879
867- // Construct the declaration, ensuring proper spacing and no duplicate colons
868- return [
880+ // Build the function declaration string
881+ const functionDeclaration = [
869882 isExported ? 'export ' : '' ,
870883 'declare ' ,
871- parseResult . isAsync ? 'async ' : '' ,
884+ asyncKeyword ,
872885 'function ' ,
873- parseResult . functionName ,
874- parseResult . genericParams ,
886+ functionName ,
887+ genericParams ,
875888 '(' ,
876- parseResult . parameters ,
889+ paramsMatch ,
877890 '): ' ,
878- parseResult . returnType ,
891+ returnType ,
879892 ';' ,
880893 ] . join ( '' )
894+
895+ return functionDeclaration
896+ . replace ( / \s + / g, ' ' )
897+ . replace ( / \s * ( [ < > ( ) , ; ] ) \s * / g, '$1' )
898+ . replace ( / , ( [ ^ , \s ] ) / g, ', $1' )
899+ . replace ( / > \s * \( / g, '>(' )
900+ . replace ( / \( \s * \) / g, '()' )
901+ . replace ( / f u n c t i o n \s + f u n c t i o n / , 'function' )
881902}
882903
883904// Helper functions for line processing
@@ -907,17 +928,22 @@ export function isDeclarationLine(line: string): boolean {
907928
908929export function processDeclarationLine ( line : string , state : ProcessingState ) : void {
909930 state . currentDeclaration += `${ line } \n`
910- const opens = ( line . match ( REGEX . bracketOpen ) || [ ] ) . length
911- const closes = ( line . match ( REGEX . bracketClose ) || [ ] ) . length
912- state . bracketCount += opens - closes
931+
932+ // Count brackets to track multi-line declarations
933+ const bracketMatch = line . match ( / [ [ { ( ] / g)
934+ const closeBracketMatch = line . match ( / [ \] } ) ] / g)
935+ const openCount = bracketMatch ? bracketMatch . length : 0
936+ const closeCount = closeBracketMatch ? closeBracketMatch . length : 0
937+ state . bracketCount += openCount - closeCount
938+
913939 state . isMultiLineDeclaration = state . bracketCount > 0
914940
915941 if ( ! state . isMultiLineDeclaration ) {
916942 if ( state . lastCommentBlock ) {
917943 state . dtsLines . push ( state . lastCommentBlock . trimEnd ( ) )
918944 state . lastCommentBlock = ''
919945 }
920- const processed = processDeclaration ( state . currentDeclaration . trim ( ) , state . usedTypes )
946+ const processed = processDeclaration ( state . currentDeclaration . trim ( ) , state )
921947 if ( processed )
922948 state . dtsLines . push ( processed )
923949 state . currentDeclaration = ''
@@ -956,15 +982,14 @@ export function formatOutput(state: ProcessingState): string {
956982 line . startsWith ( '*' ) ? ` ${ line } ` : line ,
957983 )
958984
959- // Ensure double newline after imports
960- const importSection = allImports . length > 0 ? [ ...allImports , '' , '' ] : [ ]
961-
962985 let result = [
963- ...importSection ,
986+ ...allImports ,
987+ '' ,
988+ '' , // Extra newline after imports
964989 ...declarations ,
965990 ] . filter ( Boolean ) . join ( '\n' )
966991
967- // Clean up default export if present
992+ // Clean up default export
968993 if ( state . defaultExport ) {
969994 const exportIdentifier = state . defaultExport
970995 . replace ( / ^ e x p o r t \s + d e f a u l t \s + / , '' )
0 commit comments