Skip to content

Commit 1bd6deb

Browse files
committed
chore: wip
1 parent b7db6c1 commit 1bd6deb

File tree

2 files changed

+125
-105
lines changed

2 files changed

+125
-105
lines changed

fixtures/output/function.d.ts

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,11 @@ export declare function fetchUsers(): Promise<ResponseData>;
44
export declare function getProduct(id: number): Promise<ApiResponse<Product>>;
55
export declare function authenticate(user: string, password: string): Promise<AuthResponse>;
66
export declare function dts(options?: DtsGenerationOption): BunPlugin;
7-
export declare function export();
8-
export declare function loadConfig <T extends Record<string, unknown>> ();
97
export declare function processData(data: string): string;
108
export declare function processData(data: number): number;
119
export declare function processData(data: boolean): boolean;
1210
export declare function processData <T extends object> (data: T): T;
1311
export declare function processData(data: unknown): unknown;
14-
export declare function export();
15-
export declare function complexAsyncGenerator(): any;
12+
declare const results: unknown;
1613
export declare function isUser(value: unknown): value is User;
1714
export declare function extractFunctionSignature(declaration: string): FunctionSignature;
18-
export declare function Pattern();
19-
export declare function \s* (\*);
20-
export declare function Match();
21-
export declare function Pattern) ();
22-
export declare function Match[4] ();
23-
export declare function createApi <T extends Record<string, (...args: any[]) => any> ();

src/extract.ts

