Skip to content

Commit 084920a

Browse files
committed
chore: wip
1 parent f25dd3d commit 084920a

File tree

3 files changed

+252
-14
lines changed

3 files changed

+252
-14
lines changed

src/extractor.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,11 @@ function buildFunctionSignature(node: ts.FunctionDeclaration): string {
207207
const name = getParameterName(param)
208208
const type = param.type?.getText() || 'any'
209209
const optional = param.questionToken || param.initializer ? '?' : ''
210+
const isRest = !!param.dotDotDotToken
211+
212+
if (isRest) {
213+
return `...${name}: ${type}`
214+
}
210215
return `${name}${optional}: ${type}`
211216
}).join(', ')
212217
result += `(${params})`
@@ -357,10 +362,45 @@ function getInterfaceBody(node: ts.InterfaceDeclaration): string {
357362
const paramName = param.name.getText()
358363
const paramType = param.type?.getText() || 'any'
359364
const optional = param.questionToken ? '?' : ''
365+
const isRest = !!param.dotDotDotToken
366+
367+
if (isRest) {
368+
return `...${paramName}: ${paramType}`
369+
}
360370
return `${paramName}${optional}: ${paramType}`
361371
}).join(', ')
362372
const returnType = member.type?.getText() || 'void'
363373
members.push(` ${name}(${params}): ${returnType}`)
374+
} else if (ts.isCallSignatureDeclaration(member)) {
375+
// Call signature: (param: type) => returnType
376+
const params = member.parameters.map(param => {
377+
const paramName = param.name.getText()
378+
const paramType = param.type?.getText() || 'any'
379+
const optional = param.questionToken ? '?' : ''
380+
const isRest = !!param.dotDotDotToken
381+
382+
if (isRest) {
383+
return `...${paramName}: ${paramType}`
384+
}
385+
return `${paramName}${optional}: ${paramType}`
386+
}).join(', ')
387+
const returnType = member.type?.getText() || 'void'
388+
members.push(` (${params}): ${returnType}`)
389+
} else if (ts.isConstructSignatureDeclaration(member)) {
390+
// Constructor signature: new (param: type) => returnType
391+
const params = member.parameters.map(param => {
392+
const paramName = param.name.getText()
393+
const paramType = param.type?.getText() || 'any'
394+
const optional = param.questionToken ? '?' : ''
395+
const isRest = !!param.dotDotDotToken
396+
397+
if (isRest) {
398+
return `...${paramName}: ${paramType}`
399+
}
400+
return `${paramName}${optional}: ${paramType}`
401+
}).join(', ')
402+
const returnType = member.type?.getText() || 'any'
403+
members.push(` new (${params}): ${returnType}`)
364404
}
365405
}
366406

src/processor.ts

Lines changed: 184 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1109,39 +1109,209 @@ function parseObjectProperties(content: string): Array<[string, string]> {
11091109
return properties
11101110
}
11111111

