Skip to content

Commit 64851ec

Browse files
committed
chore: wip
1 parent 01dc5cb commit 64851ec

File tree

4 files changed

+71
-54
lines changed

4 files changed

+71
-54
lines changed

fixtures/input/function.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -146,9 +146,9 @@ export function extractFunctionSignature(declaration: string): FunctionSignature
146146
}
147147
}
148148

149-
// TODO: Type Inference in Functions
150-
// export function createApi<T extends Record<string, (...args: any[]) => any>>(
151-
// endpoints: T
152-
// ): { [K in keyof T]: ReturnType<T[K]> extends Promise<infer R> ? R : ReturnType<T[K]> } {
153-
// return {} as any
154-
// }
149+
// Type Inference in Functions
150+
export function createApi<T extends Record<string, (...args: any[]) => any>>(
151+
endpoints: T
152+
): { [K in keyof T]: ReturnType<T[K]> extends Promise<infer R> ? R : ReturnType<T[K]> } {
153+
return {} as any
154+
}

fixtures/output/function.d.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import type { BunPlugin } from 'bun';
22
import type { DtsGenerationOption } from '@stacksjs/dtsx';
3-
export declare function fetchUsers(): Promise<ResponseData>;
4-
export declare function getProduct(id: number): Promise<ApiResponse<Product>>;
5-
export declare function authenticate(user: string, password: string): Promise<AuthResponse>;
3+
export declare function fetchUsers(): Promise<ResponseData;
4+
export declare function getProduct(id: number): Promise<ApiResponse<Product;
5+
export declare function authenticate(user: string, password: string): Promise<AuthResponse;
66
export declare function dts(options?: DtsGenerationOption): BunPlugin;
77
declare const cwd: unknown;
8+
export declare function loadConfig<T extends Record<string, unknown>>({ name, cwd, defaultConfig }: Options<T>): Promise<T;
89
declare const c: unknown;
910
declare const configPath: unknown;
1011
declare const importedConfig: unknown;
@@ -14,6 +15,7 @@ export declare function processData(data: number): number;
1415
export declare function processData(data: boolean): boolean;
1516
export declare function processData<T extends object>(data: T): T;
1617
export declare function processData(data: unknown): unknown;
18+
export declare function complexAsyncGenerator(): any;
1719
declare const results: unknown;
1820
export declare function isUser(value: unknown): value is User;
1921
export declare function extractFunctionSignature(declaration: string): FunctionSignature;
@@ -24,3 +26,4 @@ declare const name: unknown;
2426
declare const genericsResult: unknown;
2527
declare const paramsResult: unknown;
2628
declare const match: unknown;
29+
export declare function createApi<T extends Record<string,(...args: any[]) => any>();

