diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts index 692dc2e9e400..c4bed147eb88 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts @@ -32,9 +32,11 @@ const valueIsPseudoBigInt = ( return typeof value === 'object'; }; -const getValue = (type: ts.LiteralType): bigint | number | string => { +const getValueOfLiteralType = ( + type: ts.LiteralType, +): bigint | number | string => { if (valueIsPseudoBigInt(type.value)) { - return BigInt((type.value.negative ? '-' : '') + type.value.base10Value); + return pseudoBigIntToBigInt(type.value); } return type.value; }; @@ -43,13 +45,12 @@ const isFalsyBigInt = (type: ts.Type): boolean => { return ( tsutils.isLiteralType(type) && valueIsPseudoBigInt(type.value) && - !getValue(type) + !getValueOfLiteralType(type) ); }; const isTruthyLiteral = (type: ts.Type): boolean => tsutils.isTrueLiteralType(type) || - // || type. - (type.isLiteral() && !!getValue(type)); + (type.isLiteral() && !!getValueOfLiteralType(type)); const isPossiblyFalsy = (type: ts.Type): boolean => tsutils @@ -89,13 +90,83 @@ const isPossiblyNullish = (type: ts.Type): boolean => const isAlwaysNullish = (type: ts.Type): boolean => tsutils.unionTypeParts(type).every(isNullishType); -// isLiteralType only covers numbers and strings, this is a more exhaustive check. -const isLiteral = (type: ts.Type): boolean => - tsutils.isBooleanLiteralType(type) || - type.flags === ts.TypeFlags.Undefined || - type.flags === ts.TypeFlags.Null || - type.flags === ts.TypeFlags.Void || - type.isLiteral(); +function toStaticValue( + type: ts.Type, +): + | { value: bigint | boolean | number | string | null | undefined } + | undefined { + // type.isLiteral() only covers numbers/bigints and strings, hence the rest of the branches. + if (tsutils.isBooleanLiteralType(type)) { + // Using `type.intrinsicName` instead of `type.value` because `type.value` + // is `undefined`, contrary to what the type guard tells us. + // See https://github.com/JoshuaKGoldberg/ts-api-utils/issues/528 + return { value: type.intrinsicName === 'true' }; + } + if (type.flags === ts.TypeFlags.Undefined) { + return { value: undefined }; + } + if (type.flags === ts.TypeFlags.Null) { + return { value: null }; + } + if (type.isLiteral()) { + return { value: getValueOfLiteralType(type) }; + } + + return undefined; +} + +function pseudoBigIntToBigInt(value: ts.PseudoBigInt): bigint { + return BigInt((value.negative ? '-' : '') + value.base10Value); +} + +const BOOL_OPERATORS = new Set([ + '<', + '>', + '<=', + '>=', + '==', + '===', + '!=', + '!==', +] as const); + +type BoolOperator = typeof BOOL_OPERATORS extends Set ? T : never; + +function isBoolOperator(operator: string): operator is BoolOperator { + return (BOOL_OPERATORS as Set).has(operator); +} + +function booleanComparison( + left: unknown, + operator: BoolOperator, + right: unknown, +): boolean { + switch (operator) { + case '!=': + // eslint-disable-next-line eqeqeq -- intentionally comparing with loose equality + return left != right; + case '!==': + return left !== right; + case '<': + // @ts-expect-error: we don't care if the comparison seems unintentional. + return left < right; + case '<=': + // @ts-expect-error: we don't care if the comparison seems unintentional. + return left <= right; + case '==': + // eslint-disable-next-line eqeqeq -- intentionally comparing with loose equality + return left == right; + case '===': + return left === right; + case '>': + // @ts-expect-error: we don't care if the comparison seems unintentional. + return left > right; + case '>=': + // @ts-expect-error: we don't care if the comparison seems unintentional. + return left >= right; + } +} + // #endregion export type Options = [ @@ -141,7 +212,7 @@ export default createRule({ alwaysTruthyFunc: 'This callback should return a conditional, but return is always truthy.', literalBooleanExpression: - 'Unnecessary conditional, both sides of the expression are literal values.', + 'Unnecessary conditional, comparison is always {{trueOrFalse}}. Both sides of the comparison always have a literal type.', never: 'Unnecessary conditional, value is `never`.', neverNullish: 'Unnecessary conditional, expected left-hand side of `??` operator to be possibly null or undefined.', @@ -397,19 +468,6 @@ export default createRule({ * - https://github.com/microsoft/TypeScript/issues/32627 * - https://github.com/microsoft/TypeScript/issues/37160 (handled) */ - const BOOL_OPERATORS = new Set([ - '<', - '>', - '<=', - '>=', - '==', - '===', - '!=', - '!==', - ] as const); - type BoolOperator = Parameters[0]; - const isBoolOperator = (operator: string): operator is BoolOperator => - (BOOL_OPERATORS as Set).has(operator); function checkIfBoolExpressionIsNecessaryConditional( node: TSESTree.Node, left: TSESTree.Node, @@ -418,10 +476,27 @@ export default createRule({ ): void { const leftType = getConstrainedTypeAtLocation(services, left); const rightType = getConstrainedTypeAtLocation(services, right); - if (isLiteral(leftType) && isLiteral(rightType)) { - context.report({ node, messageId: 'literalBooleanExpression' }); + + const leftStaticValue = toStaticValue(leftType); + const rightStaticValue = toStaticValue(rightType); + + if (leftStaticValue != null && rightStaticValue != null) { + const conditionIsTrue = booleanComparison( + leftStaticValue.value, + operator, + rightStaticValue.value, + ); + + context.report({ + node, + messageId: 'literalBooleanExpression', + data: { + trueOrFalse: conditionIsTrue ? 'true' : 'false', + }, + }); return; } + // Workaround for https://github.com/microsoft/TypeScript/issues/37160 if (isStrictNullChecks) { const UNDEFINED = ts.TypeFlags.Undefined; 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 d3042406ed1d..7aaed5539e21 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts @@ -1,7 +1,4 @@ -import type { - InvalidTestCase, - TestCaseError, -} from '@typescript-eslint/rule-tester'; +import type { InvalidTestCase } from '@typescript-eslint/rule-tester'; import { noFormat, RuleTester } from '@typescript-eslint/rule-tester'; import * as path from 'node:path'; @@ -30,16 +27,6 @@ const optionsWithExactOptionalPropertyTypes = { tsconfigRootDir: rootPath, }; -const ruleError = ( - line: number, - column: number, - messageId: MessageId, -): TestCaseError => ({ - column, - line, - messageId, -}); - const necessaryConditionTest = (condition: string): string => ` declare const b1: ${condition}; declare const b2: boolean; @@ -51,7 +38,7 @@ const unnecessaryConditionTest = ( messageId: MessageId, ): InvalidTestCase => ({ code: necessaryConditionTest(condition), - errors: [ruleError(4, 12, messageId)], + errors: [{ column: 12, line: 4, messageId }], }); ruleTester.run('no-unnecessary-condition', rule, { @@ -1022,18 +1009,22 @@ switch (b1) { } `, errors: [ - ruleError(4, 12, 'alwaysTruthy'), - ruleError(5, 12, 'alwaysTruthy'), - ruleError(6, 5, 'alwaysTruthy'), - ruleError(8, 11, 'alwaysTruthy'), - ruleError(10, 8, 'alwaysTruthy'), - ruleError(11, 14, 'alwaysTruthy'), - ruleError(12, 17, 'alwaysTruthy'), - ruleError(15, 12, 'alwaysTruthy'), - ruleError(16, 18, 'alwaysTruthy'), - ruleError(18, 8, 'literalBooleanExpression'), + { column: 12, line: 4, messageId: 'alwaysTruthy' }, + { column: 12, line: 5, messageId: 'alwaysTruthy' }, + { column: 5, line: 6, messageId: 'alwaysTruthy' }, + { column: 11, line: 8, messageId: 'alwaysTruthy' }, + { column: 8, line: 10, messageId: 'alwaysTruthy' }, + { column: 14, line: 11, messageId: 'alwaysTruthy' }, + { column: 17, line: 12, messageId: 'alwaysTruthy' }, + { column: 12, line: 15, messageId: 'alwaysTruthy' }, + { column: 18, line: 16, messageId: 'alwaysTruthy' }, + { + column: 8, + data: { trueOrFalse: 'true' }, + line: 18, + messageId: 'literalBooleanExpression', + }, ], - output: null, }, // Ensure that it's complaining about the right things unnecessaryConditionTest('object', 'alwaysTruthy'), @@ -1052,7 +1043,7 @@ declare const falseyBigInt: 0n; if (falseyBigInt) { } `, - errors: [ruleError(3, 5, 'alwaysFalsy')], + errors: [{ column: 5, line: 3, messageId: 'alwaysFalsy' }], }, { code: ` @@ -1060,7 +1051,7 @@ declare const posbigInt: 1n; if (posbigInt) { } `, - errors: [ruleError(3, 5, 'alwaysTruthy')], + errors: [{ column: 5, line: 3, messageId: 'alwaysTruthy' }], }, { code: ` @@ -1068,7 +1059,7 @@ declare const negBigInt: -2n; if (negBigInt) { } `, - errors: [ruleError(3, 5, 'alwaysTruthy')], + errors: [{ column: 5, line: 3, messageId: 'alwaysTruthy' }], }, { code: ` @@ -1082,11 +1073,10 @@ if (b1 || b2 || true) { } `, errors: [ - ruleError(4, 5, 'alwaysTruthy'), - ruleError(6, 11, 'alwaysFalsy'), - ruleError(8, 17, 'alwaysTruthy'), + { column: 5, line: 4, messageId: 'alwaysTruthy' }, + { column: 11, line: 6, messageId: 'alwaysFalsy' }, + { column: 17, line: 8, messageId: 'alwaysTruthy' }, ], - output: null, }, // Generic type params @@ -1096,8 +1086,7 @@ function test(t: T) { return t ? 'yes' : 'no'; } `, - errors: [ruleError(3, 10, 'alwaysTruthy')], - output: null, + errors: [{ column: 10, line: 3, messageId: 'alwaysTruthy' }], }, { code: ` @@ -1105,8 +1094,7 @@ function test(t: T) { return t ? 'yes' : 'no'; } `, - errors: [ruleError(3, 10, 'alwaysFalsy')], - output: null, + errors: [{ column: 10, line: 3, messageId: 'alwaysFalsy' }], }, { code: ` @@ -1114,8 +1102,7 @@ function test(t: T) { return t ? 'yes' : 'no'; } `, - errors: [ruleError(3, 10, 'alwaysTruthy')], - output: null, + errors: [{ column: 10, line: 3, messageId: 'alwaysTruthy' }], }, // Boolean expressions @@ -1125,8 +1112,32 @@ function test(a: 'a') { return a === 'a'; } `, - errors: [ruleError(3, 10, 'literalBooleanExpression')], - output: null, + errors: [ + { + column: 10, + data: { + trueOrFalse: 'true', + }, + line: 3, + messageId: 'literalBooleanExpression', + }, + ], + }, + { + code: ` +declare const a: '34'; +declare const b: '56'; +a > b; + `, + errors: [ + { + data: { + trueOrFalse: 'false', + }, + line: 4, + messageId: 'literalBooleanExpression', + }, + ], }, { code: ` @@ -1134,8 +1145,115 @@ const y = 1; if (y === 0) { } `, - errors: [ruleError(3, 5, 'literalBooleanExpression')], - output: null, + errors: [ + { + data: { trueOrFalse: 'false' }, + line: 3, + messageId: 'literalBooleanExpression', + }, + ], + }, + { + code: ` +// @ts-expect-error +if (1 == '1') { +} + `, + errors: [ + { + data: { trueOrFalse: 'true' }, + line: 3, + messageId: 'literalBooleanExpression', + }, + ], + }, + { + code: ` +2.3 > 2.3; + `, + errors: [ + { + data: { trueOrFalse: 'false' }, + line: 2, + messageId: 'literalBooleanExpression', + }, + ], + }, + { + code: ` +2.3 >= 2.3; + `, + errors: [ + { + data: { trueOrFalse: 'true' }, + line: 2, + messageId: 'literalBooleanExpression', + }, + ], + }, + { + code: ` +2n < 2n; + `, + errors: [ + { + data: { trueOrFalse: 'false' }, + line: 2, + messageId: 'literalBooleanExpression', + }, + ], + }, + { + code: ` +2n <= 2n; + `, + errors: [ + { + data: { trueOrFalse: 'true' }, + line: 2, + messageId: 'literalBooleanExpression', + }, + ], + }, + { + code: ` +-2n !== 2n; + `, + errors: [ + { + data: { trueOrFalse: 'true' }, + line: 2, + messageId: 'literalBooleanExpression', + }, + ], + }, + { + code: ` +// @ts-expect-error +if (1 == '2') { +} + `, + errors: [ + { + data: { trueOrFalse: 'false' }, + line: 3, + messageId: 'literalBooleanExpression', + }, + ], + }, + { + code: ` +// @ts-expect-error +if (1 != '2') { +} + `, + errors: [ + { + data: { trueOrFalse: 'true' }, + line: 3, + messageId: 'literalBooleanExpression', + }, + ], }, { code: ` @@ -1148,8 +1266,129 @@ const x = Foo.a; if (x === Foo.a) { } `, - errors: [ruleError(8, 5, 'literalBooleanExpression')], - output: null, + errors: [ + { + column: 5, + data: { + trueOrFalse: 'true', + }, + line: 8, + messageId: 'literalBooleanExpression', + }, + ], + }, + { + // narrowed to null. always-true because of loose nullish equality + code: ` +function takesMaybeValue(a: null | object) { + if (a) { + } else if (a == undefined) { + } +} + `, + errors: [ + { + column: 14, + data: { trueOrFalse: 'true' }, + endColumn: 28, + endLine: 4, + line: 4, + messageId: 'literalBooleanExpression', + }, + ], + }, + { + // narrowed to null. always-false because of strict undefined equality + code: ` +function takesMaybeValue(a: null | object) { + if (a) { + } else if (a === undefined) { + } +} + `, + errors: [ + { + column: 14, + data: { trueOrFalse: 'false' }, + endColumn: 29, + endLine: 4, + line: 4, + messageId: 'literalBooleanExpression', + }, + ], + }, + { + // narrowed to null. always-false because of loose nullish equality + code: ` +function takesMaybeValue(a: null | object) { + if (a) { + } else if (a != undefined) { + } +} + `, + errors: [ + { + column: 14, + data: { trueOrFalse: 'false' }, + endColumn: 28, + endLine: 4, + line: 4, + messageId: 'literalBooleanExpression', + }, + ], + }, + { + // narrowed to null. always-true because of strict undefined equality + code: ` +function takesMaybeValue(a: null | object) { + if (a) { + } else if (a !== undefined) { + } +} + `, + errors: [ + { + column: 14, + data: { trueOrFalse: 'true' }, + endColumn: 29, + endLine: 4, + line: 4, + messageId: 'literalBooleanExpression', + }, + ], + }, + { + code: ` +true === false; + `, + errors: [ + { + data: { trueOrFalse: 'false' }, + messageId: 'literalBooleanExpression', + }, + ], + }, + { + code: ` +true === true; + `, + errors: [ + { + data: { trueOrFalse: 'true' }, + messageId: 'literalBooleanExpression', + }, + ], + }, + { + code: ` +true === undefined; + `, + errors: [ + { + data: { trueOrFalse: 'false' }, + messageId: 'literalBooleanExpression', + }, + ], }, // Workaround https://github.com/microsoft/TypeScript/issues/37160 { @@ -1166,16 +1405,15 @@ function test(a: string) { } `, 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'), + { column: 14, line: 3, messageId: 'noOverlapBooleanExpression' }, + { column: 14, line: 4, messageId: 'noOverlapBooleanExpression' }, + { column: 14, line: 5, messageId: 'noOverlapBooleanExpression' }, + { column: 14, line: 6, messageId: 'noOverlapBooleanExpression' }, + { column: 14, line: 7, messageId: 'noOverlapBooleanExpression' }, + { column: 14, line: 8, messageId: 'noOverlapBooleanExpression' }, + { column: 14, line: 9, messageId: 'noOverlapBooleanExpression' }, + { column: 14, line: 10, messageId: 'noOverlapBooleanExpression' }, ], - output: null, }, { code: ` @@ -1191,12 +1429,11 @@ function test(a?: string) { } `, errors: [ - ruleError(7, 14, 'noOverlapBooleanExpression'), - ruleError(8, 14, 'noOverlapBooleanExpression'), - ruleError(9, 14, 'noOverlapBooleanExpression'), - ruleError(10, 14, 'noOverlapBooleanExpression'), + { column: 14, line: 7, messageId: 'noOverlapBooleanExpression' }, + { column: 14, line: 8, messageId: 'noOverlapBooleanExpression' }, + { column: 14, line: 9, messageId: 'noOverlapBooleanExpression' }, + { column: 14, line: 10, messageId: 'noOverlapBooleanExpression' }, ], - output: null, }, { code: ` @@ -1212,12 +1449,11 @@ function test(a: null | string) { } `, errors: [ - ruleError(3, 14, 'noOverlapBooleanExpression'), - ruleError(4, 14, 'noOverlapBooleanExpression'), - ruleError(5, 14, 'noOverlapBooleanExpression'), - ruleError(6, 14, 'noOverlapBooleanExpression'), + { column: 14, line: 3, messageId: 'noOverlapBooleanExpression' }, + { column: 14, line: 4, messageId: 'noOverlapBooleanExpression' }, + { column: 14, line: 5, messageId: 'noOverlapBooleanExpression' }, + { column: 14, line: 6, messageId: 'noOverlapBooleanExpression' }, ], - output: null, }, { code: ` @@ -1241,24 +1477,23 @@ function test(a: T) { } `, 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'), + { column: 14, line: 3, messageId: 'noOverlapBooleanExpression' }, + { column: 14, line: 4, messageId: 'noOverlapBooleanExpression' }, + { column: 14, line: 5, messageId: 'noOverlapBooleanExpression' }, + { column: 14, line: 6, messageId: 'noOverlapBooleanExpression' }, + { column: 14, line: 7, messageId: 'noOverlapBooleanExpression' }, + { column: 14, line: 8, messageId: 'noOverlapBooleanExpression' }, + { column: 14, line: 9, messageId: 'noOverlapBooleanExpression' }, + { column: 14, line: 10, messageId: 'noOverlapBooleanExpression' }, + { column: 14, line: 11, messageId: 'noOverlapBooleanExpression' }, + { column: 15, line: 12, messageId: 'noOverlapBooleanExpression' }, + { column: 15, line: 13, messageId: 'noOverlapBooleanExpression' }, + { column: 15, line: 14, messageId: 'noOverlapBooleanExpression' }, + { column: 15, line: 15, messageId: 'noOverlapBooleanExpression' }, + { column: 15, line: 16, messageId: 'noOverlapBooleanExpression' }, + { column: 15, line: 17, messageId: 'noOverlapBooleanExpression' }, + { column: 15, line: 18, messageId: 'noOverlapBooleanExpression' }, ], - output: null, }, // Nullish coalescing operator { @@ -1267,8 +1502,7 @@ function test(a: string) { return a ?? 'default'; } `, - errors: [ruleError(3, 10, 'neverNullish')], - output: null, + errors: [{ column: 10, line: 3, messageId: 'neverNullish' }], }, { code: ` @@ -1276,8 +1510,7 @@ function test(a: string | false) { return a ?? 'default'; } `, - errors: [ruleError(3, 10, 'neverNullish')], - output: null, + errors: [{ column: 10, line: 3, messageId: 'neverNullish' }], }, { code: ` @@ -1285,8 +1518,7 @@ function test(a: T) { return a ?? 'default'; } `, - errors: [ruleError(3, 10, 'neverNullish')], - output: null, + errors: [{ column: 10, line: 3, messageId: 'neverNullish' }], }, // nullish + array index without optional chaining { @@ -1295,8 +1527,7 @@ function test(a: { foo: string }[]) { return a[0].foo ?? 'default'; } `, - errors: [ruleError(3, 10, 'neverNullish')], - output: null, + errors: [{ column: 10, line: 3, messageId: 'neverNullish' }], }, { code: ` @@ -1304,8 +1535,7 @@ function test(a: null) { return a ?? 'default'; } `, - errors: [ruleError(3, 10, 'alwaysNullish')], - output: null, + errors: [{ column: 10, line: 3, messageId: 'alwaysNullish' }], }, { code: ` @@ -1313,8 +1543,7 @@ function test(a: null[]) { return a[0] ?? 'default'; } `, - errors: [ruleError(3, 10, 'alwaysNullish')], - output: null, + errors: [{ column: 10, line: 3, messageId: 'alwaysNullish' }], }, { code: ` @@ -1322,8 +1551,7 @@ function test(a: T) { return a ?? 'default'; } `, - errors: [ruleError(3, 10, 'alwaysNullish')], - output: null, + errors: [{ column: 10, line: 3, messageId: 'alwaysNullish' }], }, { code: ` @@ -1331,8 +1559,7 @@ function test(a: never) { return a ?? 'default'; } `, - errors: [ruleError(3, 10, 'never')], - output: null, + errors: [{ column: 10, line: 3, messageId: 'never' }], }, { code: ` @@ -1340,8 +1567,7 @@ function test(num: T[K]) { num ?? 'default'; } `, - errors: [ruleError(3, 3, 'neverNullish')], - output: null, + errors: [{ column: 3, line: 3, messageId: 'neverNullish' }], }, // Predicate functions { @@ -1365,13 +1591,12 @@ function nothing3(x: [string, string]) { } `, errors: [ - ruleError(2, 24, 'alwaysTruthy'), - ruleError(4, 10, 'alwaysFalsy'), - ruleError(9, 25, 'alwaysFalsy'), - ruleError(13, 25, 'alwaysFalsy'), - ruleError(17, 25, 'alwaysFalsy'), + { column: 24, line: 2, messageId: 'alwaysTruthy' }, + { column: 10, line: 4, messageId: 'alwaysFalsy' }, + { column: 25, line: 9, messageId: 'alwaysFalsy' }, + { column: 25, line: 13, messageId: 'alwaysFalsy' }, + { column: 25, line: 17, messageId: 'alwaysFalsy' }, ], - output: null, }, // Indexing cases { @@ -1382,8 +1607,7 @@ declare const dict: Record; if (dict['mightNotExist']) { } `, - errors: [ruleError(3, 5, 'alwaysTruthy')], - output: null, + errors: [{ column: 5, line: 3, messageId: 'alwaysTruthy' }], }, { // Should still check tuples when accessed with literal numbers, since they don't have @@ -1396,8 +1620,8 @@ if (x[0]?.foo) { } `, errors: [ - ruleError(3, 5, 'alwaysTruthy'), - ruleError(5, 9, 'neverOptionalChain'), + { column: 5, line: 3, messageId: 'alwaysTruthy' }, + { column: 9, line: 5, messageId: 'neverOptionalChain' }, ], output: ` const x = [{}] as [{ foo: string }]; @@ -1414,8 +1638,7 @@ declare const arr: object[]; if (arr.filter) { } `, - errors: [ruleError(3, 5, 'alwaysTruthy')], - output: null, + errors: [{ column: 5, line: 3, messageId: 'alwaysTruthy' }], }, { code: ` @@ -1428,11 +1651,10 @@ function falsy() {} [1, 2, 3].findLastIndex(falsy); `, errors: [ - ruleError(6, 18, 'alwaysTruthyFunc'), - ruleError(7, 16, 'alwaysFalsyFunc'), - ruleError(8, 25, 'alwaysFalsyFunc'), + { column: 18, line: 6, messageId: 'alwaysTruthyFunc' }, + { column: 16, line: 7, messageId: 'alwaysFalsyFunc' }, + { column: 25, line: 8, messageId: 'alwaysFalsyFunc' }, ], - output: null, }, // Supports generics // TODO: fix this @@ -1444,7 +1666,7 @@ function falsy() {} // // Invalid: arrays are always falsy. // [[1,2], [3,4]].filter(isTruthy); // `, - // errors: [ruleError(6, 23, 'alwaysTruthyFunc')], + // errors: [({ line: 6, column: 23, messageId: 'alwaysTruthyFunc' })], // }, { code: ` @@ -1453,12 +1675,11 @@ for (; true; ) {} do {} while (true); `, errors: [ - ruleError(2, 8, 'alwaysTruthy'), - ruleError(3, 8, 'alwaysTruthy'), - ruleError(4, 14, 'alwaysTruthy'), + { column: 8, line: 2, messageId: 'alwaysTruthy' }, + { column: 8, line: 3, messageId: 'alwaysTruthy' }, + { column: 14, line: 4, messageId: 'alwaysTruthy' }, ], options: [{ allowConstantLoopConditions: false }], - output: null, }, { code: noFormat` @@ -1896,7 +2117,7 @@ foo?.fooOrBar.baz?.qux; declare const x: { a: { b: number } }[]; x[0].a?.b; `, - errors: [ruleError(3, 7, 'neverOptionalChain')], + errors: [{ column: 7, line: 3, messageId: 'neverOptionalChain' }], output: ` declare const x: { a: { b: number } }[]; x[0].a.b; @@ -2044,7 +2265,6 @@ function test(testVal?: true) { messageId: 'alwaysTruthy', }, ], - output: null, }, // https://github.com/typescript-eslint/typescript-eslint/issues/2255 { @@ -2053,8 +2273,7 @@ const a = null; if (!a) { } `, - errors: [ruleError(3, 5, 'alwaysTruthy')], - output: null, + errors: [{ column: 5, line: 3, messageId: 'alwaysTruthy' }], }, { code: ` @@ -2062,8 +2281,7 @@ const a = true; if (!a) { } `, - errors: [ruleError(3, 5, 'alwaysFalsy')], - output: null, + errors: [{ column: 5, line: 3, messageId: 'alwaysFalsy' }], }, { code: ` @@ -2075,8 +2293,7 @@ let speech: never = sayHi(); if (!speech) { } `, - errors: [ruleError(7, 5, 'never')], - output: null, + errors: [{ column: 5, line: 7, messageId: 'never' }], }, { code: ` @@ -2101,7 +2318,6 @@ if (x) { tsconfigRootDir: path.join(rootPath, 'unstrict'), }, }, - output: null, }, { code: ` @@ -2158,7 +2374,6 @@ pick({ foo: 1, bar: 2 }, 'bar'); messageId: 'alwaysTruthy', }, ], - output: null, }, { code: ` @@ -2179,7 +2394,6 @@ function getElem(dict: Record, key: string) { messageId: 'alwaysTruthy', }, ], - output: null, }, { code: ` @@ -2195,7 +2409,6 @@ foo ??= 1; messageId: 'neverNullish', }, ], - output: null, }, { code: ` @@ -2211,7 +2424,6 @@ foo ??= 1; messageId: 'neverNullish', }, ], - output: null, }, { code: ` @@ -2227,7 +2439,6 @@ foo ??= null; messageId: 'alwaysNullish', }, ], - output: null, }, { code: ` @@ -2243,7 +2454,6 @@ foo ||= 1; messageId: 'alwaysTruthy', }, ], - output: null, }, { code: ` @@ -2259,7 +2469,6 @@ foo ||= null; messageId: 'alwaysFalsy', }, ], - output: null, }, { code: ` @@ -2275,7 +2484,6 @@ foo &&= 1; messageId: 'alwaysTruthy', }, ], - output: null, }, { code: ` @@ -2291,7 +2499,6 @@ foo &&= null; messageId: 'alwaysFalsy', }, ], - output: null, }, { code: ` @@ -2308,7 +2515,6 @@ foo.bar ??= 1; }, ], languageOptions: { parserOptions: optionsWithExactOptionalPropertyTypes }, - output: null, }, { code: ` @@ -2428,7 +2634,7 @@ foo?.['bar']?.().toExponential(); if (!!a) { } `, - errors: [ruleError(3, 13, 'alwaysTruthy')], + errors: [{ column: 13, line: 3, messageId: 'alwaysTruthy' }], }, { code: `