Skip to content

Commit 928842d

Browse files
committed
chore: wip
1 parent 8dbb04c commit 928842d

File tree

2 files changed

+202
-38
lines changed

2 files changed

+202
-38
lines changed

fixtures/output/example-0001.d.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,12 @@ transform: (input: T[K]) => string
143143
nested: Array<Partial<T>>
144144
}
145145

146-
export declare type ComplexUnionIntersection =;
146+
export declare type ComplexUnionIntersection =
147+
| (User & { role: 'admin' })
148+
| (Product & { category: string })
149+
& {
150+
metadata: Record<string, unknown>
151+
}
147152

148153
export * from './extract';
149154
export * from './generate';

src/extract.ts

Lines changed: 196 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -121,31 +121,20 @@ export interface PropertyInfo {
121121
*/
122122
interface ProcessingState {
123123
dtsLines: string[]
124-
/** Import statements */
125124
imports: string[]
126-
/** Set of types used in declarations */
127125
usedTypes: Set<string>
128-
/** Map of type names to their source modules */
129126
typeSources: Map<string, string>
130-
/** Default export declaration */
131127
defaultExport: string
132-
/** Current declaration being processed */
133128
currentDeclaration: string
134-
/** Last processed JSDoc comment block */
135129
lastCommentBlock: string
136-
/** Bracket nesting level counter */
137130
bracketCount: number
138-
/** Flag for multi-line declarations */
139131
isMultiLineDeclaration: boolean
140-
/** Map of module imports with metadata */
141132
moduleImports: Map<string, ImportInfo>
142-
/** Map of available type names */
143133
availableTypes: Map<string, string>
144-
/** Map of available value names */
145134
availableValues: Map<string, string>
146135
currentIndentation: string
147136
declarationBuffer: {
148-
type: 'interface' | 'type' | 'const' | 'function'
137+
type: 'interface' | 'type' | 'const' | 'function' | 'import' | 'export'
149138
indent: string
150139
lines: string[]
151140
comments: string[]
@@ -499,9 +488,20 @@ function processDeclarationBuffer(
499488
state: ProcessingState,
500489
isExported: boolean,
501490
): string {
502-
const declaration = buffer.lines.join('\n')
503-
const cleaned = cleanDeclaration(declaration)
491+
const content = buffer.lines.join('\n')
504492

493+
// Skip processing for export * statements
494+
if (content.trim().startsWith('export *')) {
495+
return content
496+
}
497+
498+
// Skip processing for export type {} statements
499+
if (content.trim().startsWith('export type {')) {
500+
return content
501+
}
502+
503+
// Process regular declarations
504+
const cleaned = cleanDeclaration(content)
505505
switch (buffer.type) {
506506
case 'interface':
507507
return processInterfaceDeclaration(cleaned, isExported)
@@ -512,8 +512,72 @@ function processDeclarationBuffer(
512512
case 'function':
513513
return processFunctionDeclaration(cleaned, state.usedTypes, isExported)
514514
default:
515-
return declaration
515+
return content
516+
}
517+
}
518+
519+
function processDeclarationBlock(lines: string[], comments: string[], state: ProcessingState): void {
520+
const declaration = lines.join('\n')
521+
const trimmed = declaration.trim()
522+
523+
if (!trimmed || trimmed.startsWith('//'))
524+
return
525+
526+
// Keep original indentation
527+
const indentMatch = lines[0].match(/^(\s*)/)
528+
const baseIndent = indentMatch ? indentMatch[1] : ''
529+
530+
if (comments.length > 0) {
531+
state.dtsLines.push(...comments)
532+
}
533+
534+
if (trimmed.startsWith('import')) {
535+
// Imports are handled separately in the first pass
536+
return
537+
}
538+
539+
if (trimmed.startsWith('export * from')) {
540+
state.dtsLines.push(declaration)
541+
return
542+
}
543+
544+
if (trimmed.startsWith('export type {')) {
545+
state.dtsLines.push(declaration)
546+
return
547+
}
548+
549+
if (trimmed.startsWith('export {')) {
550+
state.dtsLines.push(declaration)
551+
return
552+
}
553+
554+
if (trimmed.startsWith('interface') || trimmed.startsWith('export interface')) {
555+
const processed = processInterfaceDeclaration(declaration, trimmed.startsWith('export'))
556+
state.dtsLines.push(processed)
557+
return
558+
}
559+
560+
if (trimmed.startsWith('type') || trimmed.startsWith('export type')) {
561+
const processed = processTypeDeclaration(declaration, trimmed.startsWith('export'))
562+
state.dtsLines.push(processed)
563+
return
564+
}
565+
566+
if (trimmed.startsWith('const') || trimmed.startsWith('export const')) {
567+
const processed = processConstDeclaration(declaration, trimmed.startsWith('export'))
568+
state.dtsLines.push(processed)
569+
return
570+
}
571+
572+
if (trimmed.startsWith('function') || trimmed.startsWith('export function')
573+
|| trimmed.startsWith('async function') || trimmed.startsWith('export async function')) {
574+
const processed = processFunctionDeclaration(declaration, state.usedTypes, trimmed.startsWith('export'))
575+
state.dtsLines.push(processed)
576+
return
516577
}
578+
579+
// Default case: preserve the declaration as-is
580+
state.dtsLines.push(declaration)
517581
}
518582

519583
/**
@@ -530,8 +594,7 @@ function processConstDeclaration(declaration: string, isExported = true): string
530594
const typeMatch = firstLine.match(/const\s+([^:]+):\s*([^=]+)\s*=/)
531595
if (typeMatch) {
532596
const [, name, type] = typeMatch
533-
// When there's an explicit type annotation, only use the type
534-
return `${indent}declare const ${name.trim()}: ${type.trim()};`
597+
return `${isExported ? 'export ' : ''}declare const ${name.trim()}: ${type.trim()};`
535598
}
536599

537600
// No type annotation, extract name and infer type
@@ -551,9 +614,33 @@ function processConstDeclaration(declaration: string, isExported = true): string
551614
return `${isExported ? 'export ' : ''}declare const ${name}: {\n${propertyStrings}\n};`
552615
}
553616

617+
// Handle simple value assignments
618+
const valueMatch = firstLine.match(/=\s*(.+)$/)
619+
if (valueMatch) {
620+
const value = valueMatch[1].trim()
621+
const inferredType = inferValueType(value)
622+
return `${isExported ? 'export ' : ''}declare const ${name}: ${inferredType};`
623+
}
624+
554625
return declaration
555626
}
556627

628+
function inferValueType(value: string): string {
629+
if (value.startsWith('{'))
630+
return 'Record<string, unknown>'
631+
if (value.startsWith('['))
632+
return 'unknown[]'
633+
if (value.startsWith('\'') || value.startsWith('"'))
634+
return 'string'
635+
if (!Number.isNaN(Number(value)))
636+
return 'number'
637+
if (value === 'true' || value === 'false')
638+
return 'boolean'
639+
if (value.includes('=>'))
640+
return '(...args: any[]) => unknown'
641+
return 'unknown'
642+
}
643+
557644
/**
558645
* Format nested properties with proper indentation
559646
*/
@@ -802,15 +889,33 @@ function isDeclarationStart(line: string): boolean {
802889

803890
function isDeclarationComplete(lines: string[]): boolean {
804891
let bracketCount = 0
892+
let inString = false
893+
let stringChar = ''
894+
805895
for (const line of lines) {
806896
for (const char of line) {
807-
if (char === '{')
808-
bracketCount++
809-
if (char === '}')
810-
bracketCount--
897+
// Handle string content
898+
if ((char === '"' || char === '\'') && !inString) {
899+
inString = true
900+
stringChar = char
901+
}
902+
else if (inString && char === stringChar) {
903+
inString = false
904+
continue
905+
}
906+
907+
if (!inString) {
908+
if (char === '{' || char === '(')
909+
bracketCount++
910+
if (char === '}' || char === ')')
911+
bracketCount--
912+
}
811913
}
812914
}
813-
return bracketCount === 0
915+
916+
// Also check for single-line declarations
917+
const lastLine = lines[lines.length - 1].trim()
918+
return bracketCount === 0 && (lastLine.endsWith(';') || lastLine.endsWith('}'))
814919
}
815920

816921
/**
@@ -1066,17 +1171,25 @@ function processInterfaceDeclaration(declaration: string, isExported = true): st
10661171
]
10671172

10681173
// Add members with preserved indentation
1174+
let seenContent = false
10691175
for (let i = 1; i < lines.length - 1; i++) {
10701176
const line = lines[i]
10711177
const content = line.trim()
10721178
if (content) {
1179+
seenContent = true
10731180
processedLines.push(`${memberIndent}${content}`)
10741181
}
10751182
}
10761183

1184+
// If no content was found, add a newline for better formatting
1185+
if (!seenContent) {
1186+
processedLines.push('')
1187+
}
1188+
10771189
processedLines.push(`${baseIndent}}`)
10781190
return processedLines.join('\n')
10791191
}
1192+
10801193
/**
10811194
* Process type declarations
10821195
*/
@@ -1118,30 +1231,76 @@ function processTypeDeclaration(declaration: string, isExported = true): string
11181231
}
11191232

11201233
function processSourceFile(content: string, state: ProcessingState): void {
1121-
console.log('Processing source file...')
1122-
11231234
const lines = content.split('\n')
1124-
let lineNumber = 0
1235+
1236+
// First pass: collect imports
1237+
const imports = lines.filter(line => line.trim().startsWith('import')).join('\n')
1238+
if (imports) {
1239+
state.imports = processImports(imports.split('\n'), state.usedTypes)
1240+
}
1241+
1242+
// Second pass: process everything else
1243+
let currentBlock: string[] = []
1244+
let currentComments: string[] = []
1245+
let isInMultilineDeclaration = false
11251246

11261247
for (const line of lines) {
1127-
lineNumber++
1128-
try {
1129-
// Clean the line before processing
1130-
const cleaned = cleanDeclaration(line)
1131-
1132-
// Check if line needs export
1133-
if (needsExport(cleaned)) {
1134-
console.log(`Line ${lineNumber}: Processing exportable declaration:`, cleaned)
1248+
const trimmedLine = line.trim()
1249+
1250+
// Skip empty lines between declarations
1251+
if (!trimmedLine && !isInMultilineDeclaration) {
1252+
if (currentBlock.length > 0) {
1253+
processDeclarationBlock(currentBlock, currentComments, state)
1254+
currentBlock = []
1255+
currentComments = []
1256+
}
1257+
continue
1258+
}
1259+
1260+
// Handle comments
1261+
if (isCommentLine(trimmedLine)) {
1262+
if (!isInMultilineDeclaration) {
1263+
if (trimmedLine.startsWith('/**')) {
1264+
currentComments = []
1265+
}
1266+
currentComments.push(line)
11351267
}
1268+
else {
1269+
currentBlock.push(line)
1270+
}
1271+
continue
1272+
}
1273+
1274+
// Track multiline declarations
1275+
if (!isInMultilineDeclaration && (trimmedLine.includes('{') || trimmedLine.includes('('))) {
1276+
isInMultilineDeclaration = true
1277+
}
1278+
1279+
currentBlock.push(line)
1280+
1281+
if (isInMultilineDeclaration) {
1282+
const openCount = (line.match(/[{(]/g) || []).length
1283+
const closeCount = (line.match(/[})]/g) || []).length
1284+
state.bracketCount += openCount - closeCount
11361285

1137-
processDeclarationLine(line, state)
1286+
if (state.bracketCount === 0) {
1287+
isInMultilineDeclaration = false
1288+
processDeclarationBlock(currentBlock, currentComments, state)
1289+
currentBlock = []
1290+
currentComments = []
1291+
}
11381292
}
1139-
catch (error) {
1140-
console.error(`Error processing line ${lineNumber}:`, { line, error })
1293+
else if (!trimmedLine.endsWith(',')) {
1294+
processDeclarationBlock(currentBlock, currentComments, state)
1295+
currentBlock = []
1296+
currentComments = []
11411297
}
11421298
}
11431299

1144-
console.log('Finished processing source file')
1300+
// Process any remaining block
1301+
if (currentBlock.length > 0) {
1302+
processDeclarationBlock(currentBlock, currentComments, state)
1303+
}
11451304
}
11461305

11471306
/**

0 commit comments

Comments
 (0)