@@ -219,6 +219,11 @@ export function inferNarrowType(value: unknown, isConst: boolean = false, inUnio
219219 return inferObjectType ( trimmed , isConst , _depth + 1 )
220220 }
221221
222+ // Class expressions — extract the class name and use typeof
223+ if ( trimmed . startsWith ( 'class ' ) || trimmed . startsWith ( 'class{' ) ) {
224+ return inferClassExpressionType ( trimmed )
225+ }
226+
222227 // New expressions (check before function expressions since `new X(() => {})` contains `=>`)
223228 if ( trimmed . startsWith ( 'new ' ) ) {
224229 return inferNewExpressionType ( trimmed )
@@ -312,36 +317,66 @@ function inferTemplateLiteralType(value: string, isConst: boolean): string {
312317 */
313318function inferNewExpressionType ( value : string ) : string {
314319 // Extract class name after 'new ' — must start with uppercase A-Z
320+ // Supports dotted names like Intl.NumberFormat
315321 let i = 4 // skip 'new '
316322 while ( i < value . length && value . charCodeAt ( i ) <= 32 ) i ++ // skip whitespace
317323 const nameStart = i
318324 const firstChar = value . charCodeAt ( i )
319325 if ( firstChar < 65 || firstChar > 90 ) return 'unknown' // must start with A-Z
320- while ( i < value . length && isWordChar ( value . charCodeAt ( i ) ) ) i ++
326+ while ( i < value . length && ( isWordChar ( value . charCodeAt ( i ) ) || value . charCodeAt ( i ) === 46 /* . */ ) ) i ++
321327 if ( i === nameStart ) return 'unknown'
322328 const className = value . slice ( nameStart , i )
323329
324330 {
325331 const afterClass = value . slice ( i )
332+
333+ // Check for generic type parameters
334+ let afterGenerics = afterClass
326335 if ( afterClass . startsWith ( '<' ) ) {
327336 // Extract the generic params by finding the matching '>'
328337 let depth = 0
329338 let end = - 1
330- for ( let i = 0 ; i < afterClass . length ; i ++ ) {
331- if ( afterClass [ i ] === '<' ) {
332- depth ++
333- }
334- else if ( afterClass [ i ] === '>' ) {
335- depth --
336- if ( depth === 0 ) { end = i ; break }
337- }
339+ for ( let j = 0 ; j < afterClass . length ; j ++ ) {
340+ if ( afterClass [ j ] === '<' ) depth ++
341+ else if ( afterClass [ j ] === '>' ) { depth -- ; if ( depth === 0 ) { end = j ; break } }
338342 }
339343 if ( end !== - 1 ) {
340344 const generics = afterClass . slice ( 0 , end + 1 )
345+ afterGenerics = afterClass . slice ( end + 1 )
346+ // Check for method chain after constructor (e.g., new Foo<T>(...).method())
347+ // Find closing paren of constructor, then check for '.'
348+ const parenStart = afterGenerics . indexOf ( '(' )
349+ if ( parenStart !== - 1 ) {
350+ let pDepth = 0
351+ let pEnd = - 1
352+ for ( let j = parenStart ; j < afterGenerics . length ; j ++ ) {
353+ if ( afterGenerics [ j ] === '(' ) pDepth ++
354+ else if ( afterGenerics [ j ] === ')' ) { pDepth -- ; if ( pDepth === 0 ) { pEnd = j ; break } }
355+ }
356+ if ( pEnd !== - 1 ) {
357+ const rest = afterGenerics . slice ( pEnd + 1 ) . trimStart ( )
358+ if ( rest . startsWith ( '.' ) ) return 'unknown' // method chain — can't infer
359+ }
360+ }
341361 return `${ className } ${ generics } `
342362 }
343363 }
344364
365+ // Check for method chain after constructor (e.g., new Foo(...).method())
366+ const parenStart = afterGenerics . indexOf ( '(' )
367+ if ( parenStart !== - 1 ) {
368+ let pDepth = 0
369+ let pEnd = - 1
370+ for ( let j = parenStart ; j < afterGenerics . length ; j ++ ) {
371+ if ( afterGenerics [ j ] === '(' ) pDepth ++
372+ else if ( afterGenerics [ j ] === ')' ) { pDepth -- ; if ( pDepth === 0 ) { pEnd = j ; break } }
373+ }
374+ if ( pEnd !== - 1 ) {
375+ const rest = afterGenerics . slice ( pEnd + 1 ) . trimStart ( )
376+ if ( rest . startsWith ( '.' ) ) return 'unknown' // method chain — can't infer
377+ }
378+ }
379+
345380 // Fallback: use default generic params for known built-in types
346381 switch ( className ) {
347382 case 'Date' : return 'Date'
@@ -361,6 +396,31 @@ function inferNewExpressionType(value: string): string {
361396 return 'unknown'
362397}
363398
399+ /**
400+ * Infer type from class expression used as a value.
401+ * For `class Foo { ... }`, extracts the class name and returns `typeof Foo`.
402+ * For anonymous classes, returns a basic constructor type.
403+ */
404+ function inferClassExpressionType ( value : string ) : string {
405+ // Extract class name: class Name or class Name extends ...
406+ const trimmed = value . trimStart ( )
407+ let i = 5 // skip 'class'
408+ // Skip whitespace
409+ while ( i < trimmed . length && trimmed . charCodeAt ( i ) <= 32 ) i ++
410+
411+ // Check if there's a name (identifier starting char)
412+ const nameStart = i
413+ if ( i < trimmed . length && isWordChar ( trimmed . charCodeAt ( i ) ) ) {
414+ while ( i < trimmed . length && isWordChar ( trimmed . charCodeAt ( i ) ) ) i ++
415+ const className = trimmed . slice ( nameStart , i )
416+ // Named class expression — use typeof ClassName
417+ return `typeof ${ className } `
418+ }
419+
420+ // Anonymous class expression
421+ return '{ new (...args: any[]): any }'
422+ }
423+
364424/**
365425 * Infer type from Promise expression
366426 */
@@ -669,16 +729,26 @@ export function inferObjectType(value: string, isConst: boolean, _depth: number
669729 const saved = _cleanDefaultResult
670730 _cleanDefaultResult = null
671731
672- let valueType = inferNarrowType ( val , isConst , false , _depth + 1 )
732+ let valueType : string
733+ const trimVal = val . trim ( )
673734
674- const nestedDefault = _cleanDefaultResult
675- _cleanDefaultResult = saved
735+ // Method definitions (method shorthand syntax) — convert directly to function type
736+ // to avoid double-processing through inferNarrowType which loses return type info
737+ if ( isMethodDefinition ( trimVal ) ) {
738+ valueType = convertMethodToFunctionType ( key , trimVal )
739+ }
740+ else {
741+ valueType = inferNarrowType ( val , isConst , false , _depth + 1 )
676742
677- // Handle method signatures - clean up async and parameter defaults
678- if ( valueType . includes ( '=>' ) || valueType . includes ( 'function' ) || valueType . includes ( 'async' ) ) {
679- valueType = cleanMethodSignature ( valueType )
743+ // Handle method signatures - clean up async and parameter defaults
744+ if ( valueType . includes ( '=>' ) || valueType . includes ( 'function' ) || valueType . includes ( 'async' ) ) {
745+ valueType = cleanMethodSignature ( valueType )
746+ }
680747 }
681748
749+ const nestedDefault = _cleanDefaultResult
750+ _cleanDefaultResult = saved
751+
682752 // Add inline @defaultValue for widened primitive properties
683753 const rawVal = val . trim ( )
684754 if ( ! isConst && isBaseType ( valueType ) && isPrimitiveLiteral ( rawVal ) ) {
@@ -1073,11 +1143,18 @@ function parseObjectProperties(content: string): Array<[string, string]> {
10731143 // Method definition like: methodName(params) or async methodName<T>(params)
10741144 // Must be checked BEFORE general bracket tracking so ( isn't swallowed
10751145 currentKey = current . trim ( )
1076- // Remove 'async' from the key if present
1146+ // Remove 'async' from the key if present, prefix value with 'async ' marker
1147+ let methodPrefix = ''
10771148 if ( currentKey . startsWith ( 'async ' ) ) {
10781149 currentKey = currentKey . slice ( 6 ) . trim ( )
1150+ methodPrefix = 'async '
1151+ }
1152+ // Remove generator '*' prefix from key, prefix value with '*' marker
1153+ if ( currentKey . startsWith ( '*' ) ) {
1154+ currentKey = currentKey . slice ( 1 ) . trim ( )
1155+ methodPrefix += '*'
10791156 }
1080- current = char // Start with the opening parenthesis
1157+ current = methodPrefix + char // Start with any prefix + opening parenthesis
10811158 inKey = false
10821159 depth = 1 // We're now inside the method definition
10831160 }
@@ -1096,18 +1173,7 @@ function parseObjectProperties(content: string): Array<[string, string]> {
10961173 }
10971174 else if ( cc === 44 /* , */ && depth === 0 ) {
10981175 if ( currentKey && current . trim ( ) ) {
1099- // Clean method signatures before storing
1100- let value = current . trim ( )
1101-
1102- // Check if this is a method definition (starts with parentheses)
1103- if ( value . startsWith ( '(' ) ) {
1104- // This is a method definition like: (params): ReturnType { ... }
1105- value = convertMethodToFunctionType ( currentKey , value )
1106- }
1107- else if ( value . includes ( '=>' ) || value . includes ( 'function' ) || value . includes ( 'async' ) ) {
1108- value = cleanMethodSignature ( value )
1109- }
1110-
1176+ const value = current . trim ( )
11111177 properties . push ( [ currentKey , value ] )
11121178 }
11131179 current = ''
@@ -1126,33 +1192,83 @@ function parseObjectProperties(content: string): Array<[string, string]> {
11261192
11271193 // Don't forget the last property
11281194 if ( currentKey && current . trim ( ) ) {
1129- let value = current . trim ( )
1130-
1131- // Check if this is a method definition (starts with parentheses)
1132- if ( value . startsWith ( '(' ) ) {
1133- // This is a method definition like: (params): ReturnType { ... }
1134- value = convertMethodToFunctionType ( currentKey , value )
1135- }
1136- else if ( value . includes ( '=>' ) || value . includes ( 'function' ) || value . includes ( 'async' ) ) {
1137- value = cleanMethodSignature ( value )
1138- }
1139-
1195+ const value = current . trim ( )
11401196 properties . push ( [ currentKey , value ] )
11411197 }
11421198
11431199 return properties
11441200}
11451201
1202+ /** Check if value looks like a method definition (not an arrow function).
1203+ * Method definitions: (params): ReturnType { body }
1204+ * Arrow functions: (params): ReturnType => body
1205+ * Key difference: method definitions have no '=>' at top level. */
1206+ function isMethodDefinition ( value : string ) : boolean {
1207+ let stripped = value
1208+ // Strip async/generator prefixes to check what follows
1209+ if ( stripped . startsWith ( 'async ' ) || stripped . startsWith ( 'async\t' ) ) {
1210+ stripped = stripped . slice ( 5 ) . trimStart ( )
1211+ }
1212+ if ( stripped . startsWith ( '*' ) ) {
1213+ stripped = stripped . slice ( 1 ) . trimStart ( )
1214+ }
1215+ // Must start with '(' to be a method definition
1216+ if ( ! stripped . startsWith ( '(' ) ) return false
1217+
1218+ // Find the matching ')' for the parameter list
1219+ let depth = 0
1220+ for ( let i = 0 ; i < stripped . length ; i ++ ) {
1221+ const c = stripped . charCodeAt ( i )
1222+ if ( c === 40 /* ( */ ) depth ++
1223+ else if ( c === 41 /* ) */ ) {
1224+ depth --
1225+ if ( depth === 0 ) {
1226+ // After the closing paren, check if there's '=>' (arrow function) or not (method def)
1227+ const after = stripped . slice ( i + 1 ) . trimStart ( )
1228+ // Method definitions have ':' then return type then '{', or just '{'
1229+ // Arrow functions have '=>' (possibly after ': ReturnType')
1230+ if ( after . startsWith ( '{' ) ) return true // (params) { body }
1231+ if ( after . startsWith ( ':' ) ) {
1232+ // Could be (params): ReturnType { body } or (params): ReturnType => body
1233+ // Check if '=>' appears before '{' at depth 0
1234+ let scanDepth = 0
1235+ for ( let j = 0 ; j < after . length ; j ++ ) {
1236+ const sc = after . charCodeAt ( j )
1237+ if ( sc === 40 || sc === 91 ) scanDepth ++
1238+ else if ( sc === 41 || sc === 93 ) scanDepth --
1239+ else if ( sc === 60 ) scanDepth ++ // < for generics in return type
1240+ else if ( sc === 62 ) { if ( scanDepth > 0 ) scanDepth -- } // >
1241+ else if ( scanDepth === 0 && sc === 61 /* = */ && j + 1 < after . length && after . charCodeAt ( j + 1 ) === 62 /* > */ ) {
1242+ return false // Found '=>' — this is an arrow function
1243+ }
1244+ else if ( scanDepth === 0 && sc === 123 /* { */ ) {
1245+ return true // Found '{' before any '=>' — this is a method definition
1246+ }
1247+ }
1248+ }
1249+ return false
1250+ }
1251+ }
1252+ }
1253+ return false
1254+ }
1255+
11461256/**
11471257 * Convert method definition to function type signature
11481258 */
11491259function convertMethodToFunctionType ( _methodName : string , _methodDef : string ) : string {
1150- // Remove async modifier if present — no regex
1151- let cleaned = _methodDef
1152- let ci = 0
1153- while ( ci < cleaned . length && cleaned . charCodeAt ( ci ) <= 32 ) ci ++
1154- if ( cleaned . startsWith ( 'async' , ci ) && ci + 5 < cleaned . length && cleaned . charCodeAt ( ci + 5 ) <= 32 ) {
1155- cleaned = cleaned . slice ( ci + 5 ) . trimStart ( )
1260+ // Detect and remove async/generator prefixes
1261+ let cleaned = _methodDef . trimStart ( )
1262+ let isAsync = false
1263+ let isGenerator = false
1264+
1265+ if ( cleaned . startsWith ( 'async ' ) || cleaned . startsWith ( 'async\t' ) ) {
1266+ isAsync = true
1267+ cleaned = cleaned . slice ( 5 ) . trimStart ( )
1268+ }
1269+ if ( cleaned . startsWith ( '*' ) ) {
1270+ isGenerator = true
1271+ cleaned = cleaned . slice ( 1 ) . trimStart ( )
11561272 }
11571273
11581274 // Extract generics: starts with '<', find matching '>'
@@ -1212,6 +1328,23 @@ function convertMethodToFunctionType(_methodName: string, _methodDef: string): s
12121328 }
12131329 }
12141330
1331+ // Apply async/generator defaults when no explicit return type was provided
1332+ if ( returnType === 'unknown' ) {
1333+ if ( isAsync && isGenerator ) {
1334+ returnType = 'AsyncGenerator<unknown, void, unknown>'
1335+ }
1336+ else if ( isGenerator ) {
1337+ returnType = 'Generator<unknown, void, unknown>'
1338+ }
1339+ else if ( isAsync ) {
1340+ returnType = 'Promise<void>'
1341+ }
1342+ }
1343+ else if ( isAsync && ! returnType . startsWith ( 'Promise<' ) && ! returnType . startsWith ( 'AsyncGenerator<' ) ) {
1344+ // If async method has explicit non-Promise return type, wrap it
1345+ returnType = `Promise<${ returnType } >`
1346+ }
1347+
12151348 // Clean parameter defaults
12161349 const cleanedParams = cleanParameterDefaults ( params )
12171350
0 commit comments