From 1a02bdca0b9acecc9cea00d97cec102867ae8376 Mon Sep 17 00:00:00 2001 From: Jakob Guddas Date: Mon, 16 May 2022 09:52:22 +0200 Subject: [PATCH] feat(eslint-plugin): added checking of loose equal ternary cases for prefer-nullish-coalescing rule --- .../docs/rules/prefer-nullish-coalescing.md | 4 ++ .../src/rules/prefer-nullish-coalescing.ts | 46 ++++++++++++++++++- .../rules/prefer-nullish-coalescing.test.ts | 18 +++++++- 3 files changed, 65 insertions(+), 3 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md b/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md index 81fa97e6f9e9..c552d684fd7f 100644 --- a/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md +++ b/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md @@ -71,6 +71,8 @@ Incorrect code for `ignoreTernaryTests: false`, and correct code for `ignoreTern const foo: any = 'bar'; foo !== undefined && foo !== null ? foo : 'a string'; foo === undefined || foo === null ? 'a string' : foo; +foo == undefined ? 'a string' : foo; +foo == null ? 'a string' : foo; const foo: ?string = 'bar'; foo !== undefined ? foo : 'a string'; @@ -87,6 +89,8 @@ Correct code for `ignoreTernaryTests: false`: const foo: any = 'bar'; foo ?? 'a string'; foo ?? 'a string'; +foo ?? 'a string'; +foo ?? 'a string'; const foo: ?string = 'bar'; foo ?? 'a string'; diff --git a/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts b/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts index 1bf25bc487ed..90ce162bf600 100644 --- a/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts +++ b/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts @@ -93,10 +93,11 @@ export default util.createRule({ if ( node.consequent.type === AST_NODE_TYPES.Identifier && ((node.test.type === AST_NODE_TYPES.BinaryExpression && - node.test.operator === '!==') || + (node.test.operator === '!==' || node.test.operator === '!=')) || (node.test.type === AST_NODE_TYPES.LogicalExpression && node.test.left.type === AST_NODE_TYPES.BinaryExpression && - node.test.left.operator === '!==')) + (node.test.left.operator === '!==' || + node.test.left.operator === '!='))) ) { identifier = node.consequent; alternate = node.alternate; @@ -110,6 +111,11 @@ export default util.createRule({ } if ( + isFixableLooseTernary({ + requiredOperator, + identifier, + node, + }) || isFixableExplicitTernary({ requiredOperator, identifier, @@ -328,6 +334,42 @@ function isFixableExplicitTernary({ return true; } +function isFixableLooseTernary({ + node, + identifier, + requiredOperator, +}: { + requiredOperator: '!==' | '==='; + identifier: TSESTree.Identifier; + node: TSESTree.ConditionalExpression; +}): boolean { + if (node.test.type !== AST_NODE_TYPES.BinaryExpression) { + return false; + } + + const { left, right, operator } = node.test; + if (requiredOperator === '===' && operator !== '==') { + return false; + } + if (requiredOperator === '!==' && operator !== '!=') { + return false; + } + + const isIdentifier = ( + i: TSESTree.Expression | TSESTree.PrivateIdentifier, + ): boolean => + i.type === AST_NODE_TYPES.Identifier && i.name === identifier.name; + + if (isIdentifier(right) && (isNull(left) || isUndefined(left))) { + return true; + } + + if (isIdentifier(left) && (isNull(right) || isUndefined(right))) { + return true; + } + return false; +} + /** * This is for cases where we check either undefined or null and fall back to * using type information to ensure that our checks are correct. diff --git a/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts b/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts index 94752b2a1bbb..b5094608dab1 100644 --- a/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts @@ -86,6 +86,14 @@ x ?? 'foo'; 'x === undefined || x === null ? x : y;', 'x !== undefined || x === null ? x : y;', 'x !== undefined || x === null ? y : x;', + 'x == null ? x : y;', + 'x == undefined ? x : y;', + 'x != null ? y : x;', + 'x != undefined ? y : x;', + 'null == x ? x : y;', + 'undefined == x ? x : y;', + 'null != x ? y : x;', + 'undefined != x ? y : x;', ` declare const x: string | undefined | null; x !== undefined ? x : y; @@ -211,6 +219,14 @@ x ?? 'foo'; 'null !== x && undefined !== x ? x : y;', 'undefined === x || null === x ? y : x;', 'null === x || undefined === x ? y : x;', + 'undefined != x ? x : y;', + 'null != x ? x : y;', + 'undefined == x ? y : x;', + 'null == x ? y : x;', + 'x != undefined ? x : y;', + 'x != null ? x : y;', + 'x == undefined ? y : x;', + 'x == null ? y : x;', ].map(code => ({ code, output: null, @@ -221,7 +237,7 @@ x ?? 'foo'; line: 1, column: 1, endLine: 1, - endColumn: 38, + endColumn: code.length, suggestions: [ { messageId: 'suggestNullish' as const,