Skip to content

Commit 91a5a8e

Browse files
committed
chore: wip
chore: wip
1 parent c2f82a4 commit 91a5a8e

File tree

4 files changed

+166
-99
lines changed

4 files changed

+166
-99
lines changed

fixtures/input/exports.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import { generate, something as dts } from './generate'
22
import { dtsConfig } from './config'
3+
import type { SomeOtherType } from '@stacksjs/types';
4+
import type { BunPlugin } from 'bun';
35

4-
export { generate, dtsConfig }
6+
export { generate, dtsConfig, type BunPlugin }
7+
export type { SomeOtherType }
58

69
export default dts
710

fixtures/output/exports.d.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
import { generate } from './generate';
2-
export { generate, dtsConfig }
3-
4-
export { config } from './config'
5-
export * from './extract'
6-
export * from './generate'
7-
export * from './types'
8-
export * from './utils'
1+
import { dtsConfig } from './config';
2+
import { generate, something as dts } from './generate';
3+
export { generate, dtsConfig, type BunPlugin };
4+
export type { SomeOtherType }
5+
;
6+
export { config } from './config';
7+
export * from './extract';
8+
export * from './generate';
9+
export * from './types';
10+
export * from './utils';
11+
export default dts;

src/extract.ts

Lines changed: 147 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,6 @@ interface ProcessedMethod {
66
signature: string
77
}
88

