diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts index c4bed147eb88..892d3a9201ce 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts @@ -646,22 +646,53 @@ export default createRule({ .getCallSignaturesOfType( getConstrainedTypeAtLocation(services, callback), ) - .map(sig => sig.getReturnType()); - /* istanbul ignore if */ if (returnTypes.length === 0) { - // Not a callable function + .map(sig => sig.getReturnType()) + .map(t => { + // TODO: use `getConstraintTypeInfoAtLocation` once it's merged + // https://github.com/typescript-eslint/typescript-eslint/pull/10496 + if (tsutils.isTypeParameter(t)) { + return checker.getBaseConstraintOfType(t); + } + + return t; + }); + + if (returnTypes.length === 0) { + // Not a callable function, e.g. `any` return; } - // Predicate is always necessary if it involves `any` or `unknown` - if (returnTypes.some(t => isTypeAnyType(t) || isTypeUnknownType(t))) { - return; + + let hasFalsyReturnTypes = false; + let hasTruthyReturnTypes = false; + + for (const type of returnTypes) { + // Predicate is always necessary if it involves `any` or `unknown` + if (!type || isTypeAnyType(type) || isTypeUnknownType(type)) { + return; + } + + if (isPossiblyFalsy(type)) { + hasFalsyReturnTypes = true; + } + + if (isPossiblyTruthy(type)) { + hasTruthyReturnTypes = true; + } + + // bail early if both a possibly-truthy and a possibly-falsy have been detected + if (hasFalsyReturnTypes && hasTruthyReturnTypes) { + return; + } } - if (!returnTypes.some(isPossiblyFalsy)) { + + if (!hasFalsyReturnTypes) { return context.report({ node: callback, messageId: 'alwaysTruthyFunc', }); } - if (!returnTypes.some(isPossiblyTruthy)) { + + if (!hasTruthyReturnTypes) { return context.report({ node: callback, messageId: 'alwaysFalsyFunc', diff --git a/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts b/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts index 7aaed5539e21..e69e5c969dab 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts @@ -286,6 +286,22 @@ function count( ) { return list.filter(predicate).length; } + `, + ` +declare const test: () => T; + +[1, null].filter(test); + `, + ` +declare const test: () => T; + +[1, null].filter(test); + `, + ` +[1, null].filter(1 as any); + `, + ` +[1, null].filter(1 as never); `, // Ignores non-array methods of the same name ` @@ -1598,6 +1614,14 @@ function nothing3(x: [string, string]) { { column: 25, line: 17, messageId: 'alwaysFalsy' }, ], }, + { + code: ` +declare const test: () => T; + +[1, null].filter(test); + `, + errors: [{ column: 18, line: 4, messageId: 'alwaysTruthyFunc' }], + }, // Indexing cases { // This is an error because 'dict' doesn't represent