fixtures/output/variable.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ export declare const complexArrays: {
8989
};
9090
export declare const complexObject: {
9191
handlers: {
92-
onSuccess: <T> (data: T) => Promise<void>;
92+
onSuccess: <T> (data: T) => Promise<unknown>;
9393
onError: (error: Error & { code?: number }) => never
9494
};
9595
utils: {

src/extract.ts

Lines changed: 58 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -199,9 +199,8 @@ function extractBalancedSymbols(text: string, openSymbol: string, closeSymbol: s
199199
* Extract complete function signature using regex
200200
*/
201201
function extractFunctionSignature(declaration: string): FunctionSignature {
202-
// Remove comments and clean up the declaration
203202
const cleanDeclaration = removeLeadingComments(declaration).trim()
204-
const functionPattern = /^\s*(export\s+)?(async\s+)?function\s*(?:(\*)\s*)?([^(<\s]+)/
203+
const functionPattern = /^\s*(export\s+)?(async\s+)?function\s*(\*)?\s*([a-zA-Z_$][\w$]*)/
205204
const functionMatch = cleanDeclaration.match(functionPattern)
206205

207206
if (!functionMatch) {
@@ -237,10 +236,10 @@ function extractFunctionSignature(declaration: string): FunctionSignature {
237236
}
238237
}
239238

240-
// Extract return type - keep it exactly as specified
239+
// Extract return type
241240
let returnType = 'void'
242241
if (rest.startsWith(':')) {
243-
const match = rest.match(/^:\s*([^{]+)/)
242+
const match = rest.match(/^:\s*([^=>{]+)/)
244243
if (match) {
245244
returnType = match[1].trim()
246245
}
@@ -828,43 +827,65 @@ function inferReturnType(value: string, declaration: string): string {
828827
const isAsync = declaration.startsWith('async ') || value.includes('async ') || (value.includes('=>') && value.includes('await'))
829828
debugLog(undefined, 'return-type', `Is async method: ${isAsync}`)
830829

831-
let effectiveReturnType = 'void'
830+
// Check for generator functions
831+
const isGenerator = declaration.includes('function*') || value.includes('function*')
832+
833+
let effectiveReturnType = 'unknown'
832834

833835
// Check for known return patterns
834836
if (value.includes('throw')) {
835837
effectiveReturnType = 'never'
836838
}
837-
else if (value.includes('toISOString()') || value.includes('toString()')) {
839+
else if (value.includes('toISOString()')) {
838840
effectiveReturnType = 'string'
839841
}
840842
else if (value.includes('Intl.NumberFormat') && value.includes('format')) {
841843
effectiveReturnType = 'string'
842844
}
843-
else if (value.match(/^\{\s*\/\/[^}]*\}$/) || value.match(/^\{\s*\}$/) || value.match(/^\{\s*\/\*[\s\S]*?\*\/\s*\}$/)) {
844-
effectiveReturnType = 'void'
845+
else if (value.includes('Promise.all')) {
846+
effectiveReturnType = 'Promise<unknown[]>'
847+
}
848+
else if (value.includes('fetch(')) {
849+
effectiveReturnType = 'Promise<unknown>'
845850
}
846851
else {
847852
// Check for return statements
848853
const returnMatch = value.match(/return\s+([^;\s]+)/)
849854
if (returnMatch) {
850855
const returnValue = returnMatch[1]
851-
if (/^['"`]/.test(returnValue))
856+
if (returnValue.includes('as ')) {
857+
const typeAssertionMatch = returnValue.match(/as\s+([^;\s]+)/)
858+
if (typeAssertionMatch) {
859+
effectiveReturnType = typeAssertionMatch[1]
860+
}
861+
}
862+
else if (/^['"`]/.test(returnValue)) {
852863
effectiveReturnType = 'string'
853-
else if (!Number.isNaN(Number(returnValue)))
864+
}
865+
else if (!Number.isNaN(Number(returnValue))) {
854866
effectiveReturnType = 'number'
855-
else if (returnValue === 'true' || returnValue === 'false')
867+
}
868+
else if (returnValue === 'true' || returnValue === 'false') {
856869
effectiveReturnType = 'boolean'
857-
else if (returnValue === 'null')
870+
}
871+
else if (returnValue === 'null') {
858872
effectiveReturnType = 'null'
859-
else if (returnValue === 'undefined')
873+
}
874+
else if (returnValue === 'undefined') {
860875
effectiveReturnType = 'undefined'
861-
else effectiveReturnType = 'unknown'
876+
}
877+
else {
878+
effectiveReturnType = 'unknown'
879+
}
862880
}
863881
}
864882

865-
// Wrap in Promise for async functions
866-
if (isAsync && !effectiveReturnType.includes('Promise')) {
867-
debugLog(undefined, 'return-type', `Wrapping ${effectiveReturnType} in Promise for async method`)
883+
// Handle generators
884+
if (isGenerator) {
885+
effectiveReturnType = `Generator<unknown, ${effectiveReturnType}, unknown>`
886+
}
887+
// Handle async functions
888+
else if (isAsync && !effectiveReturnType.includes('Promise')) {
868889
effectiveReturnType = `Promise<${effectiveReturnType}>`
869890
}
870891

@@ -921,7 +942,7 @@ export function isDefaultExport(line: string): boolean {
921942
return line.trim().startsWith('export default')
922943
}
923944

924-
function isDeclarationStart(line: string): boolean {
945+
export function isDeclarationStart(line: string): boolean {
925946
return (
926947
line.startsWith('export ')
927948
|| line.startsWith('interface ')
@@ -933,6 +954,7 @@ function isDeclarationStart(line: string): boolean {
933954
|| line.startsWith('declare module')
934955
|| /^export\s+(?:interface|type|const|function|async\s+function)/.test(line)
935956
|| line.startsWith('export async function')
957+
|| line.startsWith('export function') // Added this line
936958
)
937959
}
938960

@@ -1045,7 +1067,7 @@ export function processBlock(lines: string[], comments: string[], state: Process
10451067
return
10461068
}
10471069

1048-
if (cleanDeclaration.startsWith('function') || cleanDeclaration.startsWith('export function')) {
1070+
if (/^(export\s+)?(async\s+)?function\s*(\*)?/.test(cleanDeclaration)) {
10491071
const isExported = cleanDeclaration.startsWith('export')
10501072
state.dtsLines.push(processFunction(declarationText, state.usedTypes, isExported))
10511073
return
@@ -1217,16 +1239,12 @@ function processSourceFile(content: string, state: ProcessingState): void {
12171239
const trimmedLine = line.trim()
12181240

12191241
// Track comments
1220-
if (trimmedLine.startsWith('/*')) {
1221-
currentComments.push(line)
1222-
continue
1223-
}
1224-
if (trimmedLine.startsWith('//')) {
1242+
if (trimmedLine.startsWith('/*') || trimmedLine.startsWith('//')) {
12251243
currentComments.push(line)
12261244
continue
12271245
}
12281246

1229-
// Track brackets and parentheses for nesting depth
1247+
// Start of a new declaration
12301248
if (isDeclarationStart(trimmedLine)) {
12311249
if (inDeclaration && currentBlock.length > 0) {
12321250
processBlock(currentBlock, currentComments, state)
@@ -1238,7 +1256,7 @@ function processSourceFile(content: string, state: ProcessingState): void {
12381256
inDeclaration = true
12391257
currentBlock = [line]
12401258

1241-
// Initialize depths for the first line
1259+
// Update depths
12421260
parenDepth += (line.match(/\(/g) || []).length
12431261
parenDepth -= (line.match(/\)/g) || []).length
12441262
bracketDepth += (line.match(/\{/g) || []).length
@@ -1247,7 +1265,7 @@ function processSourceFile(content: string, state: ProcessingState): void {
12471265
continue
12481266
}
12491267

1250-
// If we're in a declaration, track the nesting
1268+
// If in a declaration, collect lines
12511269
if (inDeclaration) {
12521270
currentBlock.push(line)
12531271

@@ -1260,16 +1278,14 @@ function processSourceFile(content: string, state: ProcessingState): void {
12601278
// Check if the declaration is complete
12611279
const isComplete = (
12621280
parenDepth === 0
1263-
&& bracketDepth === 0 && (
1281+
&& bracketDepth === 0
1282+
&& (
12641283
trimmedLine.endsWith(';')
12651284
|| trimmedLine.endsWith('}')
1266-
|| trimmedLine.endsWith(',')
1267-
|| trimmedLine.match(/\bas\s+const[,;]?$/)
1285+
|| (!trimmedLine.endsWith('{') && !trimmedLine.endsWith(',')) // Function overloads
12681286
)
12691287
)
12701288

1271-
debugLog(state, 'source-processing', `Line "${trimmedLine}": parenDepth=${parenDepth}, bracketDepth=${bracketDepth}, complete=${isComplete}`)
1272-
12731289
if (isComplete) {
12741290
processBlock(currentBlock, currentComments, state)
12751291
currentBlock = []
@@ -1401,17 +1417,13 @@ export function processFunction(
14011417
usedTypes?: Set<string>,
14021418
isExported = true,
14031419
): string {
1404-
// Remove comments from the declaration for parsing
14051420
const cleanDeclaration = removeLeadingComments(declaration).trim()
14061421

1407-
const {
1408-
name,
1409-
params,
1410-
returnType,
1411-
generics,
1412-
} = extractFunctionSignature(cleanDeclaration)
1422+
// Determine if the function has a body
1423+
const hasBody = /\{[\s\S]*\}$/.test(cleanDeclaration)
1424+
1425+
const { name, params, returnType, generics } = extractFunctionSignature(cleanDeclaration)
14131426

1414-
// Track used types if provided
14151427
if (usedTypes) {
14161428
trackUsedTypes(`${generics} ${params} ${returnType}`, usedTypes)
14171429
}
@@ -1424,15 +1436,17 @@ export function processFunction(
14241436
name,
14251437
generics,
14261438
`(${params})`,
1427-
':',
1428-
returnType,
1429-
';',
14301439
]
14311440

1441+
if (returnType && returnType !== 'void') {
1442+
parts.push(':', returnType)
1443+
}
1444+
1445+
parts.push(';')
1446+
14321447
return parts
14331448
.filter(Boolean)
14341449
.join(' ')
1435-
// Include ':' in the character classes to handle spacing around colons
14361450
.replace(/\s+([<>(),;:])/g, '$1')
14371451
.replace(/([<>(),;:])\s+/g, '$1 ')
14381452
.replace(/\s{2,}/g, ' ')

0 commit comments

Comments
 (0)