Skip to content

Commit 53806e8

Browse files
committed
chore: wip
1 parent 5e4cad4 commit 53806e8

File tree

3 files changed

+98
-9
lines changed

3 files changed

+98
-9
lines changed

src/processor.ts

Lines changed: 91 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,35 @@ export function processFunctionDeclaration(decl: Declaration): string {
354354
return decl.text
355355
}
356356

357+
/**
358+
* Check if a type annotation is a generic/broad type that should be replaced with narrow inference
359+
*/
360+
function isGenericType(typeAnnotation: string): boolean {
361+
const trimmed = typeAnnotation.trim()
362+
363+
// Generic types that are less specific than narrow inference
364+
if (trimmed === 'any' || trimmed === 'object' || trimmed === 'unknown') {
365+
return true
366+
}
367+
368+
// Record types like Record<string, string>, Record<string, any>, etc.
369+
if (trimmed.startsWith('Record<') && trimmed.endsWith('>')) {
370+
return true
371+
}
372+
373+
// Array types like Array<any>, Array<string>, etc. (but not specific tuples)
374+
if (trimmed.startsWith('Array<') && trimmed.endsWith('>')) {
375+
return true
376+
}
377+
378+
// Object types like { [key: string]: any }
379+
if (trimmed.match(/^\{\s*\[.*\]:\s*(any|string|number|unknown)\s*\}$/)) {
380+
return true
381+
}
382+
383+
return false
384+
}
385+
357386
/**
358387
* Process variable declaration to DTS format
359388
*/
@@ -1135,7 +1164,7 @@ function parseObjectProperties(content: string): Array<[string, string]> {
11351164
inKey = false
11361165
depth = 1 // We're now inside the method definition
11371166
} else if (char === ',' && depth === 0) {
1138-
if (currentKey && current.trim()) {
1167+
if (currentKey && current.trim()) {
11391168
// Clean method signatures before storing
11401169
let value = current.trim()
11411170

@@ -1236,6 +1265,51 @@ function findMatchingBracket(str: string, start: number, openChar: string, close
12361265
return -1
12371266
}
12381267

1268+
/**
1269+
* Find the main arrow (=>) in a function, ignoring nested arrows in parameter types
1270+
*/
1271+
function findMainArrowIndex(str: string): number {
1272+
let parenDepth = 0
1273+
let bracketDepth = 0
1274+
let inString = false
1275+
let stringChar = ''
1276+
1277+
for (let i = 0; i < str.length - 1; i++) {
1278+
const char = str[i]
1279+
const nextChar = str[i + 1]
1280+
const prevChar = i > 0 ? str[i - 1] : ''
1281+
1282+
// Handle string literals
1283+
if (!inString && (char === '"' || char === "'" || char === '`')) {
1284+
inString = true
1285+
stringChar = char
1286+
} else if (inString && char === stringChar && prevChar !== '\\') {
1287+
inString = false
1288+
}
1289+
1290+
if (!inString) {
1291+
// Track nesting depth - only parentheses and square brackets
1292+
// Don't track < > as they can be comparison operators or part of generics
1293+
if (char === '(') {
1294+
parenDepth++
1295+
} else if (char === ')') {
1296+
parenDepth--
1297+
} else if (char === '[') {
1298+
bracketDepth++
1299+
} else if (char === ']') {
1300+
bracketDepth--
1301+
}
1302+
1303+
// Look for arrow at depth 0 (not nested inside parentheses or brackets)
1304+
if (char === '=' && nextChar === '>' && parenDepth === 0 && bracketDepth === 0) {
1305+
return i
1306+
}
1307+
}
1308+
}
1309+
1310+
return -1
1311+
}
1312+
12391313
/**
12401314
* Infer function type from function expression
12411315
*/
@@ -1297,7 +1371,8 @@ function inferFunctionType(value: string, inUnion: boolean = false): string {
12971371
}
12981372
}
12991373

1300-
const arrowIndex = remaining.indexOf('=>')
1374+
// Find the main arrow (not nested ones inside parameter types)
1375+
const arrowIndex = findMainArrowIndex(remaining)
13011376
if (arrowIndex === -1) {
13021377
// Fallback if no arrow found
13031378
const funcType = '() => unknown'
@@ -1337,9 +1412,20 @@ function inferFunctionType(value: string, inUnion: boolean = false): string {
13371412
returnType = 'unknown'
13381413
} else if (body.includes('=>')) {
13391414
// This is a higher-order function returning another function
1340-
// Try to infer the return function type
1341-
const innerFuncType = inferFunctionType(body, false)
1342-
returnType = innerFuncType
1415+
// For complex nested functions, try to extract just the outer function signature
1416+
const outerFuncMatch = body.match(/^\s*\(([^)]*)\)\s*=>/);
1417+
if (outerFuncMatch) {
1418+
const outerParams = outerFuncMatch[1].trim();
1419+
// For functions like pipe that transform T => T, infer the return type from generics
1420+
if (generics.includes('T') && outerParams.includes('T')) {
1421+
returnType = `(${outerParams}) => T`;
1422+
} else {
1423+
returnType = `(${outerParams}) => any`;
1424+
}
1425+
} else {
1426+
// Fallback for complex cases
1427+
returnType = 'any';
1428+
}
13431429
} else {
13441430
// Expression body - try to infer, but be conservative in union contexts
13451431
if (inUnion) {

test/fixtures/output/function-types.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ export declare const arrowSimple: () => 'simple';
1313
export declare const arrowWithParams: (x: number, y: number) => unknown;
1414
export declare const arrowAsync: (url: string) => Promise<unknown>;
1515
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;
16+
export declare const createMultiplier: (factor: number) => (value: number) => any;
17+
export declare const pipe: <T>(...fns: Array<(value: T) => T>) => (value: T) => T;
1818
export declare const methodDecorator: (target: any, propertyKey: string, descriptor: PropertyDescriptor) => unknown;
1919
export declare const generatorArrow: <T>(items: T[]) => Generator<T, void, unknown>;
2020
export declare interface ConstructorExample {

test/fixtures/output/variable.d.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
export declare const conf: { [key: string]: string };
1+
export declare const conf: {
2+
apiUrl: 'https://api.stacksjs.org';
3+
timeout: '5000'
4+
};
25
export declare let test: 'test';
36
export declare var helloWorld: 'Hello World';
47
export declare const someObject: {
@@ -40,7 +43,7 @@ export declare const complexArrays: {
4043
readonly [1, 'string', true] |
4144
readonly ['literal', 42, false]
4245
];
43-
// TODO: (get this part to generate correctly mixedArrays: [ new Date(), Promise.resolve('async'), ()) => unknown
46+
// TODO: () => unknown
4447
};
4548
export declare const complexObject: {
4649
handlers: {

0 commit comments

Comments
 (0)