9-
interface BalancedSymbolResult {
10-
/** The extracted content including the opening and closing symbols */
11-
content: string
12-
/** The remaining text after the closing symbol */
13-
rest: string
14-
}
15-
169
function cleanParameterTypes(params: string): string {
1710
debugLog(undefined, 'params', `Cleaning parameters: ${params}`)
1811

@@ -146,7 +139,7 @@ export function extractDtsTypes(sourceCode: string): string {
146139
state.dtsLines.forEach((line) => {
147140
if (line.trim() && !line.startsWith('import')) {
148141
trackTypeUsage(line, state.importTracking)
149-
trackValueUsage(line, state.importTracking, state.dtsLines)
142+
trackValueUsage(line, state.importTracking)
150143
}
151144
})
152145

@@ -466,34 +459,85 @@ function extractFunctionType(value: string): string | null {
466459
*/
467460
function generateOptimizedImports(state: ImportTrackingState): string[] {
468461
const imports: string[] = []
462+
const seenImports = new Set<string>()
469463

470-
// Generate type imports
464+
debugLog(undefined, 'import-gen', `Generating optimized imports. Default export value: ${state.defaultExportValue}`)
465+
466+
// Handle type imports first
471467
for (const [module, types] of state.typeImports) {
472-
const usedTypes = Array.from(types)
473-
.filter(t => state.usedTypes.has(t))
468+
const typeImports = Array.from(types)
469+
.filter(t => state.usedTypes.has(t) || state.exportedValues?.has(t))
470+
.map((t) => {
471+
const alias = state.valueAliases.get(t)
472+
return alias ? `${t} as ${alias}` : t
473+
})
474474
.sort()
475475

476-
if (usedTypes.length > 0) {
477-
imports.push(`import type { ${usedTypes.join(', ')} } from '${module}'`)
476+
if (typeImports.length > 0) {
477+
const importStatement = `import type { ${typeImports.join(', ')} } from '${module}'`
478+
if (!seenImports.has(importStatement)) {
479+
imports.push(importStatement)
480+
seenImports.add(importStatement)
481+
debugLog(undefined, 'import-add', `Added type import: ${importStatement}`)
482+
}
483+
}
484+
}
485+
486+
// Group value imports by module
487+
const moduleImports = new Map<string, Set<string>>()
488+
const importAliases = new Map<string, string>()
489+
490+
// Handle default export
491+
if (state.defaultExportValue) {
492+
const originalName = Array.from(state.valueAliases.entries())
493+
.find(([alias]) => alias === state.defaultExportValue)?.[1]
494+
495+
if (originalName) {
496+
debugLog(undefined, 'import-default', `Found original name ${originalName} for default export alias ${state.defaultExportValue}`)
497+
const module = state.importSources.get(originalName)
498+
if (module) {
499+
if (!moduleImports.has(module)) {
500+
moduleImports.set(module, new Set())
501+
}
502+
moduleImports.get(module)!.add(originalName)
503+
importAliases.set(originalName, state.defaultExportValue)
504+
}
478505
}
479506
}
480507

481-
// Generate value imports
508+
// Handle regular value imports
482509
for (const [module, values] of state.valueImports) {
483510
const usedValues = Array.from(values)
484-
.filter(v => state.usedValues.has(v))
485-
// Only include values that appear in actual declarations
486-
.filter(v => dtsLines.some(line =>
487-
line.includes(`declare ${v}`)
488-
|| line.includes(`export declare ${v}`)
489-
|| line.includes(`export { ${v}`)
490-
|| line.includes(`, ${v}`)
491-
|| line.includes(`${v} }`),
492-
))
493-
.sort()
511+
.filter((v) => {
512+
const isUsed = state.usedValues.has(v)
513+
|| state.exportedValues?.has(v)
514+
|| v === state.defaultExportValue
515+
debugLog(undefined, 'import-filter', `Checking ${v}: used=${isUsed}`)
516+
return isUsed
517+
})
494518

495519
if (usedValues.length > 0) {
496-
imports.push(`import { ${usedValues.join(', ')} } from '${module}'`)
520+
if (!moduleImports.has(module)) {
521+
moduleImports.set(module, new Set())
522+
}
523+
usedValues.forEach(v => moduleImports.get(module)!.add(v))
524+
}
525+
}
526+
527+
// Generate value import statements
528+
for (const [module, values] of moduleImports) {
529+
const importParts = Array.from(values).map((value) => {
530+
const alias = importAliases.get(value)
531+
return alias ? `${value} as ${alias}` : value
532+
}).sort()
533+
534+
if (importParts.length > 0) {
535+
const importStatement = `import { ${importParts.join(', ')} } from '${module}'`
536+
if (!seenImports.has(importStatement)) {
537+
imports.push(importStatement)
538+
seenImports.add(importStatement)
539+
debugLog(undefined, 'import-add', `Added value import: ${importStatement}`)
540+
}
497541
}
498542
}
499543

@@ -555,19 +599,32 @@ function formatOutput(state: ProcessingState): string {
555599
// Deduplicate and format imports
556600
state.dtsLines
557601
.filter(line => line.startsWith('import'))
558-
.forEach(imp => imports.add(imp))
602+
.forEach(imp => imports.add(imp.replace(/;+$/, ''))) // Remove any existing semicolons
559603

560-
state.dtsLines = [
561-
...Array.from(imports),
604+
// Get all non-import lines
605+
const declarations = state.dtsLines
606+
.filter(line => !line.startsWith('import'))
607+
.map(line => line.replace(/;+$/, '')) // Clean up any multiple semicolons
608+
609+
// Add default exports from state.defaultExports
610+
const defaultExports = Array.from(state.defaultExports)
611+
.map(exp => exp.replace(/;+$/, '')) // Clean up any multiple semicolons
612+
613+
// Reconstruct the output with single semicolons where needed
614+
const output = [
615+
...Array.from(imports).map(imp => `${imp};`),
616+
'',
617+
...declarations.map(decl => decl.trim() !== '' ? `${decl};` : ''),
562618
'',
563-
...state.dtsLines.filter(line => !line.startsWith('import')),
619+
...defaultExports.map(exp => `${exp};`),
564620
]
565621

566622
// Remove comments and normalize whitespace
567-
return `${state.dtsLines
623+
return `${output
568624
.map(line => line.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, ''))
569625
.filter(Boolean)
570-
.join('\n')}\n`
626+
.join('\n')
627+
}\n`
571628
}
572629

573630
/**
@@ -630,8 +687,11 @@ function createImportTrackingState(): ImportTrackingState {
630687
return {
631688
typeImports: new Map(),
632689
valueImports: new Map(),
690+
valueAliases: new Map(),
633691
usedTypes: new Set(),
634692
usedValues: new Set(),
693+
exportedValues: null,
694+
defaultExportValue: null,
635695
}
636696
}
637697

@@ -1276,6 +1336,9 @@ function processDefaultExportBlock(cleanDeclaration: string, state: ProcessingSt
12761336
if (!cleanDeclaration.startsWith('export default'))
12771337
return false
12781338

1339+
const exportedValue = cleanDeclaration.replace(/^export\s+default\s+/, '').replace(/;$/, '')
1340+
state.importTracking.defaultExportValue = exportedValue
1341+
12791342
// Store the complete default export statement
12801343
const defaultExport = cleanDeclaration.endsWith(';')
12811344
? cleanDeclaration
@@ -1599,31 +1662,60 @@ function processSourceFile(content: string, state: ProcessingState): void {
15991662
/**
16001663
* Process imports and track their usage
16011664
*/
1602-
export function processImports(line: string, state: ImportTrackingState): void {
1603-
// Handle type imports
1604-
const typeImportMatch = line.match(/import\s+type\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/)
1665+
function processImports(line: string, state: ImportTrackingState): void {
1666+
debugLog(undefined, 'import-processing', `Processing import line: ${line}`)
1667+
1668+
// Initialize collections if they don't exist
1669+
if (!state.valueAliases)
1670+
state.valueAliases = new Map()
1671+
if (!state.exportedValues)
1672+
state.exportedValues = new Set()
1673+
if (!state.importSources)
1674+
state.importSources = new Map()
1675+
1676+
// Handle type imports - more specific regex to catch type imports with and without braces
1677+
const typeImportMatch = line.match(/import\s+type\s*(?:\{([^}]+)\}|([^;\s]+))\s*from\s*['"]([^'"]+)['"]/)
16051678
if (typeImportMatch) {
1606-
const [, names, module] = typeImportMatch
1679+
const [, bracedTypes, singleType, module] = typeImportMatch
1680+
const types = bracedTypes || singleType
1681+
debugLog(undefined, 'import-type', `Found type imports from ${module}: ${types}`)
1682+
16071683
if (!state.typeImports.has(module)) {
16081684
state.typeImports.set(module, new Set())
16091685
}
1610-
names.split(',').forEach((name) => {
1611-
const cleanName = name.trim().split(/\s+as\s+/).shift()! // Use shift() to get original name before 'as'
1612-
state.typeImports.get(module)!.add(cleanName)
1613-
})
1686+
1687+
if (types) {
1688+
types.split(',').forEach((type) => {
1689+
const [original, alias] = type.trim().split(/\s+as\s+/).map(n => n.trim())
1690+
state.typeImports.get(module)!.add(original)
1691+
if (alias) {
1692+
state.valueAliases.set(alias, original)
1693+
debugLog(undefined, 'import-alias', `Registered type alias: ${original} as ${alias}`)
1694+
}
1695+
})
1696+
}
16141697
return
16151698
}
16161699

1617-
// Handle value imports
1700+
// Handle value imports (rest of the code remains the same)
16181701
const valueImportMatch = line.match(/import\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/)
16191702
if (valueImportMatch) {
16201703
const [, names, module] = valueImportMatch
1704+
debugLog(undefined, 'import-value', `Found value imports from ${module}: ${names}`)
1705+
16211706
if (!state.valueImports.has(module)) {
16221707
state.valueImports.set(module, new Set())
16231708
}
1709+
16241710
names.split(',').forEach((name) => {
1625-
const cleanName = name.trim().split(/\s+as\s+/).shift()! // Use shift() to get original name before 'as'
1626-
state.valueImports.get(module)!.add(cleanName)
1711+
const [original, alias] = name.trim().split(/\s+as\s+/).map(n => n.trim())
1712+
state.valueImports.get(module)!.add(original)
1713+
state.importSources.set(original, module)
1714+
1715+
if (alias) {
1716+
state.valueAliases.set(alias, original)
1717+
debugLog(undefined, 'import-alias', `Registered value alias: ${original} as ${alias}`)
1718+
}
16271719
})
16281720
}
16291721
}
@@ -2073,35 +2165,6 @@ function processPropertyValue(value: string, indentLevel: number, state?: Proces
20732165
return 'unknown'
20742166
}
20752167

2076-
const REGEX = {
2077-
typePattern: /(?:typeof\s+)?([A-Z]\w*(?:<[^>]+>)?)|extends\s+([A-Z]\w*(?:<[^>]+>)?)/g,
2078-
} as const
2079-
2080-
/**
2081-
* Track used types in declarations
2082-
*/
2083-
function trackUsedTypes(content: string, usedTypes: Set<string>): void {
2084-
let match: any
2085-
while ((match = REGEX.typePattern.exec(content)) !== null) {
2086-
const type = match[1] || match[2]
2087-
if (type) {
2088-
const [baseType, ...genericParams] = type.split(/[<>]/)
2089-
if (baseType && /^[A-Z]/.test(baseType))
2090-
usedTypes.add(baseType)
2091-
2092-
if (genericParams.length > 0) {
2093-
genericParams.forEach((param: any) => {
2094-
const nestedTypes = param.split(/[,\s]/)
2095-
nestedTypes.forEach((t: any) => {
2096-
if (/^[A-Z]/.test(t))
2097-
usedTypes.add(t)
2098-
})
2099-
})
2100-
}
2101-
}
2102-
}
2103-
}
2104-
21052168
/**
21062169
* Track type usage in declarations
21072170
*/
@@ -2119,41 +2182,35 @@ function trackTypeUsage(content: string, state: ImportTrackingState): void {
21192182
/**
21202183
* Track value usage in declarations
21212184
*/
2122-
function trackValueUsage(content: string, state: ImportTrackingState, dtsLines?: string[]): void {
2185+
function trackValueUsage(content: string, state: ImportTrackingState): void {
2186+
// Track exports
2187+
const exportMatch = content.match(/export\s*\{([^}]+)\}/)
2188+
if (exportMatch) {
2189+
const exports = exportMatch[1].split(',').map(e => e.trim())
2190+
exports.forEach((e) => {
2191+
const [name] = e.split(/\s+as\s+/)
2192+
state.exportedValues.add(name.trim())
2193+
})
2194+
}
2195+
21232196
// Track values in declarations
21242197
const patterns = [
2125-
// Export statements in declarations
21262198
/export\s+declare\s+\{\s*([^}\s]+)(?:\s*,\s*[^}\s]+)*\s*\}/g,
2127-
// Declared exports
21282199
/export\s+declare\s+(?:const|function|class)\s+([a-zA-Z_$][\w$]*)/g,
2129-
// Direct exports
21302200
/export\s+\{\s*([^}\s]+)(?:\s*,\s*[^}\s]+)*\s*\}/g,
21312201
]
21322202

21332203
for (const pattern of patterns) {
21342204
let match
21352205
while ((match = pattern.exec(content)) !== null) {
21362206
const values = match[1].split(',').map(v => v.trim())
2137-
for (const value of values) {
2207+
values.forEach((value) => {
21382208
if (!['type', 'interface', 'declare', 'extends', 'implements', 'function', 'const', 'let', 'var'].includes(value)) {
21392209
state.usedValues.add(value)
21402210
}
2141-
}
2211+
})
21422212
}
21432213
}
2144-
2145-
// Track values in the final output lines if provided
2146-
if (dtsLines) {
2147-
dtsLines.forEach((line) => {
2148-
if (line.includes('declare') || line.includes('export')) {
2149-
// Look for exported values
2150-
const exportMatch = line.match(/(?:export|declare)\s+(?:const|function|class)\s+([a-zA-Z_$][\w$]*)/)
2151-
if (exportMatch) {
2152-
state.usedValues.add(exportMatch[1])
2153-
}
2154-
}
2155-
})
2156-
}
21572214
}
21582215

21592216
function debugLog(state: ProcessingState | undefined, category: string, message: string): void {

src/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ export interface ImportTrackingState {
8888
valueImports: Map<string, Set<string>> // module -> Set of value names
8989
usedTypes: Set<string> // All used type names
9090
usedValues: Set<string> // All used value names
91+
exportedValues: Set<string> // Values that are exported
92+
valueAliases: Map<string, string> // alias -> original name mapping
93+
importSources: Map<string, string> // name -> module mapping
94+
defaultExportValue?: string // The value being default exported
9195
}
9296

9397
export interface ProcessingState {

0 commit comments

Comments
 (0)