diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts index ed963aa4703b..692dc2e9e400 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts @@ -26,10 +26,30 @@ import { // Truthiness utilities // #region +const valueIsPseudoBigInt = ( + value: number | string | ts.PseudoBigInt, +): value is ts.PseudoBigInt => { + return typeof value === 'object'; +}; + +const getValue = (type: ts.LiteralType): bigint | number | string => { + if (valueIsPseudoBigInt(type.value)) { + return BigInt((type.value.negative ? '-' : '') + type.value.base10Value); + } + return type.value; +}; + +const isFalsyBigInt = (type: ts.Type): boolean => { + return ( + tsutils.isLiteralType(type) && + valueIsPseudoBigInt(type.value) && + !getValue(type) + ); +}; const isTruthyLiteral = (type: ts.Type): boolean => tsutils.isTrueLiteralType(type) || // || type. - (type.isLiteral() && !!type.value); + (type.isLiteral() && !!getValue(type)); const isPossiblyFalsy = (type: ts.Type): boolean => tsutils @@ -49,7 +69,13 @@ const isPossiblyTruthy = (type: ts.Type): boolean => .some(intersectionParts => // It is possible to define intersections that are always falsy, // like `"" & { __brand: string }`. - intersectionParts.every(type => !tsutils.isFalsyType(type)), + intersectionParts.every( + type => + !tsutils.isFalsyType(type) && + // below is a workaround for ts-api-utils bug + // see https://github.com/JoshuaKGoldberg/ts-api-utils/issues/544 + !isFalsyBigInt(type), + ), ); // Nullish utilities 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 629228f4acc7..d3042406ed1d 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts @@ -85,6 +85,11 @@ switch (b1) { declare function foo(): number | void; const result1 = foo() === undefined; const result2 = foo() == null; + `, + ` +declare const bigInt: 0n | 1n; +if (bigInt) { +} `, necessaryConditionTest('false | 5'), // Truthy literal and falsy literal necessaryConditionTest('boolean | "foo"'), // boolean and truthy literal @@ -1040,10 +1045,33 @@ switch (b1) { unnecessaryConditionTest('void', 'alwaysFalsy'), unnecessaryConditionTest('never', 'never'), unnecessaryConditionTest('string & number', 'never'), - // More complex logical expressions { code: ` +declare const falseyBigInt: 0n; +if (falseyBigInt) { +} + `, + errors: [ruleError(3, 5, 'alwaysFalsy')], + }, + { + code: ` +declare const posbigInt: 1n; +if (posbigInt) { +} + `, + errors: [ruleError(3, 5, 'alwaysTruthy')], + }, + { + code: ` +declare const negBigInt: -2n; +if (negBigInt) { +} + `, + errors: [ruleError(3, 5, 'alwaysTruthy')], + }, + { + code: ` declare const b1: boolean; declare const b2: boolean; if (true && b1 && b2) {