Lines changed: 124 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -200,14 +200,14 @@ function extractBalancedSymbols(text: string, openSymbol: string, closeSymbol: s
200200
*/
201201
function extractFunctionSignature(declaration: string): FunctionSignature {
202202
// Remove comments and clean up the declaration
203-
const cleanDeclaration = removeLeadingComments(declaration).trim()
203+
const cleanDeclaration = removeLeadingComments(declaration).trim().replace(/^export\s+/, '') // Remove leading export if present
204204

205-
// Regex to match function declarations, including complex generics and export
206-
const functionPattern = /^\s*(export\s+)?(?:declare\s+)?(?:async\s+)?function\s*(\*)?\s*([^\s(<]+)/
205+
// Enhanced regex to capture async and generator functions
206+
const functionPattern = /^(?:async\s+)?function\s*(\*)?\s*([a-zA-Z_$][\w$]*)(?:<([^>]*)>)?\s*\((.*?)\)(?:\s*:\s*([^{;]+))?/s
207207
const functionMatch = cleanDeclaration.match(functionPattern)
208208

209209
if (!functionMatch) {
210-
console.error('Function name could not be extracted from declaration:', declaration)
210+
console.error('Function name could not be extracted from declaration:', cleanDeclaration)
211211
return {
212212
name: '',
213213
params: '',
@@ -216,46 +216,19 @@ function extractFunctionSignature(declaration: string): FunctionSignature {
216216
}
217217
}
218218

219-
const name = functionMatch[3]
220-
let rest = cleanDeclaration.slice(cleanDeclaration.indexOf(name) + name.length).trim()
219+
const [, isGenerator, name, generics = '', params = '', returnType = 'void'] = functionMatch
221220

222-
// Extract generics
223-
let generics = ''
224-
if (rest.startsWith('<')) {
225-
const genericsResult = extractBalancedSymbols(rest, '<', '>')
226-
if (genericsResult) {
227-
generics = genericsResult.content // This includes the angle brackets
228-
rest = genericsResult.rest.trim()
229-
}
230-
}
231-
232-
// Extract parameters
233-
let params = ''
234-
if (rest.startsWith('(')) {
235-
const paramsResult = extractBalancedSymbols(rest, '(', ')')
236-
if (paramsResult) {
237-
params = paramsResult.content.slice(1, -1).trim()
238-
rest = paramsResult.rest.trim()
239-
}
240-
}
241-
242-
// Extract return type
243-
let returnType = 'void'
244-
if (rest.startsWith(':')) {
245-
rest = rest.slice(1).trim()
246-
const returnTypeResult = extractReturnType(rest)
247-
debugLog(undefined, 'return-type', `Extracted return type: ${returnTypeResult?.returnType}`)
248-
if (returnTypeResult) {
249-
returnType = returnTypeResult.returnType.trim()
250-
rest = returnTypeResult.rest.trim()
251-
}
221+
// Handle async generator functions
222+
let finalReturnType = returnType.trim()
223+
if (isGenerator) {
224+
finalReturnType = `AsyncGenerator<${finalReturnType}, void, unknown>`
252225
}
253226

254227
return {
255228
name,
256229
params: cleanParameterTypes(params),
257-
returnType: normalizeType(returnType),
258-
generics,
230+
returnType: normalizeType(finalReturnType),
231+
generics: generics ? `<${generics}>` : '',
259232
}
260233
}
261234

@@ -700,7 +673,7 @@ function inferArrayType(value: string, state?: ProcessingState, indentLevel = 0)
700673
return `readonly [${tuples.join(', ')}]`
701674
}
702675

703-
const elementTypes = elements.map((element, index) => {
676+
const elementTypes = elements.map((element) => {
704677
const trimmed = element.trim()
705678
// debugLog(state, 'element-processing', `Processing element ${index}: "${trimmed}"`)
706679

@@ -907,6 +880,24 @@ export function isDefaultExport(line: string): boolean {
907880
}
908881

909882
function isDeclarationStart(line: string): boolean {
883+
// Skip regex patterns
884+
if (isRegexPattern(line))
885+
return false
886+
887+
const validIdentifierRegex = /^[a-z_$][\w$]*$/i
888+
889+
// Handle function declarations
890+
if (line.startsWith('function') || line.startsWith('async function')) {
891+
const nameMatch = line.match(/function\s+([^(<\s]+)/)
892+
return nameMatch ? validIdentifierRegex.test(nameMatch[1]) : false
893+
}
894+
895+
if (line.startsWith('export function') || line.startsWith('export async function')) {
896+
const nameMatch = line.match(/function\s+([^(<\s]+)/)
897+
return nameMatch ? validIdentifierRegex.test(nameMatch[1]) : false
898+
}
899+
900+
// Handle other declarations
910901
return (
911902
line.startsWith('export ')
912903
|| line.startsWith('interface ')
@@ -922,6 +913,23 @@ function isDeclarationStart(line: string): boolean {
922913
)
923914
}
924915

916+
function isRegexPattern(line: string): boolean {
917+
return (
918+
line.includes('\\')
919+
|| line.includes('[^')
920+
|| line.includes('(?:')
921+
|| line.includes('(?=')
922+
|| line.includes('(?!')
923+
|| line.includes('\\s*')
924+
|| line.includes('\\w+')
925+
|| line.includes('\\d+')
926+
|| line.includes('(?<')
927+
|| line.includes('(?!')
928+
|| line.includes('(?<=')
929+
|| line.includes('(?<!')
930+
)
931+
}
932+
925933
/**
926934
* Check if a given type string represents a function type
927935
*/
@@ -940,6 +948,18 @@ export function isDeclarationComplete(content: string | string[]): boolean {
940948
return /;\s*$/.test(trimmedContent) || /\}\s*$/.test(trimmedContent)
941949
}
942950

951+
function isVariableInsideFunction(line: string, state: ProcessingState): boolean {
952+
const trimmed = line.trim()
953+
return (
954+
state.currentScope === 'function'
955+
&& (trimmed.startsWith('const ')
956+
|| trimmed.startsWith('let ')
957+
|| trimmed.startsWith('var ')
958+
// Handle multiline variable declarations
959+
|| /^(?:const|let|var)\s+[a-zA-Z_$][\w$]*\s*(?::|=)/.test(trimmed))
960+
)
961+
}
962+
943963
function needsMultilineFormat(types: string[]): boolean {
944964
return types.some(type =>
945965
type.includes('\n')
@@ -986,20 +1006,24 @@ function processBlock(lines: string[], comments: string[], state: ProcessingStat
9861006
const declarationText = lines.join('\n')
9871007
const cleanDeclaration = removeLeadingComments(declarationText).trim()
9881008

989-
// Keep track of declaration for debugging
990-
state.debug.currentProcessing = cleanDeclaration
9911009
debugLog(state, 'block-processing', `Full block content:\n${cleanDeclaration}`)
9921010

9931011
if (!cleanDeclaration) {
9941012
debugLog(state, 'block-processing', 'Empty declaration block')
9951013
return
9961014
}
9971015

998-
// Try each processor in order
999-
if (processVariableBlock(cleanDeclaration, lines, state))
1016+
// Early check for variables inside functions
1017+
if (isVariableInsideFunction(cleanDeclaration, state)) {
1018+
debugLog(state, 'block-processing', 'Skipping variable declaration inside function')
10001019
return
1020+
}
1021+
1022+
// Try each processor in order
10011023
if (processFunctionBlock(cleanDeclaration, state))
10021024
return
1025+
if (processVariableBlock(cleanDeclaration, lines, state))
1026+
return
10031027
if (processInterfaceBlock(cleanDeclaration, declarationText, state))
10041028
return
10051029
if (processTypeBlock(cleanDeclaration, declarationText, state))
@@ -1013,7 +1037,6 @@ function processBlock(lines: string[], comments: string[], state: ProcessingStat
10131037
if (processModuleBlock(cleanDeclaration, declarationText, state))
10141038
return
10151039

1016-
// Log any unhandled declarations
10171040
debugLog(state, 'processing', `Unhandled declaration type: ${cleanDeclaration.split('\n')[0]}`)
10181041
}
10191042

@@ -1022,7 +1045,14 @@ function processVariableBlock(cleanDeclaration: string, lines: string[], state:
10221045
if (!variableMatch)
10231046
return false
10241047

1048+
// Double-check we're not inside a function
1049+
if (isVariableInsideFunction(cleanDeclaration, state)) {
1050+
debugLog(state, 'variable-processing', 'Skipping variable inside function')
1051+
return true // Return true because we handled it (by skipping)
1052+
}
1053+
10251054
const isExported = cleanDeclaration.startsWith('export')
1055+
10261056
// Only process variables at the top level
10271057
if (state.currentScope === 'top') {
10281058
const fullDeclaration = lines.join('\n')
@@ -1035,26 +1065,33 @@ function processVariableBlock(cleanDeclaration: string, lines: string[], state:
10351065
}
10361066

10371067
function processFunctionBlock(cleanDeclaration: string, state: ProcessingState): boolean {
1038-
if (!/^(export\s+)?(async\s+)?function/.test(cleanDeclaration))
1068+
// Check for function declarations including async and generator functions
1069+
if (!/^(?:export\s+)?(?:async\s+)?function\s*(\*)?\s*[a-zA-Z_$][\w$]*/.test(cleanDeclaration))
10391070
return false
10401071

10411072
debugLog(state, 'block-processing', 'Processing function declaration')
10421073
const isExported = cleanDeclaration.startsWith('export')
10431074

1044-
// Split block into separate function declarations if multiple exist
1045-
const declarations = cleanDeclaration.split(/export function|function/)
1075+
// Split function declarations - handle export separately
1076+
const declarations = cleanDeclaration
1077+
.replace(/^export\s+/, '') // Remove leading export once
1078+
.split(/\nexport\s+function|\nfunction/)
10461079
.filter(Boolean)
1047-
.map(d => (isExported ? `export function${d}` : `function${d}`))
1080+
.map(d => d.trim())
1081+
.filter(d => d.startsWith('function') || d.startsWith('async function'))
10481082

10491083
for (const declaration of declarations) {
10501084
// Process only the function signature for overloads and declarations
10511085
const signature = declaration.split('{')[0].trim()
10521086
if (signature) {
10531087
const processed = processFunction(signature, state.usedTypes, isExported)
1054-
if (processed)
1088+
if (processed) {
1089+
debugLog(state, 'function-processing', `Processed function: ${processed}`)
10551090
state.dtsLines.push(processed)
1091+
}
10561092
}
10571093
}
1094+
10581095
return true
10591096
}
10601097

@@ -1322,7 +1359,6 @@ function processSourceFile(content: string, state: ProcessingState): void {
13221359
let bracketDepth = 0
13231360
let parenDepth = 0
13241361
let inDeclaration = false
1325-
// Ensure currentScope is initialized
13261362
state.currentScope = 'top'
13271363

13281364
for (let i = 0; i < lines.length; i++) {
@@ -1353,9 +1389,8 @@ function processSourceFile(content: string, state: ProcessingState): void {
13531389
bracketDepth += (line.match(/\{/g) || []).length
13541390
bracketDepth -= (line.match(/\}/g) || []).length
13551391

1356-
// Check if we're entering a function scope
1392+
// Update scope
13571393
if (/^(export\s+)?(async\s+)?function/.test(trimmedLine)) {
1358-
debugLog(state, 'function-scope', `Entering function scope: ${trimmedLine}`)
13591394
state.currentScope = 'function'
13601395
}
13611396

@@ -1372,28 +1407,26 @@ function processSourceFile(content: string, state: ProcessingState): void {
13721407
bracketDepth += (line.match(/\{/g) || []).length
13731408
bracketDepth -= (line.match(/\}/g) || []).length
13741409

1375-
// Check if the declaration is complete
1376-
const isComplete = (
1377-
parenDepth === 0
1378-
&& bracketDepth === 0
1379-
&& (
1410+
// Check if declaration is complete
1411+
if (parenDepth === 0 && bracketDepth === 0 && !trimmedLine.endsWith(',')) {
1412+
const isComplete = (
13801413
trimmedLine.endsWith(';')
13811414
|| trimmedLine.endsWith('}')
13821415
|| (!trimmedLine.endsWith('{') && !trimmedLine.endsWith(','))
13831416
)
1384-
)
1385-
1386-
if (isComplete) {
1387-
processBlock(currentBlock, currentComments, state)
1388-
currentBlock = []
1389-
currentComments = []
1390-
inDeclaration = false
1391-
bracketDepth = 0
1392-
parenDepth = 0
13931417

1394-
// Reset scope after function ends
1395-
if (state.currentScope === 'function') {
1396-
state.currentScope = 'top' // Reset currentScope here
1418+
if (isComplete) {
1419+
processBlock(currentBlock, currentComments, state)
1420+
currentBlock = []
1421+
currentComments = []
1422+
inDeclaration = false
1423+
bracketDepth = 0
1424+
parenDepth = 0
1425+
1426+
// Reset scope after function ends
1427+
if (state.currentScope === 'function') {
1428+
state.currentScope = 'top'
1429+
}
13971430
}
13981431
}
13991432
}
@@ -1515,49 +1548,45 @@ function processVariable(declaration: string, isExported: boolean, state: Proces
15151548
* Process function declarations with overloads
15161549
*/
15171550
function processFunction(declaration: string, usedTypes?: Set<string>, isExported = true): string {
1518-
const cleanDeclaration = removeLeadingComments(declaration).trim()
1519-
const { name, params, returnType, generics } = extractFunctionSignature(cleanDeclaration)
1520-
debugLog(undefined, 'process-function', `Processing function declaration: ${name}(${params}): ${returnType}`)
1551+
// Clean up declaration first
1552+
const cleanDeclaration = removeLeadingComments(declaration).trim().replace(/^export\s+/, '') // Remove leading export if present
1553+
1554+
const signature = extractFunctionSignature(cleanDeclaration)
15211555

1522-
if (!name) {
1523-
console.error('Function name could not be extracted from declaration:', declaration)
1556+
if (!signature.name) {
1557+
debugLog(undefined, 'function-processing', `Failed to process function: ${cleanDeclaration}`)
15241558
return ''
15251559
}
15261560

1561+
// Handle async functions
1562+
const isAsync = cleanDeclaration.includes('async function')
1563+
let returnType = signature.returnType
1564+
if (isAsync && !returnType.startsWith('Promise<') && !returnType.startsWith('AsyncGenerator<')) {
1565+
returnType = `Promise<${returnType}>`
1566+
}
1567+
15271568
if (usedTypes) {
1528-
trackUsedTypes(`${generics} ${params} ${returnType}`, usedTypes)
1569+
trackUsedTypes(`${signature.generics} ${signature.params} ${returnType}`, usedTypes)
15291570
}
15301571

1531-
// Build the declaration string without function body
15321572
const parts = [
15331573
isExported ? 'export' : '',
15341574
'declare',
15351575
'function',
1536-
name,
1537-
generics,
1538-
`(${params})`,
1576+
signature.name,
1577+
signature.generics,
1578+
`(${signature.params})`,
1579+
':',
1580+
returnType,
15391581
]
15401582

1541-
if (returnType && returnType !== 'void') {
1542-
parts.push(':', returnType)
1543-
}
1544-
1545-
parts.push(';')
1546-
1547-
const ps = parts
1583+
return `${parts
15481584
.filter(Boolean)
15491585
.join(' ')
1550-
// Remove all spaces between name and parenthesis
15511586
.replace(/(\w+)\s+\(/g, '$1(')
1552-
// Ensure no space before colon, one space after
15531587
.replace(/\s*:\s*/g, ': ')
1554-
// Clean up spaces around semicolon
1555-
.replace(/\s*;/g, ';')
1556-
.trim()
1557-
1558-
debugLog(undefined, 'process-function', `Processed function declaration: ${ps}`)
1559-
1560-
return ps
1588+
.replace(/\s+;/g, ';')
1589+
.trim()};`
15611590
}
15621591

15631592
/**

0 commit comments

Comments
 (0)