@typescript-eslint/eslint-plugin 8.46.1-alpha.1 → 8.46.1-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -239,6 +239,7 @@ exports.default = (0, util_1.createRule)({
239
239
  return Usefulness.Always;
240
240
  }
241
241
  const declarations = toString.getDeclarations();
242
+ // eslint-disable-next-line @typescript-eslint/prefer-optional-chain
242
243
  if (declarations == null || declarations.length !== 1) {
243
244
  // If there are multiple declarations, at least one of them must not be
244
245
  // the default object toString.
@@ -37,6 +37,7 @@ const utils_1 = require("@typescript-eslint/utils");
37
37
  const tsutils = __importStar(require("ts-api-utils"));
38
38
  const ts = __importStar(require("typescript"));
39
39
  const util_1 = require("../util");
40
+ const promiseUtils_1 = require("../util/promiseUtils");
40
41
  function parseChecksVoidReturn(checksVoidReturn) {
41
42
  switch (checksVoidReturn) {
42
43
  case false:
@@ -302,6 +303,10 @@ exports.default = (0, util_1.createRule)({
302
303
  }
303
304
  }
304
305
  function checkArguments(node) {
306
+ if (node.type === utils_1.AST_NODE_TYPES.CallExpression &&
307
+ isPromiseFinallyMethod(node)) {
308
+ return;
309
+ }
305
310
  const tsNode = services.esTreeNodeToTSNodeMap.get(node);
306
311
  const voidArgs = voidFunctionArguments(checker, tsNode);
307
312
  if (voidArgs.size === 0) {
@@ -467,6 +472,11 @@ exports.default = (0, util_1.createRule)({
467
472
  });
468
473
  }
469
474
  }
475
+ function isPromiseFinallyMethod(node) {
476
+ const promiseFinallyCall = (0, promiseUtils_1.parseFinallyCall)(node, context);
477
+ return (promiseFinallyCall != null &&
478
+ (0, util_1.isPromiseLike)(services.program, (0, util_1.getConstrainedTypeAtLocation)(services, promiseFinallyCall.object)));
479
+ }
470
480
  function checkClassLikeOrInterfaceNode(node) {
471
481
  const tsNode = services.esTreeNodeToTSNodeMap.get(node);
472
482
  const heritageTypes = getHeritageTypes(checker, tsNode);
@@ -60,7 +60,7 @@ exports.default = (0, util_1.createRule)({
60
60
  const checker = services.program.getTypeChecker();
61
61
  function isNumber(node, value) {
62
62
  const evaluated = (0, util_1.getStaticValue)(node, globalScope);
63
- return evaluated != null && evaluated.value === value;
63
+ return evaluated?.value === value;
64
64
  }
65
65
  function isPositiveCheck(node) {
66
66
  switch (node.operator) {
@@ -1,7 +1,7 @@
1
1
  import type { ParserServicesWithTypeInformation, TSESTree } from '@typescript-eslint/utils';
2
2
  import type { RuleContext } from '@typescript-eslint/utils/ts-eslint';
3
- import type { ValidOperand } from './gatherLogicalOperands';
3
+ import type { LastChainOperand, ValidOperand } from './gatherLogicalOperands';
4
4
  import type { PreferOptionalChainMessageIds, PreferOptionalChainOptions } from './PreferOptionalChainOptions';
5
5
  export declare function analyzeChain(context: RuleContext<PreferOptionalChainMessageIds, [
6
6
  PreferOptionalChainOptions
7
- ]>, parserServices: ParserServicesWithTypeInformation, options: PreferOptionalChainOptions, node: TSESTree.Node, operator: TSESTree.LogicalExpression['operator'], chain: ValidOperand[]): void;
7
+ ]>, parserServices: ParserServicesWithTypeInformation, options: PreferOptionalChainOptions, node: TSESTree.Node, operator: TSESTree.LogicalExpression['operator'], chain: ValidOperand[], lastChainOperand?: LastChainOperand): void;
@@ -51,17 +51,71 @@ function includesType(parserServices, node, typeFlagIn) {
51
51
  }
52
52
  return false;
53
53
  }
54
+ function isAlwaysTruthyOperand(comparedName, nullishComparisonType, parserServices) {
55
+ const ANY_UNKNOWN_FLAGS = ts.TypeFlags.Any | ts.TypeFlags.Unknown;
56
+ const comparedNameType = parserServices.getTypeAtLocation(comparedName);
57
+ if ((0, util_1.isTypeFlagSet)(comparedNameType, ANY_UNKNOWN_FLAGS)) {
58
+ return false;
59
+ }
60
+ switch (nullishComparisonType) {
61
+ case gatherLogicalOperands_1.NullishComparisonType.Boolean:
62
+ case gatherLogicalOperands_1.NullishComparisonType.NotBoolean: {
63
+ const types = (0, ts_api_utils_1.unionConstituents)(comparedNameType);
64
+ return types.every(type => !(0, ts_api_utils_1.isFalsyType)(type));
65
+ }
66
+ case gatherLogicalOperands_1.NullishComparisonType.NotStrictEqualUndefined:
67
+ case gatherLogicalOperands_1.NullishComparisonType.NotStrictEqualNull:
68
+ case gatherLogicalOperands_1.NullishComparisonType.StrictEqualNull:
69
+ case gatherLogicalOperands_1.NullishComparisonType.StrictEqualUndefined:
70
+ return !(0, util_1.isTypeFlagSet)(comparedNameType, ts.TypeFlags.Null | ts.TypeFlags.Undefined);
71
+ case gatherLogicalOperands_1.NullishComparisonType.NotEqualNullOrUndefined:
72
+ case gatherLogicalOperands_1.NullishComparisonType.EqualNullOrUndefined:
73
+ return !(0, util_1.isTypeFlagSet)(comparedNameType, ts.TypeFlags.Null | ts.TypeFlags.Undefined);
74
+ }
75
+ }
76
+ function isValidAndLastChainOperand(ComparisonValueType, comparisonType, parserServices) {
77
+ const type = parserServices.getTypeAtLocation(ComparisonValueType);
78
+ const ANY_UNKNOWN_FLAGS = ts.TypeFlags.Any | ts.TypeFlags.Unknown;
79
+ const types = (0, ts_api_utils_1.unionConstituents)(type);
80
+ switch (comparisonType) {
81
+ case gatherLogicalOperands_1.ComparisonType.Equal: {
82
+ const isNullish = types.some(t => (0, util_1.isTypeFlagSet)(t, ANY_UNKNOWN_FLAGS | ts.TypeFlags.Null | ts.TypeFlags.Undefined));
83
+ return !isNullish;
84
+ }
85
+ case gatherLogicalOperands_1.ComparisonType.StrictEqual: {
86
+ const isUndefined = types.some(t => (0, util_1.isTypeFlagSet)(t, ANY_UNKNOWN_FLAGS | ts.TypeFlags.Undefined));
87
+ return !isUndefined;
88
+ }
89
+ case gatherLogicalOperands_1.ComparisonType.NotStrictEqual: {
90
+ return types.every(t => (0, util_1.isTypeFlagSet)(t, ts.TypeFlags.Undefined));
91
+ }
92
+ case gatherLogicalOperands_1.ComparisonType.NotEqual: {
93
+ return types.every(t => (0, util_1.isTypeFlagSet)(t, ts.TypeFlags.Undefined | ts.TypeFlags.Null));
94
+ }
95
+ }
96
+ }
97
+ function isValidOrLastChainOperand(ComparisonValueType, comparisonType, parserServices) {
98
+ const type = parserServices.getTypeAtLocation(ComparisonValueType);
99
+ const ANY_UNKNOWN_FLAGS = ts.TypeFlags.Any | ts.TypeFlags.Unknown;
100
+ const types = (0, ts_api_utils_1.unionConstituents)(type);
101
+ switch (comparisonType) {
102
+ case gatherLogicalOperands_1.ComparisonType.NotEqual: {
103
+ const isNullish = types.some(t => (0, util_1.isTypeFlagSet)(t, ANY_UNKNOWN_FLAGS | ts.TypeFlags.Null | ts.TypeFlags.Undefined));
104
+ return !isNullish;
105
+ }
106
+ case gatherLogicalOperands_1.ComparisonType.NotStrictEqual: {
107
+ const isUndefined = types.some(t => (0, util_1.isTypeFlagSet)(t, ANY_UNKNOWN_FLAGS | ts.TypeFlags.Undefined));
108
+ return !isUndefined;
109
+ }
110
+ case gatherLogicalOperands_1.ComparisonType.Equal:
111
+ return types.every(t => (0, util_1.isTypeFlagSet)(t, ts.TypeFlags.Undefined | ts.TypeFlags.Null));
112
+ case gatherLogicalOperands_1.ComparisonType.StrictEqual:
113
+ return types.every(t => (0, util_1.isTypeFlagSet)(t, ts.TypeFlags.Undefined));
114
+ }
115
+ }
54
116
  const analyzeAndChainOperand = (parserServices, operand, index, chain) => {
55
117
  switch (operand.comparisonType) {
56
- case gatherLogicalOperands_1.NullishComparisonType.Boolean: {
57
- const nextOperand = chain.at(index + 1);
58
- if (nextOperand?.comparisonType ===
59
- gatherLogicalOperands_1.NullishComparisonType.NotStrictEqualNull &&
60
- operand.comparedName.type === utils_1.AST_NODE_TYPES.Identifier) {
61
- return null;
62
- }
63
- return [operand];
64
- }
118
+ case gatherLogicalOperands_1.NullishComparisonType.Boolean:
65
119
  case gatherLogicalOperands_1.NullishComparisonType.NotEqualNullOrUndefined:
66
120
  return [operand];
67
121
  case gatherLogicalOperands_1.NullishComparisonType.NotStrictEqualNull: {
@@ -73,13 +127,14 @@ const analyzeAndChainOperand = (parserServices, operand, index, chain) => {
73
127
  compareNodes_1.NodeComparisonResult.Equal) {
74
128
  return [operand, nextOperand];
75
129
  }
76
- if (includesType(parserServices, operand.comparedName, ts.TypeFlags.Undefined)) {
130
+ if (nextOperand &&
131
+ !includesType(parserServices, operand.comparedName, ts.TypeFlags.Undefined)) {
77
132
  // we know the next operand is not an `undefined` check and that this
78
133
  // operand includes `undefined` - which means that making this an
79
134
  // optional chain would change the runtime behavior of the expression
80
- return null;
135
+ return [operand];
81
136
  }
82
- return [operand];
137
+ return null;
83
138
  }
84
139
  case gatherLogicalOperands_1.NullishComparisonType.NotStrictEqualUndefined: {
85
140
  // handle `x !== undefined && x !== null`
@@ -172,7 +227,8 @@ function getReportRange(chain, boundary, sourceCode) {
172
227
  }
173
228
  return [leftMost.range[0], rightMost.range[1]];
174
229
  }
175
- function getReportDescriptor(sourceCode, parserServices, node, operator, options, chain) {
230
+ function getReportDescriptor(sourceCode, parserServices, node, operator, options, subChain, lastChain) {
231
+ const chain = lastChain ? [...subChain, lastChain] : subChain;
176
232
  const lastOperand = chain[chain.length - 1];
177
233
  let useSuggestionFixer;
178
234
  if (options.allowPotentiallyUnsafeFixesThatModifyTheReturnTypeIKnowWhatImDoing ===
@@ -184,7 +240,8 @@ function getReportDescriptor(sourceCode, parserServices, node, operator, options
184
240
  // so we need to make sure that there is at least one operand that includes
185
241
  // `undefined`, or else we're going to change the final type - which is
186
242
  // unsafe and might cause downstream type errors.
187
- else if (lastOperand.comparisonType === gatherLogicalOperands_1.NullishComparisonType.EqualNullOrUndefined ||
243
+ else if (lastChain ||
244
+ lastOperand.comparisonType === gatherLogicalOperands_1.NullishComparisonType.EqualNullOrUndefined ||
188
245
  lastOperand.comparisonType ===
189
246
  gatherLogicalOperands_1.NullishComparisonType.NotEqualNullOrUndefined ||
190
247
  lastOperand.comparisonType === gatherLogicalOperands_1.NullishComparisonType.StrictEqualUndefined ||
@@ -383,9 +440,9 @@ function getReportDescriptor(sourceCode, parserServices, node, operator, options
383
440
  }
384
441
  }
385
442
  }
386
- function analyzeChain(context, parserServices, options, node, operator, chain) {
443
+ function analyzeChain(context, parserServices, options, node, operator, chain, lastChainOperand) {
387
444
  // need at least 2 operands in a chain for it to be a chain
388
- if (chain.length <= 1 ||
445
+ if (chain.length + (lastChainOperand ? 1 : 0) <= 1 ||
389
446
  /* istanbul ignore next -- previous checks make this unreachable, but keep it for exhaustiveness check */
390
447
  operator === '??') {
391
448
  return;
@@ -401,10 +458,14 @@ function analyzeChain(context, parserServices, options, node, operator, chain) {
401
458
  // Things like x !== null && x !== undefined have two nodes, but they are
402
459
  // one logical unit here, so we'll allow them to be grouped.
403
460
  let subChain = [];
461
+ let lastChain = undefined;
404
462
  const maybeReportThenReset = (newChainSeed) => {
405
- if (subChain.length > 1) {
463
+ if (subChain.length + (lastChain ? 1 : 0) > 1) {
406
464
  const subChainFlat = subChain.flat();
407
- (0, checkNullishAndReport_1.checkNullishAndReport)(context, parserServices, options, subChainFlat.slice(0, -1).map(({ node }) => node), getReportDescriptor(context.sourceCode, parserServices, node, operator, options, subChainFlat));
465
+ const maybeNullishNodes = lastChain
466
+ ? subChainFlat.map(({ node }) => node)
467
+ : subChainFlat.slice(0, -1).map(({ node }) => node);
468
+ (0, checkNullishAndReport_1.checkNullishAndReport)(context, parserServices, options, maybeNullishNodes, getReportDescriptor(context.sourceCode, parserServices, node, operator, options, subChainFlat, lastChain));
408
469
  }
409
470
  // we've reached the end of a chain of logical expressions
410
471
  // i.e. the current operand doesn't belong to the previous chain.
@@ -419,6 +480,7 @@ function analyzeChain(context, parserServices, options, node, operator, chain) {
419
480
  // ^^^^^^^^^^^ newChainSeed
420
481
  // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ second chain
421
482
  subChain = newChainSeed ? [newChainSeed] : [];
483
+ lastChain = undefined;
422
484
  };
423
485
  for (let i = 0; i < chain.length; i += 1) {
424
486
  const lastOperand = subChain.flat().at(-1);
@@ -433,6 +495,26 @@ function analyzeChain(context, parserServices, options, node, operator, chain) {
433
495
  // ^^^^^^^^^^^ valid OR chain
434
496
  // ^^^^^^^ invalid OR chain logical, but still part of
435
497
  // the chain for combination purposes
498
+ if (lastOperand) {
499
+ const comparisonResult = (0, compareNodes_1.compareNodes)(lastOperand.comparedName, operand.comparedName);
500
+ switch (operand.comparisonType) {
501
+ case gatherLogicalOperands_1.NullishComparisonType.StrictEqualUndefined:
502
+ case gatherLogicalOperands_1.NullishComparisonType.NotStrictEqualUndefined: {
503
+ if (comparisonResult === compareNodes_1.NodeComparisonResult.Subset) {
504
+ lastChain = operand;
505
+ }
506
+ break;
507
+ }
508
+ case gatherLogicalOperands_1.NullishComparisonType.StrictEqualNull:
509
+ case gatherLogicalOperands_1.NullishComparisonType.NotStrictEqualNull: {
510
+ if (comparisonResult === compareNodes_1.NodeComparisonResult.Subset &&
511
+ isAlwaysTruthyOperand(lastOperand.comparedName, lastOperand.comparisonType, parserServices)) {
512
+ lastChain = operand;
513
+ }
514
+ break;
515
+ }
516
+ }
517
+ }
436
518
  maybeReportThenReset();
437
519
  continue;
438
520
  }
@@ -462,6 +544,18 @@ function analyzeChain(context, parserServices, options, node, operator, chain) {
462
544
  subChain.push(currentOperand);
463
545
  }
464
546
  }
547
+ const lastOperand = subChain.flat().at(-1);
548
+ if (lastOperand && lastChainOperand) {
549
+ const comparisonResult = (0, compareNodes_1.compareNodes)(lastOperand.comparedName, lastChainOperand.comparedName);
550
+ const isValidLastChainOperand = operator === '&&'
551
+ ? isValidAndLastChainOperand
552
+ : isValidOrLastChainOperand;
553
+ if (comparisonResult === compareNodes_1.NodeComparisonResult.Subset &&
554
+ (isAlwaysTruthyOperand(lastOperand.comparedName, lastOperand.comparisonType, parserServices) ||
555
+ isValidLastChainOperand(lastChainOperand.comparisonValue, lastChainOperand.comparisonType, parserServices))) {
556
+ lastChain = lastChainOperand;
557
+ }
558
+ }
465
559
  // check the leftovers
466
560
  maybeReportThenReset();
467
561
  }
@@ -3,6 +3,7 @@ import type { SourceCode } from '@typescript-eslint/utils/ts-eslint';
3
3
  import type { PreferOptionalChainOptions } from './PreferOptionalChainOptions';
4
4
  export declare const enum OperandValidity {
5
5
  Valid = "Valid",
6
+ Last = "Last",
6
7
  Invalid = "Invalid"
7
8
  }
8
9
  export declare const enum NullishComparisonType {
@@ -23,6 +24,12 @@ export declare const enum NullishComparisonType {
23
24
  /** `x` */
24
25
  Boolean = "Boolean"
25
26
  }
27
+ export declare const enum ComparisonType {
28
+ NotEqual = "NotEqual",
29
+ Equal = "Equal",
30
+ NotStrictEqual = "NotStrictEqual",
31
+ StrictEqual = "StrictEqual"
32
+ }
26
33
  export interface ValidOperand {
27
34
  comparedName: TSESTree.Node;
28
35
  comparisonType: NullishComparisonType;
@@ -30,10 +37,18 @@ export interface ValidOperand {
30
37
  node: TSESTree.Expression;
31
38
  type: OperandValidity.Valid;
32
39
  }
40
+ export interface LastChainOperand {
41
+ comparedName: TSESTree.Node;
42
+ comparisonType: ComparisonType;
43
+ comparisonValue: TSESTree.Node;
44
+ isYoda: boolean;
45
+ node: TSESTree.BinaryExpression;
46
+ type: OperandValidity.Last;
47
+ }
33
48
  export interface InvalidOperand {
34
49
  type: OperandValidity.Invalid;
35
50
  }
36
- type Operand = InvalidOperand | ValidOperand;
51
+ type Operand = InvalidOperand | LastChainOperand | ValidOperand;
37
52
  export declare function gatherLogicalOperands(node: TSESTree.LogicalExpression, parserServices: ParserServicesWithTypeInformation, sourceCode: Readonly<SourceCode>, options: PreferOptionalChainOptions): {
38
53
  newlySeenLogicals: Set<TSESTree.LogicalExpression>;
39
54
  operands: Operand[];
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.NullishComparisonType = exports.OperandValidity = void 0;
36
+ exports.ComparisonType = exports.NullishComparisonType = exports.OperandValidity = void 0;
37
37
  exports.gatherLogicalOperands = gatherLogicalOperands;
38
38
  const utils_1 = require("@typescript-eslint/utils");
39
39
  const ts_api_utils_1 = require("ts-api-utils");
@@ -48,6 +48,7 @@ var ComparisonValueType;
48
48
  var OperandValidity;
49
49
  (function (OperandValidity) {
50
50
  OperandValidity["Valid"] = "Valid";
51
+ OperandValidity["Last"] = "Last";
51
52
  OperandValidity["Invalid"] = "Invalid";
52
53
  })(OperandValidity || (exports.OperandValidity = OperandValidity = {}));
53
54
  var NullishComparisonType;
@@ -69,6 +70,13 @@ var NullishComparisonType;
69
70
  /** `x` */
70
71
  NullishComparisonType["Boolean"] = "Boolean";
71
72
  })(NullishComparisonType || (exports.NullishComparisonType = NullishComparisonType = {}));
73
+ var ComparisonType;
74
+ (function (ComparisonType) {
75
+ ComparisonType["NotEqual"] = "NotEqual";
76
+ ComparisonType["Equal"] = "Equal";
77
+ ComparisonType["NotStrictEqual"] = "NotStrictEqual";
78
+ ComparisonType["StrictEqual"] = "StrictEqual";
79
+ })(ComparisonType || (exports.ComparisonType = ComparisonType = {}));
72
80
  const NULLISH_FLAGS = ts.TypeFlags.Null | ts.TypeFlags.Undefined;
73
81
  function isValidFalseBooleanCheckType(node, disallowFalseyLiteral, parserServices, options) {
74
82
  const type = parserServices.getTypeAtLocation(node);
@@ -161,56 +169,92 @@ function gatherLogicalOperands(node, parserServices, sourceCode, options) {
161
169
  result.push({ type: OperandValidity.Invalid });
162
170
  continue;
163
171
  }
164
- switch (operand.operator) {
165
- case '!=':
166
- case '==':
167
- if (comparedValue === ComparisonValueType.Null ||
168
- comparedValue === ComparisonValueType.Undefined) {
169
- // x == null, x == undefined
170
- result.push({
171
- comparedName: comparedExpression,
172
- comparisonType: operand.operator.startsWith('!')
173
- ? NullishComparisonType.NotEqualNullOrUndefined
174
- : NullishComparisonType.EqualNullOrUndefined,
175
- isYoda,
176
- node: operand,
177
- type: OperandValidity.Valid,
178
- });
179
- continue;
180
- }
181
- // x == something :(
182
- result.push({ type: OperandValidity.Invalid });
183
- continue;
184
- case '!==':
185
- case '===': {
186
- const comparedName = comparedExpression;
187
- switch (comparedValue) {
188
- case ComparisonValueType.Null:
172
+ if (operand.operator.startsWith('!') !== (node.operator === '||')) {
173
+ switch (operand.operator) {
174
+ case '!=':
175
+ case '==':
176
+ if (comparedValue === ComparisonValueType.Null ||
177
+ comparedValue === ComparisonValueType.Undefined) {
178
+ // x == null, x == undefined
189
179
  result.push({
190
- comparedName,
180
+ comparedName: comparedExpression,
191
181
  comparisonType: operand.operator.startsWith('!')
192
- ? NullishComparisonType.NotStrictEqualNull
193
- : NullishComparisonType.StrictEqualNull,
182
+ ? NullishComparisonType.NotEqualNullOrUndefined
183
+ : NullishComparisonType.EqualNullOrUndefined,
194
184
  isYoda,
195
185
  node: operand,
196
186
  type: OperandValidity.Valid,
197
187
  });
198
188
  continue;
199
- case ComparisonValueType.Undefined:
200
- result.push({
201
- comparedName,
202
- comparisonType: operand.operator.startsWith('!')
203
- ? NullishComparisonType.NotStrictEqualUndefined
204
- : NullishComparisonType.StrictEqualUndefined,
205
- isYoda,
206
- node: operand,
207
- type: OperandValidity.Valid,
208
- });
209
- continue;
210
- default:
211
- // x === something :(
212
- result.push({ type: OperandValidity.Invalid });
213
- continue;
189
+ }
190
+ break;
191
+ case '!==':
192
+ case '===': {
193
+ const comparedName = comparedExpression;
194
+ switch (comparedValue) {
195
+ case ComparisonValueType.Null:
196
+ result.push({
197
+ comparedName,
198
+ comparisonType: operand.operator.startsWith('!')
199
+ ? NullishComparisonType.NotStrictEqualNull
200
+ : NullishComparisonType.StrictEqualNull,
201
+ isYoda,
202
+ node: operand,
203
+ type: OperandValidity.Valid,
204
+ });
205
+ continue;
206
+ case ComparisonValueType.Undefined:
207
+ result.push({
208
+ comparedName,
209
+ comparisonType: operand.operator.startsWith('!')
210
+ ? NullishComparisonType.NotStrictEqualUndefined
211
+ : NullishComparisonType.StrictEqualUndefined,
212
+ isYoda,
213
+ node: operand,
214
+ type: OperandValidity.Valid,
215
+ });
216
+ continue;
217
+ }
218
+ }
219
+ }
220
+ }
221
+ // x == something :(
222
+ // x === something :(
223
+ // x != something :(
224
+ // x !== something :(
225
+ const binaryComparisonChain = getBinaryComparisonChain(operand);
226
+ if (binaryComparisonChain) {
227
+ const { comparedName, comparedValue, isYoda } = binaryComparisonChain;
228
+ switch (operand.operator) {
229
+ case '==':
230
+ case '===': {
231
+ const comparisonType = operand.operator === '=='
232
+ ? ComparisonType.Equal
233
+ : ComparisonType.StrictEqual;
234
+ result.push({
235
+ comparedName,
236
+ comparisonType,
237
+ comparisonValue: comparedValue,
238
+ isYoda,
239
+ node: operand,
240
+ type: OperandValidity.Last,
241
+ });
242
+ continue;
243
+ }
244
+ case '!=':
245
+ case '!==': {
246
+ const comparisonType = operand.operator === '!='
247
+ ? ComparisonType.NotEqual
248
+ : ComparisonType.NotStrictEqual;
249
+ result.push({
250
+ comparedName,
251
+ comparisonType,
252
+ comparisonValue: comparedValue,
253
+ isYoda,
254
+ node: operand,
255
+ type: OperandValidity.Last,
256
+ });
257
+ continue;
214
258
  }
215
259
  }
216
260
  }
@@ -318,4 +362,28 @@ function gatherLogicalOperands(node, parserServices, sourceCode, options) {
318
362
  }
319
363
  return null;
320
364
  }
365
+ function getBinaryComparisonChain(node) {
366
+ const { left, right } = node;
367
+ let isYoda = false;
368
+ const isLeftMemberExpression = left.type === utils_1.AST_NODE_TYPES.MemberExpression;
369
+ const isRightMemberExpression = right.type === utils_1.AST_NODE_TYPES.MemberExpression;
370
+ if (isLeftMemberExpression && !isRightMemberExpression) {
371
+ const [comparedName, comparedValue] = [left, right];
372
+ return {
373
+ comparedName,
374
+ comparedValue,
375
+ isYoda,
376
+ };
377
+ }
378
+ if (!isLeftMemberExpression && isRightMemberExpression) {
379
+ const [comparedName, comparedValue] = [right, left];
380
+ isYoda = true;
381
+ return {
382
+ comparedName,
383
+ comparedValue,
384
+ isYoda,
385
+ };
386
+ }
387
+ return null;
388
+ }
321
389
  }
@@ -91,6 +91,10 @@ exports.default = (0, util_1.createRule)({
91
91
  (0, analyzeChain_1.analyzeChain)(context, parserServices, options, node, node.operator, currentChain);
92
92
  currentChain = [];
93
93
  }
94
+ else if (operand.type === gatherLogicalOperands_1.OperandValidity.Last) {
95
+ (0, analyzeChain_1.analyzeChain)(context, parserServices, options, node, node.operator, currentChain, operand);
96
+ currentChain = [];
97
+ }
94
98
  else {
95
99
  currentChain.push(operand);
96
100
  }
@@ -61,7 +61,7 @@ exports.default = (0, util_1.createRule)({
61
61
  */
62
62
  function isNumber(node, value) {
63
63
  const evaluated = (0, util_1.getStaticValue)(node, globalScope);
64
- return evaluated != null && evaluated.value === value;
64
+ return evaluated?.value === value;
65
65
  }
66
66
  /**
67
67
  * Check if a given node is a `Literal` node that is a character.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@typescript-eslint/eslint-plugin",
3
- "version": "8.46.1-alpha.1",
3
+ "version": "8.46.1-alpha.3",
4
4
  "description": "TypeScript plugin for ESLint",
5
5
  "files": [
6
6
  "dist",
@@ -59,10 +59,10 @@
59
59
  },
60
60
  "dependencies": {
61
61
  "@eslint-community/regexpp": "^4.10.0",
62
- "@typescript-eslint/scope-manager": "8.46.1-alpha.1",
63
- "@typescript-eslint/type-utils": "8.46.1-alpha.1",
64
- "@typescript-eslint/utils": "8.46.1-alpha.1",
65
- "@typescript-eslint/visitor-keys": "8.46.1-alpha.1",
62
+ "@typescript-eslint/scope-manager": "8.46.1-alpha.3",
63
+ "@typescript-eslint/type-utils": "8.46.1-alpha.3",
64
+ "@typescript-eslint/utils": "8.46.1-alpha.3",
65
+ "@typescript-eslint/visitor-keys": "8.46.1-alpha.3",
66
66
  "graphemer": "^1.4.0",
67
67
  "ignore": "^7.0.0",
68
68
  "natural-compare": "^1.4.0",
@@ -71,8 +71,8 @@
71
71
  "devDependencies": {
72
72
  "@types/mdast": "^4.0.3",
73
73
  "@types/natural-compare": "*",
74
- "@typescript-eslint/rule-schema-to-typescript-types": "8.46.1-alpha.1",
75
- "@typescript-eslint/rule-tester": "8.46.1-alpha.1",
74
+ "@typescript-eslint/rule-schema-to-typescript-types": "8.46.1-alpha.3",
75
+ "@typescript-eslint/rule-tester": "8.46.1-alpha.3",
76
76
  "@vitest/coverage-v8": "^3.1.3",
77
77
  "ajv": "^6.12.6",
78
78
  "cross-fetch": "*",
@@ -92,7 +92,7 @@
92
92
  "vitest": "^3.1.3"
93
93
  },
94
94
  "peerDependencies": {
95
- "@typescript-eslint/parser": "^8.46.1-alpha.1",
95
+ "@typescript-eslint/parser": "^8.46.1-alpha.3",
96
96
  "eslint": "^8.57.0 || ^9.0.0",
97
97
  "typescript": ">=4.8.4 <6.0.0"
98
98
  },