diff --git a/fixtures/output/variable.d.ts b/fixtures/output/variable.d.ts index 48e2fa7..e906824 100644 --- a/fixtures/output/variable.d.ts +++ b/fixtures/output/variable.d.ts @@ -8,7 +8,7 @@ export declare const someObject: { someNumber: 1000; someBoolean: true; someFalse: false; - someFunction: () => void; + someFunction: () => unknown; anotherOne: () => unknown; someArray: Array<1 | 2 | 3>; someNestedArray: Array | Array<4 | 5 | 6 | 7 | 8 | 9 | 10>>; @@ -24,7 +24,7 @@ export declare const someObject: { Array<11 | 12 | 13> >; someOtherNestedArray: Array< - Array<'some text' | 2 | unknown | (() => void) | unknown> | + Array<'some text' | 2 | unknown | (() => unknown) | unknown> | Array<4 | 5 | 6 | 7 | 8 | 9 | 10> >; someComplexArray: Array< @@ -45,7 +45,7 @@ export declare const someObject: { }; otherKey: { nestedKey: unknown; - nestedKey2: () => void + nestedKey2: () => unknown } }; someNestedObjectArray: Array< @@ -89,13 +89,13 @@ export declare const complexObject: { }; utils: { formatters: { - date: (input: Date) => string; - currency: (amount: number, currency: string) => string + date: (input: Date) => unknown; + currency: (amount: number, currency: string) => unknown } } }; -export declare const methodDecorator: (...args: any[]) => unknown; -export declare const methodDecoratorWithExplicitType: (...args: any[]) => unknown; +export declare const methodDecorator: (target: any, propertyKey: string, descriptor: PropertyDescriptor) => unknown; +export declare const methodDecoratorWithExplicitType: (target: any, propertyKey: string, descriptor: PropertyDescriptor) => SomeType; export declare const CONFIG_MAP: { development: { features: { diff --git a/src/extract.ts b/src/extract.ts index e411a9d..ab4ad21 100644 --- a/src/extract.ts +++ b/src/extract.ts @@ -411,42 +411,106 @@ function extractFunctionType(value: string): string | null { debugLog('extract-function', `Extracting function type from: ${value}`) const cleanValue = value.trim() + let pos = 0 + const length = cleanValue.length + + // Check if the value starts with '(' (function expression) + if (!cleanValue.startsWith('(')) { + // Handle function keyword with explicit parameter types + const funcMatch = cleanValue.match(/^function\s*\w*\s*\((.*?)\)/s) + if (funcMatch) { + const [, params] = funcMatch + // Clean parameters while preserving type annotations + const cleanParams = cleanParameterTypes(params || '') + // Extract return type if available + const returnTypeMatch = cleanValue.match(/\):\s*([^{;]+)(?:[{;]|$)/) + const returnType = returnTypeMatch ? normalizeType(returnTypeMatch[1]) : 'unknown' + return `(${cleanParams}) => ${returnType}` + } + return null + } - // Handle explicit return type annotations - const returnTypeMatch = cleanValue.match(/\):\s*([^{;]+)(?:[{;]|$)/) - let returnType = returnTypeMatch ? normalizeType(returnTypeMatch[1]) : 'unknown' + // Now, handle arrow functions with possible return types + // Extract parameters using balanced parentheses + pos++ // Skip '(' + let depth = 1 + const paramsStart = pos + let inString = false + let stringChar = '' + for (; pos < length; pos++) { + const char = cleanValue[pos] + const prevChar = pos > 0 ? cleanValue[pos - 1] : '' - // Check value contents for return type inference - if (returnType === 'unknown') { - if (cleanValue.includes('toISOString()')) { - returnType = 'string' - } - else if (cleanValue.includes('Intl.NumberFormat') && cleanValue.includes('format')) { - returnType = 'string' + if (inString) { + if (char === stringChar && prevChar !== '\\') { + inString = false + } + else if (char === '\\') { + pos++ // Skip escaped character + } } - else if (cleanValue.includes('console.log')) { - returnType = 'void' + else { + if (char === '"' || char === '\'' || char === '`') { + inString = true + stringChar = char + } + else if (char === '(') { + depth++ + } + else if (char === ')') { + depth-- + if (depth === 0) { + break + } + } } } + if (depth !== 0) { + // Unbalanced parentheses + debugLog('extract-function', 'Unbalanced parentheses in function parameters') + return null + } + + const paramsEnd = pos + const params = cleanValue.slice(paramsStart, paramsEnd) + + pos++ // Move past ')' - // Handle arrow functions with explicit parameter types - const arrowMatch = value.match(/^\((.*?)\)\s*=>\s*(.*)/) - if (arrowMatch) { - const [, params] = arrowMatch - // Clean parameters while preserving type annotations - const cleanParams = cleanParameterTypes(params || '') - return `(${cleanParams}) => ${returnType}` + // Skip any whitespace + while (pos < length && /\s/.test(cleanValue[pos])) pos++ + + // Check for optional return type + let returnType = 'unknown' + if (cleanValue[pos] === ':') { + pos++ // Skip ':' + // Skip any whitespace + while (pos < length && /\s/.test(cleanValue[pos])) pos++ + const returnTypeStart = pos + // Read until '=>' or '{' + while (pos < length && !cleanValue.startsWith('=>', pos) && cleanValue[pos] !== '{') { + pos++ + } + const returnTypeEnd = pos + returnType = cleanValue.slice(returnTypeStart, returnTypeEnd).trim() } - // Handle function keyword with explicit parameter types - const funcMatch = value.match(/^function\s*\w*\s*\((.*?)\)/) - if (funcMatch) { - const [, params] = funcMatch - const cleanParams = cleanParameterTypes(params || '') - return `(${cleanParams}) => ${returnType}` + // Skip any whitespace + while (pos < length && /\s/.test(cleanValue[pos])) pos++ + + // Now, check for '=>' + if (cleanValue.startsWith('=>', pos)) { + pos += 2 + } + else { + // No '=>', invalid function expression + debugLog('extract-function', 'Function expression missing "=>"') + return null } - return null + // Now, construct the function type + const cleanParams = cleanParameterTypes(params || '') + debugLog('extract-function', `Extracted function type: (${cleanParams}) => ${returnType}`) + return `(${cleanParams}) => ${returnType}` } /**