diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts index 316b4fcd76a..5f04366b5fd 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts @@ -5,7 +5,6 @@ import { } from '@typescript-eslint/experimental-utils'; import * as ts from 'typescript'; import { - isTypeFlagSet, unionTypeParts, isFalsyType, isBooleanLiteralType, @@ -14,6 +13,7 @@ import { isStrictCompilerOptionEnabled, } from 'tsutils'; import { + isTypeFlagSet, createRule, getParserServices, getConstrainedTypeAtLocation, @@ -23,9 +23,6 @@ import { isMemberOrOptionalMemberExpression, } from '../util'; -const typeContainsFlag = (type: ts.Type, flag: ts.TypeFlags): boolean => { - return unionTypeParts(type).some(t => isTypeFlagSet(t, flag)); -}; // Truthiness utilities // #region const isTruthyLiteral = (type: ts.Type): boolean => @@ -268,13 +265,20 @@ export default createRule({ if (isStrictCompilerOptionEnabled(compilerOptions, 'strictNullChecks')) { const UNDEFINED = ts.TypeFlags.Undefined; const NULL = ts.TypeFlags.Null; + + const NULLISH = + node.operator === '==' || node.operator === '!=' + ? NULL | UNDEFINED + : NULL; + if ( (leftType.flags === UNDEFINED && - !typeContainsFlag(rightType, UNDEFINED)) || + !isTypeFlagSet(rightType, UNDEFINED, true)) || (rightType.flags === UNDEFINED && - !typeContainsFlag(leftType, UNDEFINED)) || - (leftType.flags === NULL && !typeContainsFlag(rightType, NULL)) || - (rightType.flags === NULL && !typeContainsFlag(leftType, NULL)) + !isTypeFlagSet(leftType, UNDEFINED, true)) || + (leftType.flags === NULL && + !isTypeFlagSet(rightType, NULLISH, true)) || + (rightType.flags === NULL && !isTypeFlagSet(leftType, NULLISH, true)) ) { context.report({ node, messageId: 'noOverlapBooleanExpression' }); return; 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 fa3d1e6fc3b..1efb193df42 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts @@ -100,13 +100,68 @@ function test(t: T | []) { // Boolean expressions ` function test(a: string) { - return a === 'a'; + const t1 = a === 'a'; + const t2 = 'a' === a; } `, ` function test(a?: string) { const t1 = a === undefined; - const t3 = undefined === a; + const t2 = undefined === a; + const t1 = a !== undefined; + const t2 = undefined !== a; +} + `, + ` +function test(a: null | string) { + const t1 = a === null; + const t2 = null === a; + const t1 = a !== null; + const t2 = null !== a; +} + `, + ` +function test(a?: null | string) { + const t1 = a == null; + const t2 = null == a; + const t3 = a != null; + const t4 = null != a; + const t5 = a == undefined; + const t6 = undefined == a; + const t7 = a != undefined; + const t8 = undefined != a; +} + `, + ` +function test(a?: string) { + const t1 = a == null; + const t2 = null == a; + const t3 = a != null; + const t4 = null != a; +} + `, + ` +function test(a?: null | string) { + const t1 = a == null; + const t2 = null == a; + const t3 = a != null; + const t4 = null != a; +} + `, + ` +function test(a: any) { + const t1 = a == null; + const t2 = null == a; + const t3 = a != null; + const t4 = null != a; +} + `, + ` +function test(a: unknown) { + const t1 = a == null; + const t2 = null == a; + const t3 = a != null; + const t4 = null != a; } `,