From 32fe2bb4fe5524355eef4f3a9bd85c824e9d7f46 Mon Sep 17 00:00:00 2001 From: Saraswathy Renuga Date: Mon, 24 Aug 2020 05:55:07 +0530 Subject: [PATCH] fix(eslint-plugin): [no-unnecessary-condition] better handling for unary negation (#2382) --- .../src/rules/no-unnecessary-condition.ts | 32 ++++++++++++ .../rules/no-unnecessary-condition.test.ts | 49 ++++++++++++++++++- 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts index 22fec531779..29f9aa4442d 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts @@ -166,6 +166,14 @@ export default createRule({ * if the type of the node is always true or always false, it's not necessary. */ function checkNode(node: TSESTree.Expression): void { + // Check if the node is Unary Negation expression and handle it + if ( + node.type === AST_NODE_TYPES.UnaryExpression && + node.operator === '!' + ) { + return checkIfUnaryNegationExpressionIsNecessaryConditional(node); + } + // Since typescript array index signature types don't represent the // possibility of out-of-bounds access, if we're indexing into an array // just skip the check, to avoid false positives @@ -213,6 +221,30 @@ export default createRule({ } } + /** + * If it is Unary Negation Expression, check the argument for alwaysTruthy / alwaysFalsy + */ + function checkIfUnaryNegationExpressionIsNecessaryConditional( + node: TSESTree.UnaryExpression, + ): void { + if (isArrayIndexExpression(node.argument)) { + return; + } + const type = getNodeType(node.argument); + const messageId = isTypeFlagSet(type, ts.TypeFlags.Never) + ? 'never' + : isPossiblyTruthy(type) + ? 'alwaysFalsy' + : isPossiblyFalsy(type) + ? 'alwaysTruthy' + : undefined; + + if (messageId) { + context.report({ node, messageId }); + } + return; + } + function checkNodeForNullish(node: TSESTree.Expression): void { // Since typescript array index signature types don't represent the // possibility of out-of-bounds access, if we're indexing into an array 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 55b7a0e7fb1..3a9d0fb450b 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts @@ -445,7 +445,26 @@ declare const key: Key; foo?.[key]?.trim(); `, - // https://github.com/typescript-eslint/typescript-eslint/issues/2384 + ` +let latencies: number[][] = []; + +function recordData(): void { + if (!latencies[0]) latencies[0] = []; + latencies[0].push(4); +} + +recordData(); + `, + ` +let latencies: number[][] = []; + +function recordData(): void { + if (latencies[0]) latencies[0] = []; + latencies[0].push(4); +} + +recordData(); + `, ` function test(testVal?: boolean) { if (testVal ?? true) { @@ -458,6 +477,34 @@ function test(testVal?: boolean) { // Ensure that it's checking in all the right places { code: ` +const a = null; +if (!a) { +} + `, + errors: [ruleError(3, 5, 'alwaysTruthy')], + }, + { + code: ` +const a = true; +if (!a) { +} + `, + errors: [ruleError(3, 5, 'alwaysFalsy')], + }, + { + code: ` +function sayHi(): void { + console.log('Hi!'); +} + +let speech: never = sayHi(); +if (!speech) { +} + `, + errors: [ruleError(7, 5, 'never')], + }, + { + code: ` const b1 = true; declare const b2: boolean; const t1 = b1 && b2;