Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
fix(eslint-plugin): [no-unnecessary-conditionals] Handle comparison o…
…f generics and loose comparisons with undefined values (#2152)
  • Loading branch information
avocadowastaken committed Jun 2, 2020
1 parent 53232d7 commit c86e2a2
Show file tree
Hide file tree
Showing 2 changed files with 154 additions and 14 deletions.
26 changes: 17 additions & 9 deletions packages/eslint-plugin/src/rules/no-unnecessary-condition.ts
Expand Up @@ -266,20 +266,28 @@ export default createRule<Options, MessageId>({
if (isStrictCompilerOptionEnabled(compilerOptions, 'strictNullChecks')) {
const UNDEFINED = ts.TypeFlags.Undefined;
const NULL = ts.TypeFlags.Null;
const isComparable = (type: ts.Type, flag: ts.TypeFlags): boolean => {
// Allow comparison to `any`, `unknown` or a naked type parameter.
flag |=
ts.TypeFlags.Any |
ts.TypeFlags.Unknown |
ts.TypeFlags.TypeParameter;

// Allow loose comparison to nullish values.
if (node.operator === '==' || node.operator === '!=') {
flag |= NULL | UNDEFINED;
}

const NULLISH =
node.operator === '==' || node.operator === '!='
? NULL | UNDEFINED
: NULL;
return isTypeFlagSet(type, flag);
};

if (
(leftType.flags === UNDEFINED &&
!isTypeFlagSet(rightType, UNDEFINED, true)) ||
!isComparable(rightType, UNDEFINED)) ||
(rightType.flags === UNDEFINED &&
!isTypeFlagSet(leftType, UNDEFINED, true)) ||
(leftType.flags === NULL &&
!isTypeFlagSet(rightType, NULLISH, true)) ||
(rightType.flags === NULL && !isTypeFlagSet(leftType, NULLISH, true))
!isComparable(leftType, UNDEFINED)) ||
(leftType.flags === NULL && !isComparable(rightType, NULL)) ||
(rightType.flags === NULL && !isComparable(leftType, NULL))
) {
context.report({ node, messageId: 'noOverlapBooleanExpression' });
return;
Expand Down
142 changes: 137 additions & 5 deletions packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts
Expand Up @@ -138,14 +138,22 @@ function test(a?: string) {
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?: null | string) {
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;
}
`,
`
Expand All @@ -154,6 +162,18 @@ function test(a: any) {
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;
const t9 = a === null;
const t10 = null === a;
const t11 = a !== null;
const t12 = null !== a;
const t13 = a === undefined;
const t14 = undefined === a;
const t15 = a !== undefined;
const t16 = undefined !== a;
}
`,
`
Expand All @@ -162,6 +182,38 @@ function test(a: unknown) {
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;
const t9 = a === null;
const t10 = null === a;
const t11 = a !== null;
const t12 = null !== a;
const t13 = a === undefined;
const t14 = undefined === a;
const t15 = a !== undefined;
const t16 = undefined !== a;
}
`,
`
function test<T>(a: T) {
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;
const t9 = a === null;
const t10 = null === a;
const t11 = a !== null;
const t12 = null !== a;
const t13 = a === undefined;
const t14 = undefined === a;
const t15 = a !== undefined;
const t16 = undefined !== a;
}
`,

Expand Down Expand Up @@ -468,25 +520,105 @@ if (x === Foo.a) {
{
code: `
function test(a: string) {
const t1 = a !== undefined;
const t3 = undefined === a;
const t1 = a === undefined;
const t2 = undefined === a;
const t3 = a !== undefined;
const t4 = undefined !== a;
const t5 = a === null;
const t6 = null === a;
const t7 = a !== null;
const t8 = null !== a;
}
`,
errors: [
ruleError(3, 14, 'noOverlapBooleanExpression'),
ruleError(4, 14, 'noOverlapBooleanExpression'),
ruleError(5, 14, 'noOverlapBooleanExpression'),
ruleError(6, 14, 'noOverlapBooleanExpression'),
ruleError(7, 14, 'noOverlapBooleanExpression'),
ruleError(8, 14, 'noOverlapBooleanExpression'),
ruleError(9, 14, 'noOverlapBooleanExpression'),
ruleError(10, 14, 'noOverlapBooleanExpression'),
],
},
{
code: `
function test(a?: string) {
const t1 = a === null;
const t3 = null !== a;
const t1 = a === undefined;
const t2 = undefined === a;
const t3 = a !== undefined;
const t4 = undefined !== a;
const t5 = a === null;
const t6 = null === a;
const t7 = a !== null;
const t8 = null !== a;
}
`,
errors: [
ruleError(7, 14, 'noOverlapBooleanExpression'),
ruleError(8, 14, 'noOverlapBooleanExpression'),
ruleError(9, 14, 'noOverlapBooleanExpression'),
ruleError(10, 14, 'noOverlapBooleanExpression'),
],
},
{
code: `
function test(a: null | string) {
const t1 = a === undefined;
const t2 = undefined === a;
const t3 = a !== undefined;
const t4 = undefined !== a;
const t5 = a === null;
const t6 = null === a;
const t7 = a !== null;
const t8 = null !== a;
}
`,
errors: [
ruleError(3, 14, 'noOverlapBooleanExpression'),
ruleError(4, 14, 'noOverlapBooleanExpression'),
ruleError(5, 14, 'noOverlapBooleanExpression'),
ruleError(6, 14, 'noOverlapBooleanExpression'),
],
},
{
code: `
function test<T extends object>(a: T) {
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;
const t9 = a === null;
const t10 = null === a;
const t11 = a !== null;
const t12 = null !== a;
const t13 = a === undefined;
const t14 = undefined === a;
const t15 = a !== undefined;
const t16 = undefined !== a;
}
`,
errors: [
ruleError(3, 14, 'noOverlapBooleanExpression'),
ruleError(4, 14, 'noOverlapBooleanExpression'),
ruleError(5, 14, 'noOverlapBooleanExpression'),
ruleError(6, 14, 'noOverlapBooleanExpression'),
ruleError(7, 14, 'noOverlapBooleanExpression'),
ruleError(8, 14, 'noOverlapBooleanExpression'),
ruleError(9, 14, 'noOverlapBooleanExpression'),
ruleError(10, 14, 'noOverlapBooleanExpression'),
ruleError(11, 14, 'noOverlapBooleanExpression'),
ruleError(12, 15, 'noOverlapBooleanExpression'),
ruleError(13, 15, 'noOverlapBooleanExpression'),
ruleError(14, 15, 'noOverlapBooleanExpression'),
ruleError(15, 15, 'noOverlapBooleanExpression'),
ruleError(16, 15, 'noOverlapBooleanExpression'),
ruleError(17, 15, 'noOverlapBooleanExpression'),
ruleError(18, 15, 'noOverlapBooleanExpression'),
],
},
// Nullish coalescing operator
Expand Down

0 comments on commit c86e2a2

Please sign in to comment.