Skip to content

Commit 1cea1fe

Browse files
committed
chore: wip
chore: wip
1 parent 38c8c80 commit 1cea1fe

1 file changed

Lines changed: 141 additions & 116 deletions

File tree

src/extract.ts

Lines changed: 141 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -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(/export\s+type\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
*/
160168
export 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(/export\s+type\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(/^(export\s+)?(async\s+)?function\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(/^(void|any|number|string|boolean|null|undefined|never|unknown|Promise)$/)) {
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(/function\s+function/, 'function')
881902
}
882903

883904
// Helper functions for line processing
@@ -907,17 +928,22 @@ export function isDeclarationLine(line: string): boolean {
907928

908929
export 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(/^export\s+default\s+/, '')

0 commit comments

Comments
 (0)