@@ -381,15 +381,13 @@ export function processVariableDeclaration(decl: Declaration): string {
381
381
// If we have a value, check if it has 'as const' - if so, infer from value instead of type annotation
382
382
if ( decl . value && decl . value . includes ( 'as const' ) ) {
383
383
typeAnnotation = inferNarrowType ( decl . value , true )
384
- } else if ( decl . value && kind === 'const' ) {
385
- // For const declarations, always try to infer a more specific type from the value
386
- const inferredType = inferNarrowType ( decl . value , false )
387
-
388
- // Use the inferred type if it's more specific than a generic Record type
389
- if ( ! typeAnnotation ||
390
- typeAnnotation . startsWith ( 'Record<' ) ||
391
- typeAnnotation === 'any' ||
392
- typeAnnotation === 'object' ) {
384
+ } else if ( ! typeAnnotation && decl . value && kind === 'const' ) {
385
+ // For const declarations WITHOUT explicit type annotation, infer narrow types from the value
386
+ typeAnnotation = inferNarrowType ( decl . value , true )
387
+ } else if ( typeAnnotation && decl . value && kind === 'const' && isGenericType ( typeAnnotation ) ) {
388
+ // For const declarations with generic type annotations (Record, any, object), prefer narrow inference
389
+ const inferredType = inferNarrowType ( decl . value , true )
390
+ if ( inferredType !== 'unknown' ) {
393
391
typeAnnotation = inferredType
394
392
}
395
393
} else if ( ! typeAnnotation && decl . value ) {
@@ -755,20 +753,14 @@ export function inferNarrowType(value: any, isConst: boolean = false): string {
755
753
return 'string'
756
754
}
757
755
758
- // Number literals
756
+ // Number literals - ALWAYS use literal types for const declarations
759
757
if ( / ^ - ? \d + ( \. \d + ) ? $ / . test ( trimmed ) ) {
760
- if ( isConst ) {
761
- return trimmed
762
- }
763
- return 'number'
758
+ return trimmed // Always return literal number
764
759
}
765
760
766
- // Boolean literals
761
+ // Boolean literals - ALWAYS use literal types for const declarations
767
762
if ( trimmed === 'true' || trimmed === 'false' ) {
768
- if ( isConst ) {
769
- return trimmed
770
- }
771
- return 'boolean'
763
+ return trimmed // Always return literal boolean
772
764
}
773
765
774
766
// Null and undefined
@@ -975,12 +967,24 @@ function inferArrayType(value: string, isConst: boolean): string {
975
967
return inferNarrowTypeInUnion ( trimmedEl , isConst )
976
968
} )
977
969
978
- // For const arrays, create readonly tuples instead of union types
970
+ // For const arrays, ALWAYS create readonly tuples for better type safety
979
971
if ( isConst ) {
980
972
return `readonly [${ elementTypes . join ( ', ' ) } ]`
981
973
}
982
974
975
+ // For simple arrays with all same literal types, also create tuples
983
976
const uniqueTypes = [ ...new Set ( elementTypes ) ]
977
+ const allLiterals = elementTypes . every ( type =>
978
+ / ^ - ? \d + ( \. \d + ) ? $ / . test ( type ) || // numbers
979
+ type === 'true' || type === 'false' || // booleans
980
+ ( type . startsWith ( '"' ) && type . endsWith ( '"' ) ) || // strings
981
+ ( type . startsWith ( "'" ) && type . endsWith ( "'" ) )
982
+ )
983
+
984
+ if ( allLiterals && elementTypes . length <= 10 ) {
985
+ // Create tuple for small arrays with literal types
986
+ return `readonly [${ elementTypes . join ( ', ' ) } ]`
987
+ }
984
988
985
989
if ( uniqueTypes . length === 1 ) {
986
990
return `Array<${ uniqueTypes [ 0 ] } >`
@@ -1044,14 +1048,48 @@ function inferObjectType(value: string, isConst: boolean): string {
1044
1048
const properties = parseObjectProperties ( content )
1045
1049
const propTypes : string [ ] = [ ]
1046
1050
1047
- for ( const [ key , val ] of properties ) {
1048
- const valueType = inferNarrowType ( val , isConst )
1051
+ for ( const [ key , val ] of properties ) {
1052
+ let valueType = inferNarrowType ( val , isConst )
1053
+
1054
+ // Handle method signatures - clean up async and parameter defaults
1055
+ if ( valueType . includes ( '=>' ) || valueType . includes ( 'function' ) || valueType . includes ( 'async' ) ) {
1056
+ valueType = cleanMethodSignature ( valueType )
1057
+ }
1058
+
1049
1059
propTypes . push ( `${ key } : ${ valueType } ` )
1050
1060
}
1051
1061
1052
1062
return `{\n ${ propTypes . join ( ';\n ' ) } \n}`
1053
1063
}
1054
1064
1065
+ /**
1066
+ * Clean method signatures for declaration files
1067
+ */
1068
+ function cleanMethodSignature ( signature : string ) : string {
1069
+ // Remove async modifier from method signatures (including in object methods)
1070
+ let cleaned = signature . replace ( / ^ a s y n c \s + / , '' ) . replace ( / \b a s y n c \s + / g, '' )
1071
+
1072
+ // Remove parameter default values (e.g., currency = 'USD' becomes currency?)
1073
+ cleaned = cleaned . replace ( / ( \w + ) \s * = \s * [ ^ , ) ] + / g, ( match , paramName ) => {
1074
+ return `${ paramName } ?`
1075
+ } )
1076
+
1077
+ // Clean up extra spaces
1078
+ cleaned = cleaned . replace ( / \s + / g, ' ' ) . trim ( )
1079
+
1080
+ return cleaned
1081
+ }
1082
+
1083
+ /**
1084
+ * Clean parameter defaults from function parameters
1085
+ */
1086
+ function cleanParameterDefaults ( params : string ) : string {
1087
+ // Remove parameter default values and make them optional
1088
+ return params . replace ( / ( \w + ) \s * = \s * [ ^ , ) ] + / g, ( match , paramName ) => {
1089
+ return `${ paramName } ?`
1090
+ } )
1091
+ }
1092
+
1055
1093
/**
1056
1094
* Parse object properties
1057
1095
*/
@@ -1086,9 +1124,30 @@ function parseObjectProperties(content: string): Array<[string, string]> {
1086
1124
currentKey = current . trim ( )
1087
1125
current = ''
1088
1126
inKey = false
1127
+ } else if ( char === '(' && depth === 0 && inKey ) {
1128
+ // This might be a method definition like: methodName(params) or async methodName<T>(params)
1129
+ currentKey = current . trim ( )
1130
+ // Remove 'async' from the key if present
1131
+ if ( currentKey . startsWith ( 'async ' ) ) {
1132
+ currentKey = currentKey . slice ( 6 ) . trim ( )
1133
+ }
1134
+ current = char // Start with the opening parenthesis
1135
+ inKey = false
1136
+ depth = 1 // We're now inside the method definition
1089
1137
} else if ( char === ',' && depth === 0 ) {
1090
- if ( currentKey && current . trim ( ) ) {
1091
- properties . push ( [ currentKey , current . trim ( ) ] )
1138
+ if ( currentKey && current . trim ( ) ) {
1139
+ // Clean method signatures before storing
1140
+ let value = current . trim ( )
1141
+
1142
+ // Check if this is a method definition (starts with parentheses)
1143
+ if ( value . startsWith ( '(' ) ) {
1144
+ // This is a method definition like: (params): ReturnType { ... }
1145
+ value = convertMethodToFunctionType ( currentKey , value )
1146
+ } else if ( value . includes ( '=>' ) || value . includes ( 'function' ) || value . includes ( 'async' ) ) {
1147
+ value = cleanMethodSignature ( value )
1148
+ }
1149
+
1150
+ properties . push ( [ currentKey , value ] )
1092
1151
}
1093
1152
current = ''
1094
1153
currentKey = ''
@@ -1101,14 +1160,64 @@ function parseObjectProperties(content: string): Array<[string, string]> {
1101
1160
}
1102
1161
}
1103
1162
1104
- // Don't forget the last property
1163
+ // Don't forget the last property
1105
1164
if ( currentKey && current . trim ( ) ) {
1106
- properties . push ( [ currentKey , current . trim ( ) ] )
1165
+ let value = current . trim ( )
1166
+
1167
+ // Check if this is a method definition (starts with parentheses)
1168
+ if ( value . startsWith ( '(' ) ) {
1169
+ // This is a method definition like: (params): ReturnType { ... }
1170
+ value = convertMethodToFunctionType ( currentKey , value )
1171
+ } else if ( value . includes ( '=>' ) || value . includes ( 'function' ) || value . includes ( 'async' ) ) {
1172
+ value = cleanMethodSignature ( value )
1173
+ }
1174
+
1175
+ properties . push ( [ currentKey , value ] )
1107
1176
}
1108
1177
1109
1178
return properties
1110
1179
}
1111
1180
1181
+ /**
1182
+ * Convert method definition to function type signature
1183
+ */
1184
+ function convertMethodToFunctionType ( methodName : string , methodDef : string ) : string {
1185
+ // Remove async modifier if present
1186
+ let cleaned = methodDef . replace ( / ^ a s y n c \s + / , '' )
1187
+
1188
+ // Extract generics, parameters, and return type
1189
+ const genericMatch = cleaned . match ( / ^ < ( [ ^ > ] + ) > / )
1190
+ const generics = genericMatch ? genericMatch [ 0 ] : ''
1191
+ if ( generics ) {
1192
+ cleaned = cleaned . slice ( generics . length ) . trim ( )
1193
+ }
1194
+
1195
+ // Find parameter list
1196
+ const paramStart = cleaned . indexOf ( '(' )
1197
+ const paramEnd = findMatchingBracket ( cleaned , paramStart , '(' , ')' )
1198
+
1199
+ if ( paramStart === - 1 || paramEnd === - 1 ) {
1200
+ return '() => unknown'
1201
+ }
1202
+
1203
+ const params = cleaned . slice ( paramStart , paramEnd + 1 )
1204
+ let returnType = 'unknown'
1205
+
1206
+ // Check for explicit return type annotation
1207
+ const afterParams = cleaned . slice ( paramEnd + 1 ) . trim ( )
1208
+ if ( afterParams . startsWith ( ':' ) ) {
1209
+ const returnTypeMatch = afterParams . match ( / ^ : \s * ( [ ^ { ] + ) / )
1210
+ if ( returnTypeMatch ) {
1211
+ returnType = returnTypeMatch [ 1 ] . trim ( )
1212
+ }
1213
+ }
1214
+
1215
+ // Clean parameter defaults
1216
+ const cleanedParams = cleanParameterDefaults ( params )
1217
+
1218
+ return `${ generics } ${ cleanedParams } => ${ returnType } `
1219
+ }
1220
+
1112
1221
/**
1113
1222
* Find matching bracket for nested structures
1114
1223
*/
@@ -1134,24 +1243,10 @@ function inferFunctionType(value: string, inUnion: boolean = false): string {
1134
1243
const trimmed = value . trim ( )
1135
1244
1136
1245
// 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`
1246
+ // Only simplify if it's truly complex AND looks like a problematic signature
1247
+ if ( trimmed . length > 200 && ( trimmed . match ( / = > / g) || [ ] ) . length > 2 && ( trimmed . match ( / < / g) || [ ] ) . length > 5 && ! trimmed . startsWith ( 'function' ) ) {
1248
+ // For extremely complex types, use a simple signature
1249
+ const funcType = '(...args: any[]) => any'
1155
1250
return inUnion ? `(${ funcType } )` : funcType
1156
1251
}
1157
1252
@@ -1162,6 +1257,9 @@ function inferFunctionType(value: string, inUnion: boolean = false): string {
1162
1257
let params = asyncRemoved . substring ( 0 , arrowIndex ) . trim ( )
1163
1258
let body = asyncRemoved . substring ( arrowIndex + 2 ) . trim ( )
1164
1259
1260
+ // Clean up params - remove default values
1261
+ params = cleanParameterDefaults ( params )
1262
+
1165
1263
// Clean up params
1166
1264
if ( params === '()' || params === '' ) {
1167
1265
params = '()'
@@ -1218,6 +1316,9 @@ function inferFunctionType(value: string, inUnion: boolean = false): string {
1218
1316
params = params . substring ( 0 , params . lastIndexOf ( '):' ) ) + ')'
1219
1317
}
1220
1318
1319
+ // Clean up params - remove default values
1320
+ params = cleanParameterDefaults ( params )
1321
+
1221
1322
// Clean up params
1222
1323
if ( params === '()' || params === '' ) {
1223
1324
params = '()'
0 commit comments