1
- /* eslint-disable @typescript-eslint/internal/prefer-ast-types-enum */
2
1
import type { TSESTree } from '@typescript-eslint/utils' ;
3
2
4
3
import { AST_NODE_TYPES } from '@typescript-eslint/utils' ;
@@ -69,7 +68,7 @@ export default createRule<Options, MessageIds>({
69
68
const checker = services . program . getTypeChecker ( ) ;
70
69
const ignoredTypeNames = option . ignoredTypeNames ?? [ ] ;
71
70
72
- function checkExpression ( node : TSESTree . Node , type ?: ts . Type ) : void {
71
+ function checkExpression ( node : TSESTree . Expression , type ?: ts . Type ) : void {
73
72
if ( node . type === AST_NODE_TYPES . Literal ) {
74
73
return ;
75
74
}
@@ -176,15 +175,17 @@ export default createRule<Options, MessageIds>({
176
175
}
177
176
178
177
function collectToStringCertainty ( type : ts . Type ) : Usefulness {
179
- const toString =
180
- checker . getPropertyOfType ( type , 'toString' ) ??
181
- checker . getPropertyOfType ( type , 'toLocaleString' ) ;
182
- const declarations = toString ?. getDeclarations ( ) ;
183
- if ( ! toString || ! declarations || declarations . length === 0 ) {
178
+ // https://github.com/JoshuaKGoldberg/ts-api-utils/issues/382
179
+ if ( ( tsutils . isTypeParameter as ( t : ts . Type ) => boolean ) ( type ) ) {
180
+ const constraint = type . getConstraint ( ) ;
181
+ if ( constraint ) {
182
+ return collectToStringCertainty ( constraint ) ;
183
+ }
184
+ // unconstrained generic means `unknown`
184
185
return Usefulness . Always ;
185
186
}
186
187
187
- // Patch for old version TypeScript, the Boolean type definition missing toString()
188
+ // the Boolean type definition missing toString()
188
189
if (
189
190
type . flags & ts . TypeFlags . Boolean ||
190
191
type . flags & ts . TypeFlags . BooleanLiteral
@@ -196,32 +197,49 @@ export default createRule<Options, MessageIds>({
196
197
return Usefulness . Always ;
197
198
}
198
199
199
- if (
200
- declarations . every (
201
- ( { parent } ) =>
202
- ! ts . isInterfaceDeclaration ( parent ) || parent . name . text !== 'Object' ,
203
- )
204
- ) {
205
- return Usefulness . Always ;
206
- }
207
-
208
200
if ( type . isIntersection ( ) ) {
209
201
return collectIntersectionTypeCertainty ( type , collectToStringCertainty ) ;
210
202
}
211
203
212
- if ( ! type . isUnion ( ) ) {
213
- return Usefulness . Never ;
204
+ if ( type . isUnion ( ) ) {
205
+ return collectUnionTypeCertainty ( type , collectToStringCertainty ) ;
206
+ }
207
+
208
+ const toString =
209
+ checker . getPropertyOfType ( type , 'toString' ) ??
210
+ checker . getPropertyOfType ( type , 'toLocaleString' ) ;
211
+ if ( ! toString ) {
212
+ // e.g. any/unknown
213
+ return Usefulness . Always ;
214
214
}
215
- return collectUnionTypeCertainty ( type , collectToStringCertainty ) ;
215
+
216
+ const declarations = toString . getDeclarations ( ) ;
217
+
218
+ if ( declarations == null || declarations . length !== 1 ) {
219
+ // If there are multiple declarations, at least one of them must not be
220
+ // the default object toString.
221
+ //
222
+ // This may only matter for older versions of TS
223
+ // see https://github.com/typescript-eslint/typescript-eslint/issues/8585
224
+ return Usefulness . Always ;
225
+ }
226
+
227
+ const declaration = declarations [ 0 ] ;
228
+ const isBaseToString =
229
+ ts . isInterfaceDeclaration ( declaration . parent ) &&
230
+ declaration . parent . name . text === 'Object' ;
231
+ return isBaseToString ? Usefulness . Never : Usefulness . Always ;
216
232
}
217
233
218
234
function isBuiltInStringCall ( node : TSESTree . CallExpression ) : boolean {
219
235
if (
220
236
node . callee . type === AST_NODE_TYPES . Identifier &&
237
+ // eslint-disable-next-line @typescript-eslint/internal/prefer-ast-types-enum
221
238
node . callee . name === 'String' &&
222
239
node . arguments [ 0 ]
223
240
) {
224
241
const scope = context . sourceCode . getScope ( node ) ;
242
+ // eslint-disable-next-line @typescript-eslint/internal/prefer-ast-types-enum
225
243
const variable = scope . set . get ( 'String' ) ;
226
244
return ! variable ?. defs . length ;
227
245
}
@@ -245,7 +263,10 @@ export default createRule<Options, MessageIds>({
245
263
}
246
264
} ,
247
265
CallExpression ( node : TSESTree . CallExpression ) : void {
248
- if ( isBuiltInStringCall ( node ) ) {
266
+ if (
267
+ isBuiltInStringCall ( node ) &&
268
+ node . arguments [ 0 ] . type !== AST_NODE_TYPES . SpreadElement
269
+ ) {
249
270
checkExpression ( node . arguments [ 0 ] ) ;
250
271
}
251
272
} ,
0 commit comments