@typescript-eslint/eslint-plugin 8.46.1-alpha.0 → 8.46.1-alpha.2
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.
- package/dist/rules/no-base-to-string.js +1 -0
- package/dist/rules/prefer-includes.js +1 -1
- package/dist/rules/prefer-optional-chain-utils/analyzeChain.d.ts +2 -2
- package/dist/rules/prefer-optional-chain-utils/analyzeChain.js +112 -18
- package/dist/rules/prefer-optional-chain-utils/gatherLogicalOperands.d.ts +16 -1
- package/dist/rules/prefer-optional-chain-utils/gatherLogicalOperands.js +112 -44
- package/dist/rules/prefer-optional-chain.js +4 -0
- package/dist/rules/prefer-string-starts-ends-with.js +1 -1
- package/package.json +8 -8
@@ -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.
|
@@ -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
|
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 (
|
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
|
135
|
+
return [operand];
|
81
136
|
}
|
82
|
-
return
|
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,
|
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 (
|
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
|
-
|
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
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
comparedValue === ComparisonValueType.
|
169
|
-
|
170
|
-
|
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.
|
193
|
-
: NullishComparisonType.
|
182
|
+
? NullishComparisonType.NotEqualNullOrUndefined
|
183
|
+
: NullishComparisonType.EqualNullOrUndefined,
|
194
184
|
isYoda,
|
195
185
|
node: operand,
|
196
186
|
type: OperandValidity.Valid,
|
197
187
|
});
|
198
188
|
continue;
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
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
|
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.
|
3
|
+
"version": "8.46.1-alpha.2",
|
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.
|
63
|
-
"@typescript-eslint/type-utils": "8.46.1-alpha.
|
64
|
-
"@typescript-eslint/utils": "8.46.1-alpha.
|
65
|
-
"@typescript-eslint/visitor-keys": "8.46.1-alpha.
|
62
|
+
"@typescript-eslint/scope-manager": "8.46.1-alpha.2",
|
63
|
+
"@typescript-eslint/type-utils": "8.46.1-alpha.2",
|
64
|
+
"@typescript-eslint/utils": "8.46.1-alpha.2",
|
65
|
+
"@typescript-eslint/visitor-keys": "8.46.1-alpha.2",
|
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.
|
75
|
-
"@typescript-eslint/rule-tester": "8.46.1-alpha.
|
74
|
+
"@typescript-eslint/rule-schema-to-typescript-types": "8.46.1-alpha.2",
|
75
|
+
"@typescript-eslint/rule-tester": "8.46.1-alpha.2",
|
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.
|
95
|
+
"@typescript-eslint/parser": "^8.46.1-alpha.2",
|
96
96
|
"eslint": "^8.57.0 || ^9.0.0",
|
97
97
|
"typescript": ">=4.8.4 <6.0.0"
|
98
98
|
},
|