Skip to content

Commit 32fe2bb

Browse files
fix(eslint-plugin): [no-unnecessary-condition] better handling for unary negation (typescript-eslint#2382)
1 parent e6be621 commit 32fe2bb

File tree

2 files changed

+80
-1
lines changed

2 files changed

+80
-1
lines changed

packages/eslint-plugin/src/rules/no-unnecessary-condition.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,14 @@ export default createRule<Options, MessageId>({
166166
* if the type of the node is always true or always false, it's not necessary.
167167
*/
168168
function checkNode(node: TSESTree.Expression): void {
169+
// Check if the node is Unary Negation expression and handle it
170+
if (
171+
node.type === AST_NODE_TYPES.UnaryExpression &&
172+
node.operator === '!'
173+
) {
174+
return checkIfUnaryNegationExpressionIsNecessaryConditional(node);
175+
}
176+
169177
// Since typescript array index signature types don't represent the
170178
// possibility of out-of-bounds access, if we're indexing into an array
171179
// just skip the check, to avoid false positives
@@ -213,6 +221,30 @@ export default createRule<Options, MessageId>({
213221
}
214222
}
215223

224+
/**
225+
* If it is Unary Negation Expression, check the argument for alwaysTruthy / alwaysFalsy
226+
*/
227+
function checkIfUnaryNegationExpressionIsNecessaryConditional(
228+
node: TSESTree.UnaryExpression,
229+
): void {
230+
if (isArrayIndexExpression(node.argument)) {
231+
return;
232+
}
233+
const type = getNodeType(node.argument);
234+
const messageId = isTypeFlagSet(type, ts.TypeFlags.Never)
235+
? 'never'
236+
: isPossiblyTruthy(type)
237+
? 'alwaysFalsy'
238+
: isPossiblyFalsy(type)
239+
? 'alwaysTruthy'
240+
: undefined;
241+
242+
if (messageId) {
243+
context.report({ node, messageId });
244+
}
245+
return;
246+
}
247+
216248
function checkNodeForNullish(node: TSESTree.Expression): void {
217249
// Since typescript array index signature types don't represent the
218250
// possibility of out-of-bounds access, if we're indexing into an array

packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,26 @@ declare const key: Key;
445445
446446
foo?.[key]?.trim();
447447
`,
448-
// https://github.com/typescript-eslint/typescript-eslint/issues/2384
448+
`
449+
let latencies: number[][] = [];
450+
451+
function recordData(): void {
452+
if (!latencies[0]) latencies[0] = [];
453+
latencies[0].push(4);
454+
}
455+
456+
recordData();
457+
`,
458+
`
459+
let latencies: number[][] = [];
460+
461+
function recordData(): void {
462+
if (latencies[0]) latencies[0] = [];
463+
latencies[0].push(4);
464+
}
465+
466+
recordData();
467+
`,
449468
`
450469
function test(testVal?: boolean) {
451470
if (testVal ?? true) {
@@ -458,6 +477,34 @@ function test(testVal?: boolean) {
458477
// Ensure that it's checking in all the right places
459478
{
460479
code: `
480+
const a = null;
481+
if (!a) {
482+
}
483+
`,
484+
errors: [ruleError(3, 5, 'alwaysTruthy')],
485+
},
486+
{
487+
code: `
488+
const a = true;
489+
if (!a) {
490+
}
491+
`,
492+
errors: [ruleError(3, 5, 'alwaysFalsy')],
493+
},
494+
{
495+
code: `
496+
function sayHi(): void {
497+
console.log('Hi!');
498+
}
499+
500+
let speech: never = sayHi();
501+
if (!speech) {
502+
}
503+
`,
504+
errors: [ruleError(7, 5, 'never')],
505+
},
506+
{
507+
code: `
461508
const b1 = true;
462509
declare const b2: boolean;
463510
const t1 = b1 && b2;

0 commit comments

Comments
 (0)