1112+
/**
1113+
* Find matching bracket for nested structures
1114+
*/
1115+
function findMatchingBracket(str: string, start: number, openChar: string, closeChar: string): number {
1116+
let depth = 0
1117+
for (let i = start; i < str.length; i++) {
1118+
if (str[i] === openChar) {
1119+
depth++
1120+
} else if (str[i] === closeChar) {
1121+
depth--
1122+
if (depth === 0) {
1123+
return i
1124+
}
1125+
}
1126+
}
1127+
return -1
1128+
}
1129+
11121130
/**
11131131
* Infer function type from function expression
11141132
*/
11151133
function inferFunctionType(value: string, inUnion: boolean = false): string {
1116-
// Arrow functions
1117-
if (value.includes('=>')) {
1118-
const arrowIndex = value.indexOf('=>')
1119-
let params = value.substring(0, arrowIndex).trim()
1134+
const trimmed = value.trim()
11201135

1121-
// Clean up params - remove extra parentheses if they exist
1136+
// Handle very complex function types early (but not function expressions)
1137+
if ((trimmed.length > 100 || (trimmed.match(/=>/g) || []).length > 2) && !trimmed.startsWith('function')) {
1138+
// Extract just the basic signature pattern
1139+
const genericMatch = trimmed.match(/^<[^>]+>/)
1140+
const generics = genericMatch ? genericMatch[0] : ''
1141+
1142+
// Look for first parameter pattern - need to find the complete parameter list
1143+
let paramStart = trimmed.indexOf('(')
1144+
if (paramStart !== -1) {
1145+
let paramEnd = findMatchingBracket(trimmed, paramStart, '(', ')')
1146+
if (paramEnd !== -1) {
1147+
const params = trimmed.substring(paramStart, paramEnd + 1)
1148+
const funcType = `${generics}${params} => any`
1149+
return inUnion ? `(${funcType})` : funcType
1150+
}
1151+
}
1152+
1153+
// Fallback if parameter extraction fails
1154+
const funcType = `${generics}(...args: any[]) => any`
1155+
return inUnion ? `(${funcType})` : funcType
1156+
}
1157+
1158+
// Handle async arrow functions
1159+
if (trimmed.startsWith('async ') && trimmed.includes('=>')) {
1160+
const asyncRemoved = trimmed.slice(5).trim() // Remove 'async '
1161+
const arrowIndex = asyncRemoved.indexOf('=>')
1162+
let params = asyncRemoved.substring(0, arrowIndex).trim()
1163+
let body = asyncRemoved.substring(arrowIndex + 2).trim()
1164+
1165+
// Clean up params
11221166
if (params === '()' || params === '') {
1123-
params = ''
1124-
} else if (params.startsWith('(') && params.endsWith(')')) {
1125-
// Keep the parentheses for parameters
1126-
params = params
1167+
params = '()'
1168+
} else if (!params.startsWith('(')) {
1169+
// Single parameter without parentheses
1170+
params = `(${params})`
1171+
}
1172+
1173+
// Try to infer return type from body
1174+
let returnType = 'unknown'
1175+
if (body.startsWith('{')) {
1176+
// Block body - can't easily infer return type
1177+
returnType = 'unknown'
11271178
} else {
1179+
// Expression body - try to infer
1180+
returnType = inferNarrowType(body, false)
1181+
}
1182+
1183+
const funcType = `${params} => Promise<${returnType}>`
1184+
return inUnion ? `(${funcType})` : funcType
1185+
}
1186+
1187+
// Regular arrow functions
1188+
if (trimmed.includes('=>')) {
1189+
// Handle generics at the beginning
1190+
let generics = ''
1191+
let remaining = trimmed
1192+
1193+
// Check for generics at the start
1194+
if (trimmed.startsWith('<')) {
1195+
const genericEnd = findMatchingBracket(trimmed, 0, '<', '>')
1196+
if (genericEnd !== -1) {
1197+
generics = trimmed.substring(0, genericEnd + 1)
1198+
remaining = trimmed.substring(genericEnd + 1).trim()
1199+
}
1200+
}
1201+
1202+
const arrowIndex = remaining.indexOf('=>')
1203+
if (arrowIndex === -1) {
1204+
// Fallback if no arrow found
1205+
const funcType = '() => unknown'
1206+
return inUnion ? `(${funcType})` : funcType
1207+
}
1208+
1209+
let params = remaining.substring(0, arrowIndex).trim()
1210+
let body = remaining.substring(arrowIndex + 2).trim()
1211+
1212+
// Handle explicit return type annotations in parameters
1213+
// Look for pattern like (param: Type): ReturnType
1214+
let explicitReturnType = ''
1215+
const returnTypeMatch = params.match(/\):\s*([^=]+)$/)
1216+
if (returnTypeMatch) {
1217+
explicitReturnType = returnTypeMatch[1].trim()
1218+
params = params.substring(0, params.lastIndexOf('):')) + ')'
1219+
}
1220+
1221+
// Clean up params
1222+
if (params === '()' || params === '') {
1223+
params = '()'
1224+
} else if (!params.startsWith('(')) {
11281225
// Single parameter without parentheses
11291226
params = `(${params})`
11301227
}
11311228

1132-
// Try to parse return type from the body
1133-
const funcType = `${params || '()'} => unknown`
1229+
// Try to infer return type from body
1230+
let returnType = 'unknown'
1231+
if (explicitReturnType) {
1232+
// Use explicit return type annotation
1233+
returnType = explicitReturnType
1234+
} else if (body.startsWith('{')) {
1235+
// Block body - can't easily infer return type
1236+
returnType = 'unknown'
1237+
} else if (body.includes('=>')) {
1238+
// This is a higher-order function returning another function
1239+
// Try to infer the return function type
1240+
const innerFuncType = inferFunctionType(body, false)
1241+
returnType = innerFuncType
1242+
} else {
1243+
// Expression body - try to infer
1244+
returnType = inferNarrowType(body, false)
1245+
}
11341246

1135-
// Add extra parentheses if this function is part of a union type
1247+
const funcType = `${generics}${params} => ${returnType}`
11361248
return inUnion ? `(${funcType})` : funcType
11371249
}
11381250

1139-
// Regular functions
1140-
if (value.startsWith('function') || value.startsWith('async function')) {
1251+
// Function expressions
1252+
if (trimmed.startsWith('function')) {
1253+
// Handle generics in function expressions like function* <T>(items: T[])
1254+
let generics = ''
1255+
let remaining = trimmed
1256+
1257+
// Look for generics after function keyword
1258+
const genericMatch = trimmed.match(/function\s*\*?\s*(<[^>]+>)/)
1259+
if (genericMatch) {
1260+
generics = genericMatch[1]
1261+
}
1262+
1263+
// Try to extract function signature
1264+
const funcMatch = trimmed.match(/function\s*(\*?)\s*(?:<[^>]+>)?\s*([^(]*)\(([^)]*)\)/)
1265+
if (funcMatch) {
1266+
const isGenerator = !!funcMatch[1]
1267+
const name = funcMatch[2].trim()
1268+
const params = funcMatch[3].trim()
1269+
1270+
let paramTypes = '(...args: any[])'
1271+
if (params) {
1272+
// Try to parse parameters
1273+
paramTypes = `(${params})`
1274+
} else {
1275+
paramTypes = '()'
1276+
}
1277+
1278+
if (isGenerator) {
1279+
// Try to extract return type from the function signature
1280+
const returnTypeMatch = trimmed.match(/:\s*Generator<([^>]+)>/)
1281+
if (returnTypeMatch) {
1282+
const generatorTypes = returnTypeMatch[1]
1283+
return inUnion ? `(${generics}${paramTypes} => Generator<${generatorTypes}>)` : `${generics}${paramTypes} => Generator<${generatorTypes}>`
1284+
}
1285+
return inUnion ? `(${generics}${paramTypes} => Generator<any, any, any>)` : `${generics}${paramTypes} => Generator<any, any, any>`
1286+
}
1287+
1288+
return inUnion ? `(${generics}${paramTypes} => unknown)` : `${generics}${paramTypes} => unknown`
1289+
}
1290+
11411291
const funcType = '(...args: any[]) => unknown'
11421292
return inUnion ? `(${funcType})` : funcType
11431293
}
11441294

1295+
// Higher-order functions (functions that return functions)
1296+
if (trimmed.includes('=>') && trimmed.includes('(') && trimmed.includes(')')) {
1297+
// For very complex function types, fall back to a simpler signature
1298+
if (trimmed.length > 100 || (trimmed.match(/=>/g) || []).length > 2) {
1299+
// Extract just the basic signature pattern
1300+
const genericMatch = trimmed.match(/^<[^>]+>/)
1301+
const generics = genericMatch ? genericMatch[0] : ''
1302+
1303+
// Look for parameter pattern
1304+
const paramMatch = trimmed.match(/\([^)]*\)/)
1305+
const params = paramMatch ? paramMatch[0] : '(...args: any[])'
1306+
1307+
const funcType = `${generics}${params} => any`
1308+
return inUnion ? `(${funcType})` : funcType
1309+
}
1310+
1311+
// This might be a higher-order function, try to preserve the structure
1312+
return inUnion ? `(${trimmed})` : trimmed
1313+
}
1314+
11451315
const funcType = '() => unknown'
11461316
return inUnion ? `(${funcType})` : funcType
11471317
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
export declare function withOptionalParams(required: string, optional?: number, defaultParam?: any): void;
2+
export declare function withRestParams(first: string, ...rest: number[]): number;
3+
export declare function withDestructuredParams({ name, age, props }: {
4+
name: string
5+
age?: number
6+
[key: string]: any
7+
}): void;
8+
export declare function withThisParam(this: { count: number }, increment: number): number;
9+
export declare function isString(value: unknown): value is string;
10+
export declare function assertDefined<T>(value: T | undefined): asserts value is T;
11+
export declare function simpleGenerator(): Generator<number, void, unknown>;
12+
export declare const arrowSimple: () => 'simple';
13+
export declare const arrowWithParams: (x: number, y: number) => unknown;
14+
export declare const arrowAsync: (url: string) => Promise<unknown>;
15+
export declare const arrowGeneric: <T extends object>(obj: T) => T;
16+
export declare const createMultiplier: (factor: number) => (value: number) => unknown;
17+
export declare const pipe: <T>(...fns: Array<(value: T) => T>) => any;
18+
export declare const methodDecorator: (target: any, propertyKey: string, descriptor: PropertyDescriptor) => unknown;
19+
export declare const generatorArrow: <T>(items: T[]) => Generator<T, void, unknown>;
20+
export declare interface ConstructorExample {
21+
new (name: string): { name: string }
22+
(name: string): string
23+
}
24+
export type SimpleFunction = () => void
25+
export type ParameterizedFunction = (a: string, b: number) => boolean
26+
export type GenericFunction = <T>(value: T) => T
27+
export type AsyncFunction = (id: string) => Promise<unknown>
28+
export type CallbackFunction = (error: Error | null, result?: unknown) => void

0 commit comments

Comments
 (0)