diff --git a/CHANGELOG.md b/CHANGELOG.md index 428ab6db594b..08cea6d1ebac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +## 8.46.1 (2025-10-13) + +### 🩹 Fixes + +- **ast-spec:** cleanup `TSLiteralType` ([#11624](https://github.com/typescript-eslint/typescript-eslint/pull/11624)) +- **eslint-plugin:** [prefer-optional-chain] include mixed "nullish comparison style" chains in checks ([#11533](https://github.com/typescript-eslint/typescript-eslint/pull/11533)) +- **eslint-plugin:** [no-misused-promises] special-case `.finally` not to report when a promise returning function is provided as an argument ([#11667](https://github.com/typescript-eslint/typescript-eslint/pull/11667)) + +### ❤️ Thank You + +- Abraham Guo +- mdm317 +- Ronen Amiel + +You can read about our [versioning strategy](https://typescript-eslint.io/users/versioning) and [releases](https://typescript-eslint.io/users/releases) on our website. + ## 8.46.0 (2025-10-06) ### 🚀 Features diff --git a/packages/ast-spec/CHANGELOG.md b/packages/ast-spec/CHANGELOG.md index c6cd653d3a17..219f836785a8 100644 --- a/packages/ast-spec/CHANGELOG.md +++ b/packages/ast-spec/CHANGELOG.md @@ -1,3 +1,15 @@ +## 8.46.1 (2025-10-13) + +### 🩹 Fixes + +- **ast-spec:** cleanup `TSLiteralType` ([#11624](https://github.com/typescript-eslint/typescript-eslint/pull/11624)) + +### ❤️ Thank You + +- Abraham Guo + +You can read about our [versioning strategy](https://typescript-eslint.io/users/versioning) and [releases](https://typescript-eslint.io/users/releases) on our website. + ## 8.46.0 (2025-10-06) ### 🚀 Features diff --git a/packages/ast-spec/package.json b/packages/ast-spec/package.json index bdd7c564bf29..e43f80e01f69 100644 --- a/packages/ast-spec/package.json +++ b/packages/ast-spec/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/ast-spec", - "version": "8.46.0", + "version": "8.46.1", "description": "Complete specification for the TypeScript-ESTree AST", "private": true, "keywords": [ diff --git a/packages/ast-spec/src/expression/UnaryExpression/spec.ts b/packages/ast-spec/src/expression/UnaryExpression/spec.ts index b5733e5ed85a..e68cc35047d0 100644 --- a/packages/ast-spec/src/expression/UnaryExpression/spec.ts +++ b/packages/ast-spec/src/expression/UnaryExpression/spec.ts @@ -1,7 +1,31 @@ import type { AST_NODE_TYPES } from '../../ast-node-types'; import type { UnaryExpressionBase } from '../../base/UnaryExpressionBase'; -export interface UnaryExpression extends UnaryExpressionBase { +interface UnaryExpressionSpecific + extends UnaryExpressionBase { type: AST_NODE_TYPES.UnaryExpression; - operator: '!' | '+' | '-' | 'delete' | 'typeof' | 'void' | '~'; + operator: T; } + +export type UnaryExpressionNot = UnaryExpressionSpecific<'!'>; + +export type UnaryExpressionPlus = UnaryExpressionSpecific<'+'>; + +export type UnaryExpressionMinus = UnaryExpressionSpecific<'-'>; + +export type UnaryExpressionDelete = UnaryExpressionSpecific<'delete'>; + +export type UnaryExpressionTypeof = UnaryExpressionSpecific<'typeof'>; + +export type UnaryExpressionVoid = UnaryExpressionSpecific<'void'>; + +export type UnaryExpressionBitwiseNot = UnaryExpressionSpecific<'~'>; + +export type UnaryExpression = + | UnaryExpressionBitwiseNot + | UnaryExpressionDelete + | UnaryExpressionMinus + | UnaryExpressionNot + | UnaryExpressionPlus + | UnaryExpressionTypeof + | UnaryExpressionVoid; diff --git a/packages/ast-spec/src/type/TSLiteralType/spec.ts b/packages/ast-spec/src/type/TSLiteralType/spec.ts index 39f6ae0d2961..7eca3ee2d9e5 100644 --- a/packages/ast-spec/src/type/TSLiteralType/spec.ts +++ b/packages/ast-spec/src/type/TSLiteralType/spec.ts @@ -1,10 +1,17 @@ import type { AST_NODE_TYPES } from '../../ast-node-types'; import type { BaseNode } from '../../base/BaseNode'; -import type { UnaryExpression } from '../../expression/UnaryExpression/spec'; -import type { UpdateExpression } from '../../expression/UpdateExpression/spec'; +import type { NullLiteral } from '../../expression/literal/NullLiteral/spec'; +import type { RegExpLiteral } from '../../expression/literal/RegExpLiteral/spec'; +import type { + UnaryExpressionMinus, + UnaryExpressionPlus, +} from '../../expression/UnaryExpression/spec'; import type { LiteralExpression } from '../../unions/LiteralExpression'; export interface TSLiteralType extends BaseNode { type: AST_NODE_TYPES.TSLiteralType; - literal: LiteralExpression | UnaryExpression | UpdateExpression; + literal: + | Exclude + | UnaryExpressionMinus + | UnaryExpressionPlus; } diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index 7be59f6e0e40..9e06dbd0a427 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -1,3 +1,17 @@ +## 8.46.1 (2025-10-13) + +### 🩹 Fixes + +- **eslint-plugin:** [no-misused-promises] special-case `.finally` not to report when a promise returning function is provided as an argument ([#11667](https://github.com/typescript-eslint/typescript-eslint/pull/11667)) +- **eslint-plugin:** [prefer-optional-chain] include mixed "nullish comparison style" chains in checks ([#11533](https://github.com/typescript-eslint/typescript-eslint/pull/11533)) + +### ❤️ Thank You + +- mdm317 +- Ronen Amiel + +You can read about our [versioning strategy](https://typescript-eslint.io/users/versioning) and [releases](https://typescript-eslint.io/users/releases) on our website. + ## 8.46.0 (2025-10-06) ### 🚀 Features diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index d4e233309acd..3e6ccd98b9a2 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/eslint-plugin", - "version": "8.46.0", + "version": "8.46.1", "description": "TypeScript plugin for ESLint", "files": [ "dist", @@ -59,10 +59,10 @@ }, "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.46.0", - "@typescript-eslint/type-utils": "8.46.0", - "@typescript-eslint/utils": "8.46.0", - "@typescript-eslint/visitor-keys": "8.46.0", + "@typescript-eslint/scope-manager": "8.46.1", + "@typescript-eslint/type-utils": "8.46.1", + "@typescript-eslint/utils": "8.46.1", + "@typescript-eslint/visitor-keys": "8.46.1", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -71,8 +71,8 @@ "devDependencies": { "@types/mdast": "^4.0.3", "@types/natural-compare": "*", - "@typescript-eslint/rule-schema-to-typescript-types": "8.46.0", - "@typescript-eslint/rule-tester": "8.46.0", + "@typescript-eslint/rule-schema-to-typescript-types": "8.46.1", + "@typescript-eslint/rule-tester": "8.46.1", "@vitest/coverage-v8": "^3.1.3", "ajv": "^6.12.6", "cross-fetch": "*", @@ -92,7 +92,7 @@ "vitest": "^3.1.3" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.46.0", + "@typescript-eslint/parser": "^8.46.1", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" }, diff --git a/packages/eslint-plugin/src/rules/no-base-to-string.ts b/packages/eslint-plugin/src/rules/no-base-to-string.ts index 0dbcfd2825ef..48fca65676ed 100644 --- a/packages/eslint-plugin/src/rules/no-base-to-string.ts +++ b/packages/eslint-plugin/src/rules/no-base-to-string.ts @@ -317,6 +317,7 @@ export default createRule({ const declarations = toString.getDeclarations(); + // eslint-disable-next-line @typescript-eslint/prefer-optional-chain if (declarations == null || declarations.length !== 1) { // If there are multiple declarations, at least one of them must not be // the default object toString. diff --git a/packages/eslint-plugin/src/rules/no-misused-promises.ts b/packages/eslint-plugin/src/rules/no-misused-promises.ts index aa225d4e0aa2..b791fe150584 100644 --- a/packages/eslint-plugin/src/rules/no-misused-promises.ts +++ b/packages/eslint-plugin/src/rules/no-misused-promises.ts @@ -6,14 +6,17 @@ import * as ts from 'typescript'; import { createRule, + getConstrainedTypeAtLocation, getFunctionHeadLoc, getParserServices, isArrayMethodCallWithPredicate, isFunction, + isPromiseLike, isRestParameterDeclaration, nullThrows, NullThrowsReasons, } from '../util'; +import { parseFinallyCall } from '../util/promiseUtils'; export type Options = [ { @@ -360,6 +363,13 @@ export default createRule({ function checkArguments( node: TSESTree.CallExpression | TSESTree.NewExpression, ): void { + if ( + node.type === AST_NODE_TYPES.CallExpression && + isPromiseFinallyMethod(node) + ) { + return; + } + const tsNode = services.esTreeNodeToTSNodeMap.get(node); const voidArgs = voidFunctionArguments(checker, tsNode); if (voidArgs.size === 0) { @@ -563,6 +573,18 @@ export default createRule({ } } + function isPromiseFinallyMethod(node: TSESTree.CallExpression): boolean { + const promiseFinallyCall = parseFinallyCall(node, context); + + return ( + promiseFinallyCall != null && + isPromiseLike( + services.program, + getConstrainedTypeAtLocation(services, promiseFinallyCall.object), + ) + ); + } + function checkClassLikeOrInterfaceNode( node: | TSESTree.ClassDeclaration diff --git a/packages/eslint-plugin/src/rules/prefer-includes.ts b/packages/eslint-plugin/src/rules/prefer-includes.ts index 6825fc042a6d..4da95f8f9866 100644 --- a/packages/eslint-plugin/src/rules/prefer-includes.ts +++ b/packages/eslint-plugin/src/rules/prefer-includes.ts @@ -39,7 +39,7 @@ export default createRule({ function isNumber(node: TSESTree.Node, value: number): boolean { const evaluated = getStaticValue(node, globalScope); - return evaluated != null && evaluated.value === value; + return evaluated?.value === value; } function isPositiveCheck(node: TSESTree.BinaryExpression): boolean { diff --git a/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/analyzeChain.ts b/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/analyzeChain.ts index c4364765f92c..84c1ca6e9593 100644 --- a/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/analyzeChain.ts +++ b/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/analyzeChain.ts @@ -10,10 +10,10 @@ import type { } from '@typescript-eslint/utils/ts-eslint'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import { unionConstituents } from 'ts-api-utils'; +import { isFalsyType, unionConstituents } from 'ts-api-utils'; import * as ts from 'typescript'; -import type { ValidOperand } from './gatherLogicalOperands'; +import type { LastChainOperand, ValidOperand } from './gatherLogicalOperands'; import type { PreferOptionalChainMessageIds, PreferOptionalChainOptions, @@ -31,7 +31,7 @@ import { } from '../../util'; import { checkNullishAndReport } from './checkNullishAndReport'; import { compareNodes, NodeComparisonResult } from './compareNodes'; -import { NullishComparisonType } from './gatherLogicalOperands'; +import { ComparisonType, NullishComparisonType } from './gatherLogicalOperands'; function includesType( parserServices: ParserServicesWithTypeInformation, @@ -48,6 +48,109 @@ function includesType( return false; } +function isAlwaysTruthyOperand( + comparedName: TSESTree.Node, + nullishComparisonType: NullishComparisonType, + parserServices: ParserServicesWithTypeInformation, +): boolean { + const ANY_UNKNOWN_FLAGS = ts.TypeFlags.Any | ts.TypeFlags.Unknown; + const comparedNameType = parserServices.getTypeAtLocation(comparedName); + + if (isTypeFlagSet(comparedNameType, ANY_UNKNOWN_FLAGS)) { + return false; + } + switch (nullishComparisonType) { + case NullishComparisonType.Boolean: + case NullishComparisonType.NotBoolean: { + const types = unionConstituents(comparedNameType); + return types.every(type => !isFalsyType(type)); + } + case NullishComparisonType.NotStrictEqualUndefined: + case NullishComparisonType.NotStrictEqualNull: + case NullishComparisonType.StrictEqualNull: + case NullishComparisonType.StrictEqualUndefined: + return !isTypeFlagSet( + comparedNameType, + ts.TypeFlags.Null | ts.TypeFlags.Undefined, + ); + case NullishComparisonType.NotEqualNullOrUndefined: + case NullishComparisonType.EqualNullOrUndefined: + return !isTypeFlagSet( + comparedNameType, + ts.TypeFlags.Null | ts.TypeFlags.Undefined, + ); + } +} + +function isValidAndLastChainOperand( + ComparisonValueType: TSESTree.Node, + comparisonType: ComparisonType, + parserServices: ParserServicesWithTypeInformation, +) { + const type = parserServices.getTypeAtLocation(ComparisonValueType); + const ANY_UNKNOWN_FLAGS = ts.TypeFlags.Any | ts.TypeFlags.Unknown; + + const types = unionConstituents(type); + switch (comparisonType) { + case ComparisonType.Equal: { + const isNullish = types.some(t => + isTypeFlagSet( + t, + ANY_UNKNOWN_FLAGS | ts.TypeFlags.Null | ts.TypeFlags.Undefined, + ), + ); + return !isNullish; + } + case ComparisonType.StrictEqual: { + const isUndefined = types.some(t => + isTypeFlagSet(t, ANY_UNKNOWN_FLAGS | ts.TypeFlags.Undefined), + ); + return !isUndefined; + } + case ComparisonType.NotStrictEqual: { + return types.every(t => isTypeFlagSet(t, ts.TypeFlags.Undefined)); + } + case ComparisonType.NotEqual: { + return types.every(t => + isTypeFlagSet(t, ts.TypeFlags.Undefined | ts.TypeFlags.Null), + ); + } + } +} +function isValidOrLastChainOperand( + ComparisonValueType: TSESTree.Node, + comparisonType: ComparisonType, + parserServices: ParserServicesWithTypeInformation, +) { + const type = parserServices.getTypeAtLocation(ComparisonValueType); + const ANY_UNKNOWN_FLAGS = ts.TypeFlags.Any | ts.TypeFlags.Unknown; + + const types = unionConstituents(type); + switch (comparisonType) { + case ComparisonType.NotEqual: { + const isNullish = types.some(t => + isTypeFlagSet( + t, + ANY_UNKNOWN_FLAGS | ts.TypeFlags.Null | ts.TypeFlags.Undefined, + ), + ); + return !isNullish; + } + case ComparisonType.NotStrictEqual: { + const isUndefined = types.some(t => + isTypeFlagSet(t, ANY_UNKNOWN_FLAGS | ts.TypeFlags.Undefined), + ); + return !isUndefined; + } + case ComparisonType.Equal: + return types.every(t => + isTypeFlagSet(t, ts.TypeFlags.Undefined | ts.TypeFlags.Null), + ); + case ComparisonType.StrictEqual: + return types.every(t => isTypeFlagSet(t, ts.TypeFlags.Undefined)); + } +} + // I hate that these functions are identical aside from the enum values used // I can't think of a good way to reuse the code here in a way that will preserve // the type safety and simplicity. @@ -65,18 +168,7 @@ const analyzeAndChainOperand: OperandAnalyzer = ( chain, ) => { switch (operand.comparisonType) { - case NullishComparisonType.Boolean: { - const nextOperand = chain.at(index + 1); - if ( - nextOperand?.comparisonType === - NullishComparisonType.NotStrictEqualNull && - operand.comparedName.type === AST_NODE_TYPES.Identifier - ) { - return null; - } - return [operand]; - } - + case NullishComparisonType.Boolean: case NullishComparisonType.NotEqualNullOrUndefined: return [operand]; @@ -92,7 +184,8 @@ const analyzeAndChainOperand: OperandAnalyzer = ( return [operand, nextOperand]; } if ( - includesType( + nextOperand && + !includesType( parserServices, operand.comparedName, ts.TypeFlags.Undefined, @@ -101,10 +194,9 @@ const analyzeAndChainOperand: OperandAnalyzer = ( // we know the next operand is not an `undefined` check and that this // operand includes `undefined` - which means that making this an // optional chain would change the runtime behavior of the expression - return null; + return [operand]; } - - return [operand]; + return null; } case NullishComparisonType.NotStrictEqualUndefined: { @@ -156,6 +248,7 @@ const analyzeOrChainOperand: OperandAnalyzer = ( ) { return [operand, nextOperand]; } + if ( includesType( parserServices, @@ -168,7 +261,6 @@ const analyzeOrChainOperand: OperandAnalyzer = ( // optional chain would change the runtime behavior of the expression return null; } - return [operand]; } @@ -207,7 +299,7 @@ const analyzeOrChainOperand: OperandAnalyzer = ( * @returns The range to report. */ function getReportRange( - chain: ValidOperand[], + chain: { node: TSESTree.Expression }[], boundary: TSESTree.Range, sourceCode: SourceCode, ): TSESTree.Range { @@ -247,8 +339,10 @@ function getReportDescriptor( node: TSESTree.Node, operator: '&&' | '||', options: PreferOptionalChainOptions, - chain: ValidOperand[], + subChain: ValidOperand[], + lastChain: (LastChainOperand | ValidOperand) | undefined, ): ReportDescriptor { + const chain = lastChain ? [...subChain, lastChain] : subChain; const lastOperand = chain[chain.length - 1]; let useSuggestionFixer: boolean; @@ -264,6 +358,7 @@ function getReportDescriptor( // `undefined`, or else we're going to change the final type - which is // unsafe and might cause downstream type errors. else if ( + lastChain || lastOperand.comparisonType === NullishComparisonType.EqualNullOrUndefined || lastOperand.comparisonType === NullishComparisonType.NotEqualNullOrUndefined || @@ -521,10 +616,11 @@ export function analyzeChain( node: TSESTree.Node, operator: TSESTree.LogicalExpression['operator'], chain: ValidOperand[], + lastChainOperand?: LastChainOperand, ): void { // need at least 2 operands in a chain for it to be a chain if ( - chain.length <= 1 || + chain.length + (lastChainOperand ? 1 : 0) <= 1 || /* istanbul ignore next -- previous checks make this unreachable, but keep it for exhaustiveness check */ operator === '??' ) { @@ -544,16 +640,20 @@ export function analyzeChain( // Things like x !== null && x !== undefined have two nodes, but they are // one logical unit here, so we'll allow them to be grouped. let subChain: (readonly ValidOperand[] | ValidOperand)[] = []; + let lastChain: LastChainOperand | ValidOperand | undefined = undefined; const maybeReportThenReset = ( newChainSeed?: readonly [ValidOperand, ...ValidOperand[]], ): void => { - if (subChain.length > 1) { + if (subChain.length + (lastChain ? 1 : 0) > 1) { const subChainFlat = subChain.flat(); + const maybeNullishNodes = lastChain + ? subChainFlat.map(({ node }) => node) + : subChainFlat.slice(0, -1).map(({ node }) => node); checkNullishAndReport( context, parserServices, options, - subChainFlat.slice(0, -1).map(({ node }) => node), + maybeNullishNodes, getReportDescriptor( context.sourceCode, parserServices, @@ -561,6 +661,7 @@ export function analyzeChain( operator, options, subChainFlat, + lastChain, ), ); } @@ -578,6 +679,7 @@ export function analyzeChain( // ^^^^^^^^^^^ newChainSeed // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ second chain subChain = newChainSeed ? [newChainSeed] : []; + lastChain = undefined; }; for (let i = 0; i < chain.length; i += 1) { @@ -595,6 +697,35 @@ export function analyzeChain( // ^^^^^^^ invalid OR chain logical, but still part of // the chain for combination purposes + if (lastOperand) { + const comparisonResult = compareNodes( + lastOperand.comparedName, + operand.comparedName, + ); + switch (operand.comparisonType) { + case NullishComparisonType.StrictEqualUndefined: + case NullishComparisonType.NotStrictEqualUndefined: { + if (comparisonResult === NodeComparisonResult.Subset) { + lastChain = operand; + } + break; + } + case NullishComparisonType.StrictEqualNull: + case NullishComparisonType.NotStrictEqualNull: { + if ( + comparisonResult === NodeComparisonResult.Subset && + isAlwaysTruthyOperand( + lastOperand.comparedName, + lastOperand.comparisonType, + parserServices, + ) + ) { + lastChain = operand; + } + break; + } + } + } maybeReportThenReset(); continue; } @@ -624,7 +755,33 @@ export function analyzeChain( subChain.push(currentOperand); } } + const lastOperand = subChain.flat().at(-1); + if (lastOperand && lastChainOperand) { + const comparisonResult = compareNodes( + lastOperand.comparedName, + lastChainOperand.comparedName, + ); + const isValidLastChainOperand = + operator === '&&' + ? isValidAndLastChainOperand + : isValidOrLastChainOperand; + if ( + comparisonResult === NodeComparisonResult.Subset && + (isAlwaysTruthyOperand( + lastOperand.comparedName, + lastOperand.comparisonType, + parserServices, + ) || + isValidLastChainOperand( + lastChainOperand.comparisonValue, + lastChainOperand.comparisonType, + parserServices, + )) + ) { + lastChain = lastChainOperand; + } + } // check the leftovers maybeReportThenReset(); } diff --git a/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.ts b/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.ts index fdb6996c612d..9092e1e67a75 100644 --- a/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.ts +++ b/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.ts @@ -25,6 +25,7 @@ const enum ComparisonValueType { } export const enum OperandValidity { Valid = 'Valid', + Last = 'Last', Invalid = 'Invalid', } export const enum NullishComparisonType { @@ -48,6 +49,12 @@ export const enum NullishComparisonType { /** `x` */ Boolean = 'Boolean', // eslint-disable-line @typescript-eslint/internal/prefer-ast-types-enum } +export const enum ComparisonType { + NotEqual = 'NotEqual', + Equal = 'Equal', + NotStrictEqual = 'NotStrictEqual', + StrictEqual = 'StrictEqual', +} export interface ValidOperand { comparedName: TSESTree.Node; comparisonType: NullishComparisonType; @@ -55,10 +62,18 @@ export interface ValidOperand { node: TSESTree.Expression; type: OperandValidity.Valid; } +export interface LastChainOperand { + comparedName: TSESTree.Node; + comparisonType: ComparisonType; + comparisonValue: TSESTree.Node; + isYoda: boolean; + node: TSESTree.BinaryExpression; + type: OperandValidity.Last; +} export interface InvalidOperand { type: OperandValidity.Invalid; } -type Operand = InvalidOperand | ValidOperand; +type Operand = InvalidOperand | LastChainOperand | ValidOperand; const NULLISH_FLAGS = ts.TypeFlags.Null | ts.TypeFlags.Undefined; function isValidFalseBooleanCheckType( @@ -182,61 +197,101 @@ export function gatherLogicalOperands( continue; } - switch (operand.operator) { - case '!=': - case '==': - if ( - comparedValue === ComparisonValueType.Null || - comparedValue === ComparisonValueType.Undefined - ) { - // x == null, x == undefined - result.push({ - comparedName: comparedExpression, - comparisonType: operand.operator.startsWith('!') - ? NullishComparisonType.NotEqualNullOrUndefined - : NullishComparisonType.EqualNullOrUndefined, - isYoda, - node: operand, - type: OperandValidity.Valid, - }); - continue; - } - // x == something :( - result.push({ type: OperandValidity.Invalid }); - continue; - - case '!==': - case '===': { - const comparedName = comparedExpression; - switch (comparedValue) { - case ComparisonValueType.Null: + if (operand.operator.startsWith('!') !== (node.operator === '||')) { + switch (operand.operator) { + case '!=': + case '==': + if ( + comparedValue === ComparisonValueType.Null || + comparedValue === ComparisonValueType.Undefined + ) { + // x == null, x == undefined result.push({ - comparedName, + comparedName: comparedExpression, comparisonType: operand.operator.startsWith('!') - ? NullishComparisonType.NotStrictEqualNull - : NullishComparisonType.StrictEqualNull, + ? NullishComparisonType.NotEqualNullOrUndefined + : NullishComparisonType.EqualNullOrUndefined, isYoda, node: operand, type: OperandValidity.Valid, }); continue; + } + break; + + case '!==': + case '===': { + const comparedName = comparedExpression; + switch (comparedValue) { + case ComparisonValueType.Null: + result.push({ + comparedName, + comparisonType: operand.operator.startsWith('!') + ? NullishComparisonType.NotStrictEqualNull + : NullishComparisonType.StrictEqualNull, + isYoda, + node: operand, + type: OperandValidity.Valid, + }); + continue; + + case ComparisonValueType.Undefined: + result.push({ + comparedName, + comparisonType: operand.operator.startsWith('!') + ? NullishComparisonType.NotStrictEqualUndefined + : NullishComparisonType.StrictEqualUndefined, + isYoda, + node: operand, + type: OperandValidity.Valid, + }); + continue; + } + } + } + } - case ComparisonValueType.Undefined: - result.push({ - comparedName, - comparisonType: operand.operator.startsWith('!') - ? NullishComparisonType.NotStrictEqualUndefined - : NullishComparisonType.StrictEqualUndefined, - isYoda, - node: operand, - type: OperandValidity.Valid, - }); - continue; + // x == something :( + // x === something :( + // x != something :( + // x !== something :( + const binaryComparisonChain = getBinaryComparisonChain(operand); + if (binaryComparisonChain) { + const { comparedName, comparedValue, isYoda } = binaryComparisonChain; + + switch (operand.operator) { + case '==': + case '===': { + const comparisonType = + operand.operator === '==' + ? ComparisonType.Equal + : ComparisonType.StrictEqual; + result.push({ + comparedName, + comparisonType, + comparisonValue: comparedValue, + isYoda, + node: operand, + type: OperandValidity.Last, + }); + continue; + } - default: - // x === something :( - result.push({ type: OperandValidity.Invalid }); - continue; + case '!=': + case '!==': { + const comparisonType = + operand.operator === '!=' + ? ComparisonType.NotEqual + : ComparisonType.NotStrictEqual; + result.push({ + comparedName, + comparisonType, + comparisonValue: comparedValue, + isYoda, + node: operand, + type: OperandValidity.Last, + }); + continue; } } } @@ -374,4 +429,32 @@ export function gatherLogicalOperands( return null; } + + function getBinaryComparisonChain(node: TSESTree.BinaryExpression) { + const { left, right } = node; + let isYoda = false; + const isLeftMemberExpression = + left.type === AST_NODE_TYPES.MemberExpression; + const isRightMemberExpression = + right.type === AST_NODE_TYPES.MemberExpression; + if (isLeftMemberExpression && !isRightMemberExpression) { + const [comparedName, comparedValue] = [left, right]; + return { + comparedName, + comparedValue, + isYoda, + }; + } + if (!isLeftMemberExpression && isRightMemberExpression) { + const [comparedName, comparedValue] = [right, left]; + + isYoda = true; + return { + comparedName, + comparedValue, + isYoda, + }; + } + return null; + } } diff --git a/packages/eslint-plugin/src/rules/prefer-optional-chain.ts b/packages/eslint-plugin/src/rules/prefer-optional-chain.ts index 241f4240e78d..c3d61c0f0f70 100644 --- a/packages/eslint-plugin/src/rules/prefer-optional-chain.ts +++ b/packages/eslint-plugin/src/rules/prefer-optional-chain.ts @@ -138,6 +138,17 @@ export default createRule< currentChain, ); currentChain = []; + } else if (operand.type === OperandValidity.Last) { + analyzeChain( + context, + parserServices, + options, + node, + node.operator, + currentChain, + operand, + ); + currentChain = []; } else { currentChain.push(operand); } diff --git a/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts b/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts index 39125b9dc891..8d15ad78e574 100644 --- a/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts +++ b/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts @@ -100,7 +100,7 @@ export default createRule({ value: number, ): node is TSESTree.Literal { const evaluated = getStaticValue(node, globalScope); - return evaluated != null && evaluated.value === value; + return evaluated?.value === value; } /** diff --git a/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts b/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts index 5f7f1faf8bb4..dc671e8869d6 100644 --- a/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts +++ b/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts @@ -1096,6 +1096,13 @@ declare const useCallback: unknown>( ) => T; useCallback(async () => {}); `, + ` +Promise.reject(3).finally(async () => {}); + `, + ` +const f = 'finally'; +Promise.reject(3)[f](async () => {}); + `, ], invalid: [ diff --git a/packages/eslint-plugin/tests/rules/prefer-optional-chain/prefer-optional-chain.test.ts b/packages/eslint-plugin/tests/rules/prefer-optional-chain/prefer-optional-chain.test.ts index b72289d020b2..a990d70b60bb 100644 --- a/packages/eslint-plugin/tests/rules/prefer-optional-chain/prefer-optional-chain.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-optional-chain/prefer-optional-chain.test.ts @@ -698,6 +698,1540 @@ describe('|| {}', () => { }); }); +describe('chain ending with comparison', () => { + ruleTester.run('prefer-optional-chain', rule, { + invalid: [ + { + code: 'foo && foo.bar == 0;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar == 0;`, + }, + { + code: 'foo && foo.bar == 1;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar == 1;`, + }, + { + code: "foo && foo.bar == '123';", + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar == '123';`, + }, + { + code: 'foo && foo.bar == {};', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar == {};`, + }, + { + code: 'foo && foo.bar == false;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar == false;`, + }, + { + code: 'foo && foo.bar == true;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar == true;`, + }, + { + code: 'foo && foo.bar === 0;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar === 0;`, + }, + { + code: 'foo && foo.bar === 1;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar === 1;`, + }, + { + code: "foo && foo.bar === '123';", + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar === '123';`, + }, + { + code: 'foo && foo.bar === {};', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar === {};`, + }, + { + code: 'foo && foo.bar === false;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar === false;`, + }, + { + code: 'foo && foo.bar === true;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar === true;`, + }, + { + code: 'foo && foo.bar === null;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar === null;`, + }, + { + code: 'foo && foo.bar !== undefined;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar !== undefined;`, + }, + { + code: 'foo && foo.bar != undefined;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar != undefined;`, + }, + { + code: 'foo && foo.bar != null;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar != null;`, + }, + { + code: 'foo != null && foo.bar == 0;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar == 0;`, + }, + { + code: 'foo != null && foo.bar == 1;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar == 1;`, + }, + { + code: "foo != null && foo.bar == '123';", + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar == '123';`, + }, + { + code: 'foo != null && foo.bar == {};', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar == {};`, + }, + { + code: 'foo != null && foo.bar == false;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar == false;`, + }, + { + code: 'foo != null && foo.bar == true;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar == true;`, + }, + { + code: 'foo != null && foo.bar === 0;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar === 0;`, + }, + { + code: 'foo != null && foo.bar === 1;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar === 1;`, + }, + { + code: "foo != null && foo.bar === '123';", + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar === '123';`, + }, + { + code: 'foo != null && foo.bar === {};', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar === {};`, + }, + { + code: 'foo != null && foo.bar === false;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar === false;`, + }, + { + code: 'foo != null && foo.bar === true;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar === true;`, + }, + { + code: 'foo != null && foo.bar === null;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar === null;`, + }, + { + code: 'foo != null && foo.bar !== undefined;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar !== undefined;`, + }, + { + code: 'foo != null && foo.bar != undefined;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar != undefined;`, + }, + { + code: 'foo != null && foo.bar != null;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar != null;`, + }, + { + code: ` + declare const foo: { bar: number }; + foo && foo.bar == x; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar == x; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo && foo.bar == null; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar == null; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo && foo.bar == undefined; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar == undefined; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo && foo.bar === x; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar === x; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo && foo.bar === undefined; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar === undefined; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo && foo.bar !== 0; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar !== 0; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo && foo.bar !== 1; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar !== 1; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo && foo.bar !== '123'; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar !== '123'; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo && foo.bar !== {}; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar !== {}; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo && foo.bar !== false; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar !== false; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo && foo.bar !== true; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar !== true; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo && foo.bar !== null; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar !== null; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo && foo.bar !== x; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar !== x; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo && foo.bar != 0; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar != 0; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo && foo.bar != 1; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar != 1; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo && foo.bar != '123'; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar != '123'; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo && foo.bar != {}; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar != {}; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo && foo.bar != false; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar != false; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo && foo.bar != true; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar != true; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo && foo.bar != null; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar != null; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo && foo.bar != x; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar != x; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo != null && foo.bar == x; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar == x; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo != null && foo.bar == null; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar == null; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo != null && foo.bar == undefined; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar == undefined; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo != null && foo.bar === x; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar === x; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo != null && foo.bar === undefined; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar === undefined; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo != null && foo.bar !== 0; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar !== 0; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo != null && foo.bar !== 1; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar !== 1; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo != null && foo.bar !== '123'; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar !== '123'; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo != null && foo.bar !== {}; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar !== {}; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo != null && foo.bar !== false; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar !== false; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo != null && foo.bar !== true; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar !== true; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo != null && foo.bar !== null; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar !== null; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo != null && foo.bar !== x; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar !== x; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo != null && foo.bar != 0; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar != 0; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo != null && foo.bar != 1; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar != 1; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo != null && foo.bar != '123'; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar != '123'; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo != null && foo.bar != {}; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar != {}; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo != null && foo.bar != false; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar != false; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo != null && foo.bar != true; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar != true; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo != null && foo.bar != null; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar != null; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo != null && foo.bar != x; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar != x; + `, + }, + { + code: ` + declare const foo: { bar: number } | 1; + foo && foo.bar == x; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number } | 1; + foo?.bar == x; + `, + }, + { + code: ` + declare const foo: { bar: number } | 0; + foo != null && foo.bar == x; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number } | 0; + foo?.bar == x; + `, + }, + { + code: '!foo || foo.bar != 0;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar != 0;`, + }, + { + code: '!foo || foo.bar != 1;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar != 1;`, + }, + { + code: "!foo || foo.bar != '123';", + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar != '123';`, + }, + { + code: '!foo || foo.bar != {};', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar != {};`, + }, + { + code: '!foo || foo.bar != false;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar != false;`, + }, + { + code: '!foo || foo.bar != true;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar != true;`, + }, + { + code: '!foo || foo.bar === undefined;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar === undefined;`, + }, + { + code: '!foo || foo.bar == undefined;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar == undefined;`, + }, + { + code: '!foo || foo.bar == null;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar == null;`, + }, + { + code: '!foo || foo.bar !== 0;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar !== 0;`, + }, + { + code: '!foo || foo.bar !== 1;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar !== 1;`, + }, + { + code: "!foo || foo.bar !== '123';", + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar !== '123';`, + }, + { + code: '!foo || foo.bar !== {};', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar !== {};`, + }, + { + code: '!foo || foo.bar !== false;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar !== false;`, + }, + { + code: '!foo || foo.bar !== true;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar !== true;`, + }, + { + code: '!foo || foo.bar !== null;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar !== null;`, + }, + { + code: 'foo == null || foo.bar != 0;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar != 0;`, + }, + { + code: 'foo == null || foo.bar != 1;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar != 1;`, + }, + { + code: "foo == null || foo.bar != '123';", + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar != '123';`, + }, + { + code: 'foo == null || foo.bar != {};', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar != {};`, + }, + { + code: 'foo == null || foo.bar != false;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar != false;`, + }, + { + code: 'foo == null || foo.bar != true;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar != true;`, + }, + { + code: 'foo == null || foo.bar === undefined;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar === undefined;`, + }, + { + code: 'foo == null || foo.bar == undefined;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar == undefined;`, + }, + { + code: 'foo == null || foo.bar == null;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar == null;`, + }, + { + code: 'foo == null || foo.bar !== 0;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar !== 0;`, + }, + { + code: 'foo == null || foo.bar !== 1;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar !== 1;`, + }, + { + code: "foo == null || foo.bar !== '123';", + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar !== '123';`, + }, + { + code: 'foo == null || foo.bar !== {};', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar !== {};`, + }, + { + code: 'foo == null || foo.bar !== false;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar !== false;`, + }, + { + code: 'foo == null || foo.bar !== true;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar !== true;`, + }, + { + code: 'foo == null || foo.bar !== null;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `foo?.bar !== null;`, + }, + { + code: ` + declare const foo: { bar: number }; + !foo || foo.bar == x; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar == x; + `, + }, + { + code: ` + declare const foo: { bar: number }; + !foo || foo.bar == null; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar == null; + `, + }, + { + code: ` + declare const foo: { bar: number }; + !foo || foo.bar == undefined; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar == undefined; + `, + }, + { + code: ` + declare const foo: { bar: number }; + !foo || foo.bar === x; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar === x; + `, + }, + { + code: ` + declare const foo: { bar: number }; + !foo || foo.bar === undefined; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar === undefined; + `, + }, + { + code: ` + declare const foo: { bar: number }; + !foo || foo.bar !== 0; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar !== 0; + `, + }, + { + code: ` + declare const foo: { bar: number }; + !foo || foo.bar !== 1; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar !== 1; + `, + }, + { + code: ` + declare const foo: { bar: number }; + !foo || foo.bar !== '123'; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar !== '123'; + `, + }, + { + code: ` + declare const foo: { bar: number }; + !foo || foo.bar !== {}; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar !== {}; + `, + }, + { + code: ` + declare const foo: { bar: number }; + !foo || foo.bar !== false; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar !== false; + `, + }, + { + code: ` + declare const foo: { bar: number }; + !foo || foo.bar !== true; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar !== true; + `, + }, + { + code: ` + declare const foo: { bar: number }; + !foo || foo.bar !== null; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar !== null; + `, + }, + { + code: ` + declare const foo: { bar: number }; + !foo || foo.bar !== x; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar !== x; + `, + }, + { + code: ` + declare const foo: { bar: number }; + !foo || foo.bar != 0; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar != 0; + `, + }, + { + code: ` + declare const foo: { bar: number }; + !foo || foo.bar != 1; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar != 1; + `, + }, + { + code: ` + declare const foo: { bar: number }; + !foo || foo.bar != '123'; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar != '123'; + `, + }, + { + code: ` + declare const foo: { bar: number }; + !foo || foo.bar != {}; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar != {}; + `, + }, + { + code: ` + declare const foo: { bar: number }; + !foo || foo.bar != false; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar != false; + `, + }, + { + code: ` + declare const foo: { bar: number }; + !foo || foo.bar != true; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar != true; + `, + }, + { + code: ` + declare const foo: { bar: number }; + !foo || foo.bar != null; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar != null; + `, + }, + { + code: ` + declare const foo: { bar: number }; + !foo || foo.bar != x; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar != x; + `, + }, + + { + code: ` + declare const foo: { bar: number }; + foo == null || foo.bar == x; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar == x; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo == null || foo.bar == null; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar == null; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo == null || foo.bar == undefined; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar == undefined; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo == null || foo.bar === x; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar === x; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo == null || foo.bar === undefined; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar === undefined; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo == null || foo.bar !== 0; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar !== 0; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo == null || foo.bar !== 1; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar !== 1; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo == null || foo.bar !== '123'; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar !== '123'; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo == null || foo.bar !== {}; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar !== {}; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo == null || foo.bar !== false; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar !== false; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo == null || foo.bar !== true; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar !== true; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo == null || foo.bar !== null; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar !== null; + `, + }, + { + code: ` + declare const foo: { bar: number }; + foo == null || foo.bar !== x; + `, + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: ` + declare const foo: { bar: number }; + foo?.bar !== x; + `, + }, + // yoda case + { + code: "foo != null && null != foo.bar && '123' == foo.bar.baz;", + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `'123' == foo?.bar?.baz;`, + }, + { + code: "foo != null && null != foo.bar && '123' === foo.bar.baz;", + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `'123' === foo?.bar?.baz;`, + }, + { + code: 'foo != null && null != foo.bar && undefined !== foo.bar.baz;', + errors: [{ messageId: 'preferOptionalChain', suggestions: null }], + output: `undefined !== foo?.bar?.baz;`, + }, + ], + valid: [ + 'foo && foo.bar == x;', + 'foo && foo.bar == null;', + 'foo && foo.bar == undefined;', + 'foo && foo.bar === x;', + 'foo && foo.bar === undefined;', + 'foo && foo.bar !== 0;', + 'foo && foo.bar !== 1;', + "foo && foo.bar !== '123';", + 'foo && foo.bar !== {};', + 'foo && foo.bar !== false;', + 'foo && foo.bar !== true;', + 'foo && foo.bar !== null;', + 'foo && foo.bar !== x;', + 'foo && foo.bar != 0;', + 'foo && foo.bar != 1;', + "foo && foo.bar != '123';", + 'foo && foo.bar != {};', + 'foo && foo.bar != false;', + 'foo && foo.bar != true;', + 'foo && foo.bar != x;', + 'foo != null && foo.bar == x;', + 'foo != null && foo.bar == null;', + 'foo != null && foo.bar == undefined;', + 'foo != null && foo.bar === x;', + 'foo != null && foo.bar === undefined;', + 'foo != null && foo.bar !== 0;', + 'foo != null && foo.bar !== 1;', + "foo != null && foo.bar !== '123';", + 'foo != null && foo.bar !== {};', + 'foo != null && foo.bar !== false;', + 'foo != null && foo.bar !== true;', + 'foo != null && foo.bar !== null;', + 'foo != null && foo.bar !== x;', + 'foo != null && foo.bar != 0;', + 'foo != null && foo.bar != 1;', + "foo != null && foo.bar != '123';", + 'foo != null && foo.bar != {};', + 'foo != null && foo.bar != false;', + 'foo != null && foo.bar != true;', + 'foo != null && foo.bar != x;', + ` + declare const foo: { bar: number } | null; + foo && foo.bar == x; + `, + ` + declare const foo: { bar: number } | null; + foo && foo.bar == null; + `, + ` + declare const foo: { bar: number } | null; + foo && foo.bar == undefined; + `, + ` + declare const foo: { bar: number } | null; + foo && foo.bar === x; + `, + ` + declare const foo: { bar: number } | null; + foo && foo.bar === undefined; + `, + ` + declare const foo: { bar: number } | null; + foo && foo.bar !== 0; + `, + ` + declare const foo: { bar: number } | null; + foo && foo.bar !== 1; + `, + ` + declare const foo: { bar: number } | null; + foo && foo.bar !== '123'; + `, + ` + declare const foo: { bar: number } | null; + foo && foo.bar !== {}; + `, + ` + declare const foo: { bar: number } | null; + foo && foo.bar !== false; + `, + ` + declare const foo: { bar: number } | null; + foo && foo.bar !== true; + `, + ` + declare const foo: { bar: number } | null; + foo && foo.bar !== null; + `, + ` + declare const foo: { bar: number } | null; + foo && foo.bar !== x; + `, + ` + declare const foo: { bar: number } | null; + foo != null && foo.bar == x; + `, + ` + declare const foo: { bar: number } | null; + foo != null && foo.bar == null; + `, + ` + declare const foo: { bar: number } | null; + foo != null && foo.bar == undefined; + `, + ` + declare const foo: { bar: number } | null; + foo != null && foo.bar === x; + `, + ` + declare const foo: { bar: number } | null; + foo != null && foo.bar === undefined; + `, + ` + declare const foo: { bar: number } | null; + foo != null && foo.bar !== 0; + `, + ` + declare const foo: { bar: number } | null; + foo != null && foo.bar !== 1; + `, + ` + declare const foo: { bar: number } | null; + foo != null && foo.bar !== '123'; + `, + ` + declare const foo: { bar: number } | null; + foo != null && foo.bar !== {}; + `, + ` + declare const foo: { bar: number } | null; + foo != null && foo.bar !== false; + `, + ` + declare const foo: { bar: number } | null; + foo != null && foo.bar !== true; + `, + ` + declare const foo: { bar: number } | null; + foo != null && foo.bar !== null; + `, + ` + declare const foo: { bar: number } | null; + foo != null && foo.bar !== x; + `, + ` + declare const foo: { bar: number } | null; + foo !== null && foo !== undefined && foo.bar == null; + `, + ` + declare const foo: { bar: number } | null; + foo !== null && foo !== undefined && foo.bar === undefined; + `, + ` + declare const foo: { bar: number } | null; + foo !== null && foo !== undefined && foo.bar !== 1; + `, + ` + declare const foo: { bar: number } | null; + foo !== null && foo !== undefined && foo.bar != 1; + `, + + ` + declare const foo: { bar: number } | undefined; + foo !== null && foo !== undefined && foo.bar == null; + `, + ` + declare const foo: { bar: number } | undefined; + foo !== null && foo !== undefined && foo.bar === undefined; + `, + ` + declare const foo: { bar: number } | undefined; + foo !== null && foo !== undefined && foo.bar !== 1; + `, + ` + declare const foo: { bar: number } | undefined; + foo !== null && foo !== undefined && foo.bar != 1; + `, + ` + declare const foo: { bar: number } | null; + foo !== undefined && foo !== undefined && foo.bar == null; + `, + ` + declare const foo: { bar: number } | null; + foo !== undefined && foo !== undefined && foo.bar === undefined; + `, + ` + declare const foo: { bar: number } | null; + foo !== undefined && foo !== undefined && foo.bar !== 1; + `, + ` + declare const foo: { bar: number } | null; + foo !== undefined && foo !== undefined && foo.bar != 1; + `, + + ` + declare const foo: { bar: number } | undefined; + foo !== undefined && foo !== undefined && foo.bar == null; + `, + ` + declare const foo: { bar: number } | undefined; + foo !== undefined && foo !== undefined && foo.bar === undefined; + `, + ` + declare const foo: { bar: number } | undefined; + foo !== undefined && foo !== undefined && foo.bar !== 1; + `, + ` + declare const foo: { bar: number } | undefined; + foo !== undefined && foo !== undefined && foo.bar != 1; + `, + '!foo && foo.bar == 0;', + '!foo && foo.bar == 1;', + "!foo && foo.bar == '123';", + '!foo && foo.bar == {};', + '!foo && foo.bar == false;', + '!foo && foo.bar == true;', + '!foo && foo.bar === 0;', + '!foo && foo.bar === 1;', + "!foo && foo.bar === '123';", + '!foo && foo.bar === {};', + '!foo && foo.bar === false;', + '!foo && foo.bar === true;', + '!foo && foo.bar === null;', + '!foo && foo.bar !== undefined;', + '!foo && foo.bar != undefined;', + '!foo && foo.bar != null;', + 'foo == null && foo.bar == 0;', + 'foo == null && foo.bar == 1;', + "foo == null && foo.bar == '123';", + 'foo == null && foo.bar == {};', + 'foo == null && foo.bar == false;', + 'foo == null && foo.bar == true;', + 'foo == null && foo.bar === 0;', + 'foo == null && foo.bar === 1;', + "foo == null && foo.bar === '123';", + 'foo == null && foo.bar === {};', + 'foo == null && foo.bar === false;', + 'foo == null && foo.bar === true;', + 'foo == null && foo.bar === null;', + 'foo == null && foo.bar !== undefined;', + 'foo == null && foo.bar != null;', + 'foo == null && foo.bar != undefined;', + ` + declare const x: false | { a: string }; + x && x.a == x; + `, + ` + declare const x: '' | { a: string }; + x && x.a == x; + `, + ` + declare const x: 0 | { a: string }; + x && x.a == x; + `, + ` + declare const x: 0n | { a: string }; + x && x.a; + `, + '!foo || foo.bar != x;', + '!foo || foo.bar != null;', + '!foo || foo.bar != undefined;', + '!foo || foo.bar === 0;', + '!foo || foo.bar === 1;', + "!foo || foo.bar === '123';", + '!foo || foo.bar === {};', + '!foo || foo.bar === false;', + '!foo || foo.bar === true;', + '!foo || foo.bar === null;', + '!foo || foo.bar === x;', + '!foo || foo.bar == 0;', + '!foo || foo.bar == 1;', + "!foo || foo.bar == '123';", + '!foo || foo.bar == {};', + '!foo || foo.bar == false;', + '!foo || foo.bar == true;', + '!foo || foo.bar == x;', + '!foo || foo.bar !== x;', + '!foo || foo.bar !== undefined;', + 'foo == null || foo.bar != x;', + 'foo == null || foo.bar != null;', + 'foo == null || foo.bar != undefined;', + 'foo == null || foo.bar === 0;', + 'foo == null || foo.bar === 1;', + "foo == null || foo.bar === '123';", + 'foo == null || foo.bar === {};', + 'foo == null || foo.bar === false;', + 'foo == null || foo.bar === true;', + 'foo == null || foo.bar === null;', + 'foo == null || foo.bar === x;', + 'foo == null || foo.bar == 0;', + 'foo == null || foo.bar == 1;', + "foo == null || foo.bar == '123';", + 'foo == null || foo.bar == {};', + 'foo == null || foo.bar == false;', + 'foo == null || foo.bar == true;', + 'foo == null || foo.bar == x;', + 'foo == null || foo.bar !== x;', + 'foo == null || foo.bar !== undefined;', + 'foo || foo.bar != 0;', + 'foo || foo.bar != 1;', + "foo || foo.bar != '123';", + 'foo || foo.bar != {};', + 'foo || foo.bar != false;', + 'foo || foo.bar != true;', + 'foo || foo.bar === undefined;', + 'foo || foo.bar == undefined;', + 'foo || foo.bar == null;', + 'foo || foo.bar !== 0;', + 'foo || foo.bar !== 1;', + "foo || foo.bar !== '123';", + 'foo || foo.bar !== {};', + 'foo || foo.bar !== false;', + 'foo || foo.bar !== true;', + 'foo || foo.bar !== null;', + 'foo != null || foo.bar != 0;', + 'foo != null || foo.bar != 1;', + "foo != null || foo.bar != '123';", + 'foo != null || foo.bar != {};', + 'foo != null || foo.bar != false;', + 'foo != null || foo.bar != true;', + 'foo != null || foo.bar === undefined;', + 'foo != null || foo.bar == undefined;', + 'foo != null || foo.bar == null;', + 'foo != null || foo.bar !== 0;', + 'foo != null || foo.bar !== 1;', + "foo != null || foo.bar !== '123';", + 'foo != null || foo.bar !== {};', + 'foo != null || foo.bar !== false;', + 'foo != null || foo.bar !== true;', + 'foo != null || foo.bar !== null;', + ], + }); +}); + describe('hand-crafted cases', () => { ruleTester.run('prefer-optional-chain', rule, { invalid: [ @@ -714,8 +2248,7 @@ describe('hand-crafted cases', () => { { code: 'foo && foo.bar != null && foo.bar.baz !== undefined && foo.bar.baz.buzz;', errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: - 'foo?.bar != null && foo.bar.baz !== undefined && foo.bar.baz.buzz;', + output: 'foo?.bar?.baz !== undefined && foo.bar.baz.buzz;', }, { code: ` @@ -726,8 +2259,7 @@ describe('hand-crafted cases', () => { `, errors: [{ messageId: 'preferOptionalChain', suggestions: null }], output: ` - foo.bar?.baz != null && - foo.bar.baz.qux !== undefined && + foo.bar?.baz?.qux !== undefined && foo.bar.baz.qux.buzz; `, }, @@ -956,53 +2488,11 @@ describe('hand-crafted cases', () => { errors: [{ messageId: 'preferOptionalChain', suggestions: null }], output: '!foo.bar!.baz?.paz;', }, - { - code: ` - declare const foo: { bar: string } | null; - foo !== null && foo.bar !== null; - `, - errors: [ - { - messageId: 'preferOptionalChain', - suggestions: [ - { - messageId: 'optionalChainSuggest', - output: ` - declare const foo: { bar: string } | null; - foo?.bar !== null; - `, - }, - ], - }, - ], - output: null, - }, { code: 'foo != null && foo.bar != null;', errors: [{ messageId: 'preferOptionalChain', suggestions: null }], output: 'foo?.bar != null;', }, - { - code: ` - declare const foo: { bar: string | null } | null; - foo != null && foo.bar !== null; - `, - errors: [ - { - messageId: 'preferOptionalChain', - suggestions: [ - { - messageId: 'optionalChainSuggest', - output: ` - declare const foo: { bar: string | null } | null; - foo?.bar !== null; - `, - }, - ], - }, - ], - output: null, - }, { code: ` declare const foo: { bar: string | null } | null; @@ -1809,6 +3299,14 @@ const baz = foo?.bar; '(x || y) != null && (x || y).foo;', // TODO - should we handle this? '(await foo) && (await foo).bar;', + ` + declare const foo: { bar: string } | null; + foo !== null && foo.bar !== null; + `, + ` + declare const foo: { bar: string | null } | null; + foo != null && foo.bar !== null; + `, { code: ` declare const x: string; @@ -1991,16 +3489,58 @@ describe('base cases', () => { mutateCode: c => c.replaceAll('&&', '!== null &&'), mutateOutput: identity, operator: '&&', + skipIds: [20, 26], }), // but if the type is just `| null` - then it covers the cases and is // a valid conversion - invalid: BaseCases({ - mutateCode: c => c.replaceAll('&&', '!== null &&'), - mutateDeclaration: c => c.replaceAll('| undefined', ''), - mutateOutput: identity, - operator: '&&', - useSuggestionFixer: true, - }), + invalid: [ + ...BaseCases({ + mutateCode: c => c.replaceAll('&&', '!== null &&'), + mutateDeclaration: c => c.replaceAll('| undefined', ''), + mutateOutput: identity, + operator: '&&', + useSuggestionFixer: true, + }), + { + code: ` + declare const foo: { + bar: () => + | { baz: { buzz: (() => number) | null | undefined } | null | undefined } + | null + | undefined; + }; + foo.bar !== null && + foo.bar() !== null && + foo.bar().baz !== null && + foo.bar().baz.buzz !== null && + foo.bar().baz.buzz(); + `, + errors: [{ messageId: 'preferOptionalChain' }], + output: ` + declare const foo: { + bar: () => + | { baz: { buzz: (() => number) | null | undefined } | null | undefined } + | null + | undefined; + }; + foo.bar?.() !== null && + foo.bar().baz !== null && + foo.bar().baz.buzz !== null && + foo.bar().baz.buzz(); + `, + }, + { + code: ` + declare const foo: { bar: () => { baz: number } | null | undefined }; + foo.bar !== null && foo.bar?.() !== null && foo.bar?.().baz; + `, + errors: [{ messageId: 'preferOptionalChain' }], + output: ` + declare const foo: { bar: () => { baz: number } | null | undefined }; + foo.bar?.() !== null && foo.bar?.().baz; + `, + }, + ], }); }); @@ -2024,16 +3564,58 @@ describe('base cases', () => { mutateCode: c => c.replaceAll('&&', '!== undefined &&'), mutateOutput: identity, operator: '&&', + skipIds: [20, 26], }), // but if the type is just `| undefined` - then it covers the cases and is // a valid conversion - invalid: BaseCases({ - mutateCode: c => c.replaceAll('&&', '!== undefined &&'), - mutateDeclaration: c => c.replaceAll('| null', ''), - mutateOutput: identity, - operator: '&&', - useSuggestionFixer: true, - }), + invalid: [ + ...BaseCases({ + mutateCode: c => c.replaceAll('&&', '!== undefined &&'), + mutateDeclaration: c => c.replaceAll('| null', ''), + mutateOutput: identity, + operator: '&&', + useSuggestionFixer: true, + }), + { + code: ` + declare const foo: { + bar: () => + | { baz: { buzz: (() => number) | null | undefined } | null | undefined } + | null + | undefined; + }; + foo.bar !== undefined && + foo.bar() !== undefined && + foo.bar().baz !== undefined && + foo.bar().baz.buzz !== undefined && + foo.bar().baz.buzz(); + `, + errors: [{ messageId: 'preferOptionalChain' }], + output: ` + declare const foo: { + bar: () => + | { baz: { buzz: (() => number) | null | undefined } | null | undefined } + | null + | undefined; + }; + foo.bar?.() !== undefined && + foo.bar().baz !== undefined && + foo.bar().baz.buzz !== undefined && + foo.bar().baz.buzz(); + `, + }, + { + code: ` + declare const foo: { bar: () => { baz: number } | null | undefined }; + foo.bar !== undefined && foo.bar?.() !== undefined && foo.bar?.().baz; + `, + errors: [{ messageId: 'preferOptionalChain' }], + output: ` + declare const foo: { bar: () => { baz: number } | null | undefined }; + foo.bar?.() !== undefined && foo.bar?.().baz; + `, + }, + ], }); }); @@ -2072,21 +3654,63 @@ describe('base cases', () => { mutateCode: c => c.replaceAll('||', '=== null ||'), mutateOutput: identity, operator: '||', + skipIds: [20, 26], }), // but if the type is just `| null` - then it covers the cases and is // a valid conversion - invalid: BaseCases({ - mutateCode: c => - c - .replaceAll('||', '=== null ||') - // SEE TODO AT THE BOTTOM OF THE RULE - // We need to ensure the final operand is also a "valid" `||` check - .replace(/;$/, ' === null;'), - mutateDeclaration: c => c.replaceAll('| undefined', ''), - mutateOutput: c => c.replace(/;$/, ' === null;'), - operator: '||', - useSuggestionFixer: true, - }), + invalid: [ + ...BaseCases({ + mutateCode: c => + c + .replaceAll('||', '=== null ||') + // SEE TODO AT THE BOTTOM OF THE RULE + // We need to ensure the final operand is also a "valid" `||` check + .replace(/;$/, ' === null;'), + mutateDeclaration: c => c.replaceAll('| undefined', ''), + mutateOutput: c => c.replace(/;$/, ' === null;'), + operator: '||', + useSuggestionFixer: true, + }), + { + code: ` + declare const foo: { + bar: () => + | { baz: { buzz: (() => number) | null | undefined } | null | undefined } + | null + | undefined; + }; + foo.bar === null || + foo.bar() === null || + foo.bar().baz === null || + foo.bar().baz.buzz === null || + foo.bar().baz.buzz(); + `, + errors: [{ messageId: 'preferOptionalChain' }], + output: ` + declare const foo: { + bar: () => + | { baz: { buzz: (() => number) | null | undefined } | null | undefined } + | null + | undefined; + }; + foo.bar?.() === null || + foo.bar().baz === null || + foo.bar().baz.buzz === null || + foo.bar().baz.buzz(); + `, + }, + { + code: ` + declare const foo: { bar: () => { baz: number } | null | undefined }; + foo.bar === null || foo.bar?.() === null || foo.bar?.().baz; + `, + errors: [{ messageId: 'preferOptionalChain' }], + output: ` + declare const foo: { bar: () => { baz: number } | null | undefined }; + foo.bar?.() === null || foo.bar?.().baz; + `, + }, + ], }); }); @@ -2114,20 +3738,62 @@ describe('base cases', () => { mutateCode: c => c.replaceAll('||', '=== undefined ||'), mutateOutput: identity, operator: '||', + skipIds: [20, 26], }), // but if the type is just `| undefined` - then it covers the cases and is // a valid conversion - invalid: BaseCases({ - mutateCode: c => - c - .replaceAll('||', '=== undefined ||') - // SEE TODO AT THE BOTTOM OF THE RULE - // We need to ensure the final operand is also a "valid" `||` check - .replace(/;$/, ' === undefined;'), - mutateDeclaration: c => c.replaceAll('| null', ''), - mutateOutput: c => c.replace(/;$/, ' === undefined;'), - operator: '||', - }), + invalid: [ + ...BaseCases({ + mutateCode: c => + c + .replaceAll('||', '=== undefined ||') + // SEE TODO AT THE BOTTOM OF THE RULE + // We need to ensure the final operand is also a "valid" `||` check + .replace(/;$/, ' === undefined;'), + mutateDeclaration: c => c.replaceAll('| null', ''), + mutateOutput: c => c.replace(/;$/, ' === undefined;'), + operator: '||', + }), + { + code: ` + declare const foo: { + bar: () => + | { baz: { buzz: (() => number) | null | undefined } | null | undefined } + | null + | undefined; + }; + foo.bar === undefined || + foo.bar() === undefined || + foo.bar().baz === undefined || + foo.bar().baz.buzz === undefined || + foo.bar().baz.buzz(); + `, + errors: [{ messageId: 'preferOptionalChain' }], + output: ` + declare const foo: { + bar: () => + | { baz: { buzz: (() => number) | null | undefined } | null | undefined } + | null + | undefined; + }; + foo.bar?.() === undefined || + foo.bar().baz === undefined || + foo.bar().baz.buzz === undefined || + foo.bar().baz.buzz(); + `, + }, + { + code: ` + declare const foo: { bar: () => { baz: number } | null | undefined }; + foo.bar === undefined || foo.bar?.() === undefined || foo.bar?.().baz; + `, + errors: [{ messageId: 'preferOptionalChain' }], + output: ` + declare const foo: { bar: () => { baz: number } | null | undefined }; + foo.bar?.() === undefined || foo.bar?.().baz; + `, + }, + ], }); }); diff --git a/packages/parser/CHANGELOG.md b/packages/parser/CHANGELOG.md index 45579e3b5d5d..1093507356f5 100644 --- a/packages/parser/CHANGELOG.md +++ b/packages/parser/CHANGELOG.md @@ -1,3 +1,9 @@ +## 8.46.1 (2025-10-13) + +This was a version bump only for parser to align it with other projects, there were no code changes. + +You can read about our [versioning strategy](https://typescript-eslint.io/users/versioning) and [releases](https://typescript-eslint.io/users/releases) on our website. + ## 8.46.0 (2025-10-06) This was a version bump only for parser to align it with other projects, there were no code changes. diff --git a/packages/parser/package.json b/packages/parser/package.json index ed6e1efc32d9..1da67fef90bd 100644 --- a/packages/parser/package.json +++ b/packages/parser/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/parser", - "version": "8.46.0", + "version": "8.46.1", "description": "An ESLint custom parser which leverages TypeScript ESTree", "files": [ "dist", @@ -51,10 +51,10 @@ "typescript": ">=4.8.4 <6.0.0" }, "dependencies": { - "@typescript-eslint/scope-manager": "8.46.0", - "@typescript-eslint/types": "8.46.0", - "@typescript-eslint/typescript-estree": "8.46.0", - "@typescript-eslint/visitor-keys": "8.46.0", + "@typescript-eslint/scope-manager": "8.46.1", + "@typescript-eslint/types": "8.46.1", + "@typescript-eslint/typescript-estree": "8.46.1", + "@typescript-eslint/visitor-keys": "8.46.1", "debug": "^4.3.4" }, "devDependencies": { diff --git a/packages/project-service/CHANGELOG.md b/packages/project-service/CHANGELOG.md index 23925751fe40..3b10c4596cbc 100644 --- a/packages/project-service/CHANGELOG.md +++ b/packages/project-service/CHANGELOG.md @@ -1,3 +1,9 @@ +## 8.46.1 (2025-10-13) + +This was a version bump only for project-service to align it with other projects, there were no code changes. + +You can read about our [versioning strategy](https://typescript-eslint.io/users/versioning) and [releases](https://typescript-eslint.io/users/releases) on our website. + ## 8.46.0 (2025-10-06) This was a version bump only for project-service to align it with other projects, there were no code changes. diff --git a/packages/project-service/package.json b/packages/project-service/package.json index 7de623639b7f..78212eb7ec89 100644 --- a/packages/project-service/package.json +++ b/packages/project-service/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/project-service", - "version": "8.46.0", + "version": "8.46.1", "description": "Standalone TypeScript project service wrapper for linting.", "files": [ "dist", @@ -49,8 +49,8 @@ "typescript": ">=4.8.4 <6.0.0" }, "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.46.0", - "@typescript-eslint/types": "^8.46.0", + "@typescript-eslint/tsconfig-utils": "^8.46.1", + "@typescript-eslint/types": "^8.46.1", "debug": "^4.3.4" }, "devDependencies": { diff --git a/packages/rule-schema-to-typescript-types/CHANGELOG.md b/packages/rule-schema-to-typescript-types/CHANGELOG.md index f7280aeb60ec..45091e92496a 100644 --- a/packages/rule-schema-to-typescript-types/CHANGELOG.md +++ b/packages/rule-schema-to-typescript-types/CHANGELOG.md @@ -1,3 +1,9 @@ +## 8.46.1 (2025-10-13) + +This was a version bump only for rule-schema-to-typescript-types to align it with other projects, there were no code changes. + +You can read about our [versioning strategy](https://typescript-eslint.io/users/versioning) and [releases](https://typescript-eslint.io/users/releases) on our website. + ## 8.46.0 (2025-10-06) ### 🚀 Features diff --git a/packages/rule-schema-to-typescript-types/package.json b/packages/rule-schema-to-typescript-types/package.json index b47a3fb329ab..31da5b8086b6 100644 --- a/packages/rule-schema-to-typescript-types/package.json +++ b/packages/rule-schema-to-typescript-types/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/rule-schema-to-typescript-types", - "version": "8.46.0", + "version": "8.46.1", "description": "Converts ESLint rule schemas to equivalent TypeScript type strings.", "type": "module", "exports": { @@ -32,8 +32,8 @@ "typecheck": "yarn run -BT nx typecheck" }, "dependencies": { - "@typescript-eslint/type-utils": "8.46.0", - "@typescript-eslint/utils": "8.46.0", + "@typescript-eslint/type-utils": "8.46.1", + "@typescript-eslint/utils": "8.46.1", "natural-compare": "^1.4.0" }, "devDependencies": { diff --git a/packages/rule-tester/CHANGELOG.md b/packages/rule-tester/CHANGELOG.md index 6d53afcf7509..10762eb60663 100644 --- a/packages/rule-tester/CHANGELOG.md +++ b/packages/rule-tester/CHANGELOG.md @@ -1,3 +1,9 @@ +## 8.46.1 (2025-10-13) + +This was a version bump only for rule-tester to align it with other projects, there were no code changes. + +You can read about our [versioning strategy](https://typescript-eslint.io/users/versioning) and [releases](https://typescript-eslint.io/users/releases) on our website. + ## 8.46.0 (2025-10-06) ### 🩹 Fixes diff --git a/packages/rule-tester/package.json b/packages/rule-tester/package.json index b12bbd465ab6..d94d3b565be6 100644 --- a/packages/rule-tester/package.json +++ b/packages/rule-tester/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/rule-tester", - "version": "8.46.0", + "version": "8.46.1", "description": "Tooling to test ESLint rules", "files": [ "dist", @@ -44,9 +44,9 @@ }, "//": "NOTE - AJV is out-of-date, but it's intentionally synced with ESLint - https://github.com/eslint/eslint/blob/ad9dd6a933fd098a0d99c6a9aa059850535c23ee/package.json#L70", "dependencies": { - "@typescript-eslint/parser": "8.46.0", - "@typescript-eslint/typescript-estree": "8.46.0", - "@typescript-eslint/utils": "8.46.0", + "@typescript-eslint/parser": "8.46.1", + "@typescript-eslint/typescript-estree": "8.46.1", + "@typescript-eslint/utils": "8.46.1", "ajv": "^6.12.6", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "4.6.2", diff --git a/packages/scope-manager/CHANGELOG.md b/packages/scope-manager/CHANGELOG.md index 9c273f69cb7b..5c73caa90084 100644 --- a/packages/scope-manager/CHANGELOG.md +++ b/packages/scope-manager/CHANGELOG.md @@ -1,3 +1,9 @@ +## 8.46.1 (2025-10-13) + +This was a version bump only for scope-manager to align it with other projects, there were no code changes. + +You can read about our [versioning strategy](https://typescript-eslint.io/users/versioning) and [releases](https://typescript-eslint.io/users/releases) on our website. + ## 8.46.0 (2025-10-06) This was a version bump only for scope-manager to align it with other projects, there were no code changes. diff --git a/packages/scope-manager/package.json b/packages/scope-manager/package.json index 7bf9f5c6c957..32669b55bcd3 100644 --- a/packages/scope-manager/package.json +++ b/packages/scope-manager/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/scope-manager", - "version": "8.46.0", + "version": "8.46.1", "description": "TypeScript scope analyser for ESLint", "files": [ "dist", @@ -47,11 +47,11 @@ "typecheck": "yarn run -BT nx typecheck" }, "dependencies": { - "@typescript-eslint/types": "8.46.0", - "@typescript-eslint/visitor-keys": "8.46.0" + "@typescript-eslint/types": "8.46.1", + "@typescript-eslint/visitor-keys": "8.46.1" }, "devDependencies": { - "@typescript-eslint/typescript-estree": "8.46.0", + "@typescript-eslint/typescript-estree": "8.46.1", "@vitest/coverage-v8": "^3.1.3", "@vitest/pretty-format": "^3.1.3", "eslint": "*", diff --git a/packages/tsconfig-utils/CHANGELOG.md b/packages/tsconfig-utils/CHANGELOG.md index 56e6067e86e5..ff51316e6f78 100644 --- a/packages/tsconfig-utils/CHANGELOG.md +++ b/packages/tsconfig-utils/CHANGELOG.md @@ -1,3 +1,9 @@ +## 8.46.1 (2025-10-13) + +This was a version bump only for tsconfig-utils to align it with other projects, there were no code changes. + +You can read about our [versioning strategy](https://typescript-eslint.io/users/versioning) and [releases](https://typescript-eslint.io/users/releases) on our website. + ## 8.46.0 (2025-10-06) This was a version bump only for tsconfig-utils to align it with other projects, there were no code changes. diff --git a/packages/tsconfig-utils/package.json b/packages/tsconfig-utils/package.json index 0a489b1036ca..970065ac322f 100644 --- a/packages/tsconfig-utils/package.json +++ b/packages/tsconfig-utils/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/tsconfig-utils", - "version": "8.46.0", + "version": "8.46.1", "description": "Utilities for collecting TSConfigs for linting scenarios.", "files": [ "dist", diff --git a/packages/type-utils/CHANGELOG.md b/packages/type-utils/CHANGELOG.md index e3173aa353e5..c46cf6e734e4 100644 --- a/packages/type-utils/CHANGELOG.md +++ b/packages/type-utils/CHANGELOG.md @@ -1,3 +1,9 @@ +## 8.46.1 (2025-10-13) + +This was a version bump only for type-utils to align it with other projects, there were no code changes. + +You can read about our [versioning strategy](https://typescript-eslint.io/users/versioning) and [releases](https://typescript-eslint.io/users/releases) on our website. + ## 8.46.0 (2025-10-06) ### 🩹 Fixes diff --git a/packages/type-utils/package.json b/packages/type-utils/package.json index fb7d9b0df675..3adbe12ab697 100644 --- a/packages/type-utils/package.json +++ b/packages/type-utils/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/type-utils", - "version": "8.46.0", + "version": "8.46.1", "description": "Type utilities for working with TypeScript + ESLint together", "files": [ "dist", @@ -44,9 +44,9 @@ "typecheck": "yarn run -BT nx typecheck" }, "dependencies": { - "@typescript-eslint/types": "8.46.0", - "@typescript-eslint/typescript-estree": "8.46.0", - "@typescript-eslint/utils": "8.46.0", + "@typescript-eslint/types": "8.46.1", + "@typescript-eslint/typescript-estree": "8.46.1", + "@typescript-eslint/utils": "8.46.1", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -55,7 +55,7 @@ "typescript": ">=4.8.4 <6.0.0" }, "devDependencies": { - "@typescript-eslint/parser": "8.46.0", + "@typescript-eslint/parser": "8.46.1", "@vitest/coverage-v8": "^3.1.3", "ajv": "^6.12.6", "eslint": "*", diff --git a/packages/types/CHANGELOG.md b/packages/types/CHANGELOG.md index a8b32d0d089e..93ac7964f418 100644 --- a/packages/types/CHANGELOG.md +++ b/packages/types/CHANGELOG.md @@ -1,3 +1,9 @@ +## 8.46.1 (2025-10-13) + +This was a version bump only for types to align it with other projects, there were no code changes. + +You can read about our [versioning strategy](https://typescript-eslint.io/users/versioning) and [releases](https://typescript-eslint.io/users/releases) on our website. + ## 8.46.0 (2025-10-06) This was a version bump only for types to align it with other projects, there were no code changes. diff --git a/packages/types/package.json b/packages/types/package.json index 95466be524bb..3c25f4b1db49 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/types", - "version": "8.46.0", + "version": "8.46.1", "description": "Types for the TypeScript-ESTree AST spec", "files": [ "dist", diff --git a/packages/typescript-eslint/CHANGELOG.md b/packages/typescript-eslint/CHANGELOG.md index f16f92e76f8b..aa8169b2ec5c 100644 --- a/packages/typescript-eslint/CHANGELOG.md +++ b/packages/typescript-eslint/CHANGELOG.md @@ -1,3 +1,9 @@ +## 8.46.1 (2025-10-13) + +This was a version bump only for typescript-eslint to align it with other projects, there were no code changes. + +You can read about our [versioning strategy](https://typescript-eslint.io/users/versioning) and [releases](https://typescript-eslint.io/users/releases) on our website. + ## 8.46.0 (2025-10-06) ### 🚀 Features diff --git a/packages/typescript-eslint/package.json b/packages/typescript-eslint/package.json index 19132a1a4198..8563c8a0cde6 100644 --- a/packages/typescript-eslint/package.json +++ b/packages/typescript-eslint/package.json @@ -1,6 +1,6 @@ { "name": "typescript-eslint", - "version": "8.46.0", + "version": "8.46.1", "description": "Tooling which enables you to use TypeScript with ESLint", "files": [ "dist", @@ -50,10 +50,10 @@ "typecheck": "yarn run -BT nx typecheck" }, "dependencies": { - "@typescript-eslint/eslint-plugin": "8.46.0", - "@typescript-eslint/parser": "8.46.0", - "@typescript-eslint/typescript-estree": "8.46.0", - "@typescript-eslint/utils": "8.46.0" + "@typescript-eslint/eslint-plugin": "8.46.1", + "@typescript-eslint/parser": "8.46.1", + "@typescript-eslint/typescript-estree": "8.46.1", + "@typescript-eslint/utils": "8.46.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", diff --git a/packages/typescript-estree/CHANGELOG.md b/packages/typescript-estree/CHANGELOG.md index 9f63c47283f1..41aa41f43b2f 100644 --- a/packages/typescript-estree/CHANGELOG.md +++ b/packages/typescript-estree/CHANGELOG.md @@ -1,3 +1,9 @@ +## 8.46.1 (2025-10-13) + +This was a version bump only for typescript-estree to align it with other projects, there were no code changes. + +You can read about our [versioning strategy](https://typescript-eslint.io/users/versioning) and [releases](https://typescript-eslint.io/users/releases) on our website. + ## 8.46.0 (2025-10-06) ### 🚀 Features diff --git a/packages/typescript-estree/package.json b/packages/typescript-estree/package.json index 29d8b7f4e88b..a734ec40bfc3 100644 --- a/packages/typescript-estree/package.json +++ b/packages/typescript-estree/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/typescript-estree", - "version": "8.46.0", + "version": "8.46.1", "description": "A parser that converts TypeScript source code into an ESTree compatible form", "files": [ "dist", @@ -52,10 +52,10 @@ "typecheck": "yarn run -BT nx typecheck" }, "dependencies": { - "@typescript-eslint/project-service": "8.46.0", - "@typescript-eslint/tsconfig-utils": "8.46.0", - "@typescript-eslint/types": "8.46.0", - "@typescript-eslint/visitor-keys": "8.46.0", + "@typescript-eslint/project-service": "8.46.1", + "@typescript-eslint/tsconfig-utils": "8.46.1", + "@typescript-eslint/types": "8.46.1", + "@typescript-eslint/visitor-keys": "8.46.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", diff --git a/packages/utils/CHANGELOG.md b/packages/utils/CHANGELOG.md index 1551ec7a7ab8..19eb831f87e7 100644 --- a/packages/utils/CHANGELOG.md +++ b/packages/utils/CHANGELOG.md @@ -1,3 +1,9 @@ +## 8.46.1 (2025-10-13) + +This was a version bump only for utils to align it with other projects, there were no code changes. + +You can read about our [versioning strategy](https://typescript-eslint.io/users/versioning) and [releases](https://typescript-eslint.io/users/releases) on our website. + ## 8.46.0 (2025-10-06) ### 🩹 Fixes diff --git a/packages/utils/package.json b/packages/utils/package.json index 21546d4601ed..4cec4b04e2bd 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/utils", - "version": "8.46.0", + "version": "8.46.1", "description": "Utilities for working with TypeScript + ESLint together", "files": [ "dist", @@ -62,9 +62,9 @@ }, "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.46.0", - "@typescript-eslint/types": "8.46.0", - "@typescript-eslint/typescript-estree": "8.46.0" + "@typescript-eslint/scope-manager": "8.46.1", + "@typescript-eslint/types": "8.46.1", + "@typescript-eslint/typescript-estree": "8.46.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", diff --git a/packages/visitor-keys/CHANGELOG.md b/packages/visitor-keys/CHANGELOG.md index 4ead0db9eaa4..fd4d06aadeb2 100644 --- a/packages/visitor-keys/CHANGELOG.md +++ b/packages/visitor-keys/CHANGELOG.md @@ -1,3 +1,9 @@ +## 8.46.1 (2025-10-13) + +This was a version bump only for visitor-keys to align it with other projects, there were no code changes. + +You can read about our [versioning strategy](https://typescript-eslint.io/users/versioning) and [releases](https://typescript-eslint.io/users/releases) on our website. + ## 8.46.0 (2025-10-06) This was a version bump only for visitor-keys to align it with other projects, there were no code changes. diff --git a/packages/visitor-keys/package.json b/packages/visitor-keys/package.json index 156e3591061e..653f7dc4c6ca 100644 --- a/packages/visitor-keys/package.json +++ b/packages/visitor-keys/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/visitor-keys", - "version": "8.46.0", + "version": "8.46.1", "description": "Visitor keys used to help traverse the TypeScript-ESTree AST", "files": [ "dist", @@ -45,7 +45,7 @@ "typecheck": "yarn run -BT nx typecheck" }, "dependencies": { - "@typescript-eslint/types": "8.46.0", + "@typescript-eslint/types": "8.46.1", "eslint-visitor-keys": "^4.2.1" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index abe0aa56244e..e9f3ec2ffc1c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5910,19 +5910,19 @@ __metadata: languageName: unknown linkType: soft -"@typescript-eslint/eslint-plugin@8.46.0, @typescript-eslint/eslint-plugin@workspace:*, @typescript-eslint/eslint-plugin@workspace:^, @typescript-eslint/eslint-plugin@workspace:packages/eslint-plugin": +"@typescript-eslint/eslint-plugin@8.46.1, @typescript-eslint/eslint-plugin@workspace:*, @typescript-eslint/eslint-plugin@workspace:^, @typescript-eslint/eslint-plugin@workspace:packages/eslint-plugin": version: 0.0.0-use.local resolution: "@typescript-eslint/eslint-plugin@workspace:packages/eslint-plugin" dependencies: "@eslint-community/regexpp": ^4.10.0 "@types/mdast": ^4.0.3 "@types/natural-compare": "*" - "@typescript-eslint/rule-schema-to-typescript-types": 8.46.0 - "@typescript-eslint/rule-tester": 8.46.0 - "@typescript-eslint/scope-manager": 8.46.0 - "@typescript-eslint/type-utils": 8.46.0 - "@typescript-eslint/utils": 8.46.0 - "@typescript-eslint/visitor-keys": 8.46.0 + "@typescript-eslint/rule-schema-to-typescript-types": 8.46.1 + "@typescript-eslint/rule-tester": 8.46.1 + "@typescript-eslint/scope-manager": 8.46.1 + "@typescript-eslint/type-utils": 8.46.1 + "@typescript-eslint/utils": 8.46.1 + "@typescript-eslint/visitor-keys": 8.46.1 "@vitest/coverage-v8": ^3.1.3 ajv: ^6.12.6 cross-fetch: "*" @@ -5945,7 +5945,7 @@ __metadata: unist-util-visit: ^5.0.0 vitest: ^3.1.3 peerDependencies: - "@typescript-eslint/parser": ^8.46.0 + "@typescript-eslint/parser": ^8.46.1 eslint: ^8.57.0 || ^9.0.0 typescript: ">=4.8.4 <6.0.0" languageName: unknown @@ -5961,14 +5961,14 @@ __metadata: languageName: unknown linkType: soft -"@typescript-eslint/parser@8.46.0, @typescript-eslint/parser@workspace:*, @typescript-eslint/parser@workspace:^, @typescript-eslint/parser@workspace:packages/parser": +"@typescript-eslint/parser@8.46.1, @typescript-eslint/parser@workspace:*, @typescript-eslint/parser@workspace:^, @typescript-eslint/parser@workspace:packages/parser": version: 0.0.0-use.local resolution: "@typescript-eslint/parser@workspace:packages/parser" dependencies: - "@typescript-eslint/scope-manager": 8.46.0 - "@typescript-eslint/types": 8.46.0 - "@typescript-eslint/typescript-estree": 8.46.0 - "@typescript-eslint/visitor-keys": 8.46.0 + "@typescript-eslint/scope-manager": 8.46.1 + "@typescript-eslint/types": 8.46.1 + "@typescript-eslint/typescript-estree": 8.46.1 + "@typescript-eslint/visitor-keys": 8.46.1 "@vitest/coverage-v8": ^3.1.3 debug: ^4.3.4 eslint: "*" @@ -5982,12 +5982,12 @@ __metadata: languageName: unknown linkType: soft -"@typescript-eslint/project-service@8.46.0, @typescript-eslint/project-service@workspace:packages/project-service": +"@typescript-eslint/project-service@8.46.1, @typescript-eslint/project-service@workspace:packages/project-service": version: 0.0.0-use.local resolution: "@typescript-eslint/project-service@workspace:packages/project-service" dependencies: - "@typescript-eslint/tsconfig-utils": ^8.46.0 - "@typescript-eslint/types": ^8.46.0 + "@typescript-eslint/tsconfig-utils": ^8.46.1 + "@typescript-eslint/types": ^8.46.1 "@vitest/coverage-v8": ^3.1.3 debug: ^4.3.4 rimraf: "*" @@ -5998,12 +5998,12 @@ __metadata: languageName: unknown linkType: soft -"@typescript-eslint/rule-schema-to-typescript-types@8.46.0, @typescript-eslint/rule-schema-to-typescript-types@workspace:*, @typescript-eslint/rule-schema-to-typescript-types@workspace:packages/rule-schema-to-typescript-types": +"@typescript-eslint/rule-schema-to-typescript-types@8.46.1, @typescript-eslint/rule-schema-to-typescript-types@workspace:*, @typescript-eslint/rule-schema-to-typescript-types@workspace:packages/rule-schema-to-typescript-types": version: 0.0.0-use.local resolution: "@typescript-eslint/rule-schema-to-typescript-types@workspace:packages/rule-schema-to-typescript-types" dependencies: - "@typescript-eslint/type-utils": 8.46.0 - "@typescript-eslint/utils": 8.46.0 + "@typescript-eslint/type-utils": 8.46.1 + "@typescript-eslint/utils": 8.46.1 "@vitest/coverage-v8": ^3.1.3 eslint: "*" natural-compare: ^1.4.0 @@ -6013,15 +6013,15 @@ __metadata: languageName: unknown linkType: soft -"@typescript-eslint/rule-tester@8.46.0, @typescript-eslint/rule-tester@workspace:*, @typescript-eslint/rule-tester@workspace:packages/rule-tester": +"@typescript-eslint/rule-tester@8.46.1, @typescript-eslint/rule-tester@workspace:*, @typescript-eslint/rule-tester@workspace:packages/rule-tester": version: 0.0.0-use.local resolution: "@typescript-eslint/rule-tester@workspace:packages/rule-tester" dependencies: "@types/json-stable-stringify-without-jsonify": ^1.0.2 "@types/lodash.merge": 4.6.9 - "@typescript-eslint/parser": 8.46.0 - "@typescript-eslint/typescript-estree": 8.46.0 - "@typescript-eslint/utils": 8.46.0 + "@typescript-eslint/parser": 8.46.1 + "@typescript-eslint/typescript-estree": 8.46.1 + "@typescript-eslint/utils": 8.46.1 "@vitest/coverage-v8": ^3.1.3 ajv: ^6.12.6 eslint: "*" @@ -6036,13 +6036,13 @@ __metadata: languageName: unknown linkType: soft -"@typescript-eslint/scope-manager@8.46.0, @typescript-eslint/scope-manager@^8.41.0, @typescript-eslint/scope-manager@workspace:*, @typescript-eslint/scope-manager@workspace:^, @typescript-eslint/scope-manager@workspace:packages/scope-manager": +"@typescript-eslint/scope-manager@8.46.1, @typescript-eslint/scope-manager@^8.41.0, @typescript-eslint/scope-manager@workspace:*, @typescript-eslint/scope-manager@workspace:^, @typescript-eslint/scope-manager@workspace:packages/scope-manager": version: 0.0.0-use.local resolution: "@typescript-eslint/scope-manager@workspace:packages/scope-manager" dependencies: - "@typescript-eslint/types": 8.46.0 - "@typescript-eslint/typescript-estree": 8.46.0 - "@typescript-eslint/visitor-keys": 8.46.0 + "@typescript-eslint/types": 8.46.1 + "@typescript-eslint/typescript-estree": 8.46.1 + "@typescript-eslint/visitor-keys": 8.46.1 "@vitest/coverage-v8": ^3.1.3 "@vitest/pretty-format": ^3.1.3 eslint: "*" @@ -6053,7 +6053,7 @@ __metadata: languageName: unknown linkType: soft -"@typescript-eslint/tsconfig-utils@8.46.0, @typescript-eslint/tsconfig-utils@^8.46.0, @typescript-eslint/tsconfig-utils@workspace:packages/tsconfig-utils": +"@typescript-eslint/tsconfig-utils@8.46.1, @typescript-eslint/tsconfig-utils@^8.46.1, @typescript-eslint/tsconfig-utils@workspace:packages/tsconfig-utils": version: 0.0.0-use.local resolution: "@typescript-eslint/tsconfig-utils@workspace:packages/tsconfig-utils" dependencies: @@ -6066,14 +6066,14 @@ __metadata: languageName: unknown linkType: soft -"@typescript-eslint/type-utils@8.46.0, @typescript-eslint/type-utils@workspace:*, @typescript-eslint/type-utils@workspace:packages/type-utils": +"@typescript-eslint/type-utils@8.46.1, @typescript-eslint/type-utils@workspace:*, @typescript-eslint/type-utils@workspace:packages/type-utils": version: 0.0.0-use.local resolution: "@typescript-eslint/type-utils@workspace:packages/type-utils" dependencies: - "@typescript-eslint/parser": 8.46.0 - "@typescript-eslint/types": 8.46.0 - "@typescript-eslint/typescript-estree": 8.46.0 - "@typescript-eslint/utils": 8.46.0 + "@typescript-eslint/parser": 8.46.1 + "@typescript-eslint/types": 8.46.1 + "@typescript-eslint/typescript-estree": 8.46.1 + "@typescript-eslint/utils": 8.46.1 "@vitest/coverage-v8": ^3.1.3 ajv: ^6.12.6 debug: ^4.3.4 @@ -6088,7 +6088,7 @@ __metadata: languageName: unknown linkType: soft -"@typescript-eslint/types@8.46.0, @typescript-eslint/types@^8.11.0, @typescript-eslint/types@^8.34.1, @typescript-eslint/types@^8.46.0, @typescript-eslint/types@workspace:*, @typescript-eslint/types@workspace:^, @typescript-eslint/types@workspace:packages/types": +"@typescript-eslint/types@8.46.1, @typescript-eslint/types@^8.11.0, @typescript-eslint/types@^8.34.1, @typescript-eslint/types@^8.46.1, @typescript-eslint/types@workspace:*, @typescript-eslint/types@workspace:^, @typescript-eslint/types@workspace:packages/types": version: 0.0.0-use.local resolution: "@typescript-eslint/types@workspace:packages/types" dependencies: @@ -6162,15 +6162,15 @@ __metadata: languageName: unknown linkType: soft -"@typescript-eslint/typescript-estree@8.46.0, @typescript-eslint/typescript-estree@workspace:*, @typescript-eslint/typescript-estree@workspace:^, @typescript-eslint/typescript-estree@workspace:packages/typescript-estree": +"@typescript-eslint/typescript-estree@8.46.1, @typescript-eslint/typescript-estree@workspace:*, @typescript-eslint/typescript-estree@workspace:^, @typescript-eslint/typescript-estree@workspace:packages/typescript-estree": version: 0.0.0-use.local resolution: "@typescript-eslint/typescript-estree@workspace:packages/typescript-estree" dependencies: "@types/is-glob": ^4.0.4 - "@typescript-eslint/project-service": 8.46.0 - "@typescript-eslint/tsconfig-utils": 8.46.0 - "@typescript-eslint/types": 8.46.0 - "@typescript-eslint/visitor-keys": 8.46.0 + "@typescript-eslint/project-service": 8.46.1 + "@typescript-eslint/tsconfig-utils": 8.46.1 + "@typescript-eslint/types": 8.46.1 + "@typescript-eslint/visitor-keys": 8.46.1 "@vitest/coverage-v8": ^3.1.3 debug: ^4.3.4 eslint: "*" @@ -6188,14 +6188,14 @@ __metadata: languageName: unknown linkType: soft -"@typescript-eslint/utils@8.46.0, @typescript-eslint/utils@^8.24.1, @typescript-eslint/utils@^8.34.1, @typescript-eslint/utils@workspace:*, @typescript-eslint/utils@workspace:^, @typescript-eslint/utils@workspace:packages/utils": +"@typescript-eslint/utils@8.46.1, @typescript-eslint/utils@^8.24.1, @typescript-eslint/utils@^8.34.1, @typescript-eslint/utils@workspace:*, @typescript-eslint/utils@workspace:^, @typescript-eslint/utils@workspace:packages/utils": version: 0.0.0-use.local resolution: "@typescript-eslint/utils@workspace:packages/utils" dependencies: "@eslint-community/eslint-utils": ^4.7.0 - "@typescript-eslint/scope-manager": 8.46.0 - "@typescript-eslint/types": 8.46.0 - "@typescript-eslint/typescript-estree": 8.46.0 + "@typescript-eslint/scope-manager": 8.46.1 + "@typescript-eslint/types": 8.46.1 + "@typescript-eslint/typescript-estree": 8.46.1 "@vitest/coverage-v8": ^3.1.3 eslint: "*" rimraf: "*" @@ -6207,11 +6207,11 @@ __metadata: languageName: unknown linkType: soft -"@typescript-eslint/visitor-keys@8.46.0, @typescript-eslint/visitor-keys@workspace:*, @typescript-eslint/visitor-keys@workspace:packages/visitor-keys": +"@typescript-eslint/visitor-keys@8.46.1, @typescript-eslint/visitor-keys@workspace:*, @typescript-eslint/visitor-keys@workspace:packages/visitor-keys": version: 0.0.0-use.local resolution: "@typescript-eslint/visitor-keys@workspace:packages/visitor-keys" dependencies: - "@typescript-eslint/types": 8.46.0 + "@typescript-eslint/types": 8.46.1 "@vitest/coverage-v8": ^3.1.3 eslint: "*" eslint-visitor-keys: ^4.2.1 @@ -19196,10 +19196,10 @@ __metadata: languageName: node linkType: hard -"tapable@npm:^2.0.0, tapable@npm:^2.1.1, tapable@npm:^2.2.0, tapable@npm:^2.2.1": - version: 2.2.1 - resolution: "tapable@npm:2.2.1" - checksum: 3b7a1b4d86fa940aad46d9e73d1e8739335efd4c48322cb37d073eb6f80f5281889bf0320c6d8ffcfa1a0dd5bfdbd0f9d037e252ef972aca595330538aac4d51 +"tapable@npm:^2.0.0, tapable@npm:^2.2.0, tapable@npm:^2.2.1, tapable@npm:^2.2.3": + version: 2.3.0 + resolution: "tapable@npm:2.3.0" + checksum: ada1194219ad550e3626d15019d87a2b8e77521d8463ab1135f46356e987a4c37eff1e87ffdd5acd573590962e519cc81e8ea6f7ed632c66bb58c0f12bd772a4 languageName: node linkType: hard @@ -19669,10 +19669,10 @@ __metadata: version: 0.0.0-use.local resolution: "typescript-eslint@workspace:packages/typescript-eslint" dependencies: - "@typescript-eslint/eslint-plugin": 8.46.0 - "@typescript-eslint/parser": 8.46.0 - "@typescript-eslint/typescript-estree": 8.46.0 - "@typescript-eslint/utils": 8.46.0 + "@typescript-eslint/eslint-plugin": 8.46.1 + "@typescript-eslint/parser": 8.46.1 + "@typescript-eslint/typescript-estree": 8.46.1 + "@typescript-eslint/utils": 8.46.1 "@vitest/coverage-v8": ^3.1.3 eslint: "*" rimraf: "*" @@ -20207,13 +20207,13 @@ __metadata: languageName: node linkType: hard -"watchpack@npm:^2.4.1": - version: 2.4.1 - resolution: "watchpack@npm:2.4.1" +"watchpack@npm:^2.4.4": + version: 2.4.4 + resolution: "watchpack@npm:2.4.4" dependencies: glob-to-regexp: ^0.4.1 graceful-fs: ^4.1.2 - checksum: 5b0179348655dcdf19cac7cb4ff923fdc024d630650c0bf6bec8899cf47c60e19d4f810a88dba692ed0e7f684cf0fcffea86efdbf6c35d81f031e328043b7fab + checksum: 469514a04bcdd7ea77d4b3c62d1f087eafbce64cbc728c89355d5710ee01311533456122da7c585d3654d5bfcf09e6085db1a6eb274c4762a18e370526d17561 languageName: node linkType: hard @@ -20370,8 +20370,8 @@ __metadata: linkType: hard "webpack@npm:^5.88.1, webpack@npm:^5.91.0, webpack@npm:^5.95.0": - version: 5.101.3 - resolution: "webpack@npm:5.101.3" + version: 5.102.0 + resolution: "webpack@npm:5.102.0" dependencies: "@types/eslint-scope": ^3.7.7 "@types/estree": ^1.0.8 @@ -20381,7 +20381,7 @@ __metadata: "@webassemblyjs/wasm-parser": ^1.14.1 acorn: ^8.15.0 acorn-import-phases: ^1.0.3 - browserslist: ^4.24.0 + browserslist: ^4.24.5 chrome-trace-event: ^1.0.2 enhanced-resolve: ^5.17.3 es-module-lexer: ^1.2.1 @@ -20394,16 +20394,16 @@ __metadata: mime-types: ^2.1.27 neo-async: ^2.6.2 schema-utils: ^4.3.2 - tapable: ^2.1.1 + tapable: ^2.2.3 terser-webpack-plugin: ^5.3.11 - watchpack: ^2.4.1 + watchpack: ^2.4.4 webpack-sources: ^3.3.3 peerDependenciesMeta: webpack-cli: optional: true bin: webpack: bin/webpack.js - checksum: d23fd86b6bc9854f9b488830d9aa123a7f654ee22849bc8670d0a22add88951f6d9cab1d04087758b642fc31115e3b97e0ac56b80b2200c04c486067aa945663 + checksum: 0df990632239a9127c36acf219f208e9f534fd53b4a4534ab4dbd6904cfbac10e44c73d5d04fd0617f0fe2150fc222f2839d7558b68c3a617855c8b71b8254cb languageName: node linkType: hard