diff --git a/packages/eslint-plugin/src/rules/no-base-to-string.ts b/packages/eslint-plugin/src/rules/no-base-to-string.ts index 53d50bd4694f..3612dc3da1ce 100644 --- a/packages/eslint-plugin/src/rules/no-base-to-string.ts +++ b/packages/eslint-plugin/src/rules/no-base-to-string.ts @@ -212,6 +212,36 @@ export default createRule({ return Usefulness.Always; } + function hasBaseTypes(type: ts.Type): type is ts.InterfaceType { + return ( + tsutils.isObjectType(type) && + tsutils.isObjectFlagSet( + type, + ts.ObjectFlags.Interface | ts.ObjectFlags.Class, + ) + ); + } + + function isIgnoredTypeOrBase( + type: ts.Type, + seen = new Set(), + ): boolean { + if (seen.has(type)) { + return false; + } + + seen.add(type); + + const typeName = getTypeName(checker, type); + return ( + ignoredTypeNames.includes(typeName) || + (hasBaseTypes(type) && + checker + .getBaseTypes(type) + .some(base => isIgnoredTypeOrBase(base, seen))) + ); + } + function collectToStringCertainty( type: ts.Type, visited: Set, @@ -249,7 +279,7 @@ export default createRule({ return Usefulness.Always; } - if (ignoredTypeNames.includes(getTypeName(checker, type))) { + if (isIgnoredTypeOrBase(type)) { return Usefulness.Always; } diff --git a/packages/eslint-plugin/tests/rules/no-base-to-string.test.ts b/packages/eslint-plugin/tests/rules/no-base-to-string.test.ts index 7e6fa055b55e..a75e9130b921 100644 --- a/packages/eslint-plugin/tests/rules/no-base-to-string.test.ts +++ b/packages/eslint-plugin/tests/rules/no-base-to-string.test.ts @@ -324,6 +324,47 @@ error.toString(); `, options: [{ ignoredTypeNames: ['MyError'] }], }, + { + code: ` +interface Animal {} +interface Serializable {} +interface Cat extends Animal, Serializable {} + +declare const whiskers: Cat; +whiskers.toString(); + `, + options: [{ ignoredTypeNames: ['Animal'] }], + }, + { + code: ` +interface MyError extends Error {} + +declare const error: MyError; +error.toString(); + `, + }, + { + code: ` +class UnknownBase {} +class CustomError extends UnknownBase {} + +declare const err: CustomError; +err.toString(); + `, + options: [{ ignoredTypeNames: ['UnknownBase'] }], + }, + { + code: ` +interface Animal {} +interface Dog extends Animal {} +interface Cat extends Animal {} + +declare const dog: Dog; +declare const cat: Cat; +cat.toString(); + `, + options: [{ ignoredTypeNames: ['Animal'] }], + }, ` function String(value) { return value; @@ -2265,5 +2306,60 @@ v.join(); }, ], }, + { + code: ` +interface Dog extends Animal {} + +declare const labrador: Dog; +labrador.toString(); + `, + errors: [ + { + data: { + certainty: 'will', + name: 'labrador', + }, + messageId: 'baseToString', + }, + ], + }, + { + code: ` +interface A extends B {} +interface B extends A {} + +declare const a: A; +a.toString(); + `, + errors: [ + { + data: { + certainty: 'will', + name: 'a', + }, + messageId: 'baseToString', + }, + ], + }, + { + code: ` + interface Base {} + interface Left extends Base {} + interface Right extends Base {} + interface Diamond extends Left, Right {} + + declare const d: Diamond; + d.toString(); + `, + errors: [ + { + data: { + certainty: 'will', + name: 'd', + }, + messageId: 'baseToString', + }, + ], + }, ], });