eslint 8.47.0 → 8.49.0
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/README.md +2 -2
- package/lib/config/rule-validator.js +2 -1
- package/lib/linter/code-path-analysis/code-path-analyzer.js +32 -24
- package/lib/linter/code-path-analysis/code-path-segment.js +52 -24
- package/lib/linter/code-path-analysis/code-path.js +1 -0
- package/lib/linter/linter.js +1 -0
- package/lib/rule-tester/flat-rule-tester.js +54 -20
- package/lib/rule-tester/rule-tester.js +117 -22
- package/lib/rules/array-callback-return.js +36 -11
- package/lib/rules/consistent-return.js +32 -7
- package/lib/rules/constructor-super.js +37 -14
- package/lib/rules/for-direction.js +15 -8
- package/lib/rules/getter-return.js +33 -8
- package/lib/rules/lines-between-class-members.js +92 -7
- package/lib/rules/no-fallthrough.js +42 -14
- package/lib/rules/no-promise-executor-return.js +154 -16
- package/lib/rules/no-this-before-super.js +38 -11
- package/lib/rules/no-unreachable-loop.js +47 -12
- package/lib/rules/no-unreachable.js +39 -10
- package/lib/rules/no-useless-return.js +35 -4
- package/lib/rules/require-atomic-updates.js +21 -7
- package/messages/eslintrc-incompat.js +1 -1
- package/package.json +11 -9
@@ -48,7 +48,8 @@ const
|
|
48
48
|
equal = require("fast-deep-equal"),
|
49
49
|
Traverser = require("../../lib/shared/traverser"),
|
50
50
|
{ getRuleOptionsSchema, validate } = require("../shared/config-validator"),
|
51
|
-
{ Linter, SourceCodeFixer, interpolate } = require("../linter")
|
51
|
+
{ Linter, SourceCodeFixer, interpolate } = require("../linter"),
|
52
|
+
CodePath = require("../linter/code-path-analysis/code-path");
|
52
53
|
|
53
54
|
const ajv = require("../shared/ajv")({ strictDefaults: true });
|
54
55
|
|
@@ -62,6 +63,7 @@ const { SourceCode } = require("../source-code");
|
|
62
63
|
//------------------------------------------------------------------------------
|
63
64
|
|
64
65
|
/** @typedef {import("../shared/types").Parser} Parser */
|
66
|
+
/** @typedef {import("../shared/types").Rule} Rule */
|
65
67
|
|
66
68
|
|
67
69
|
/**
|
@@ -163,6 +165,30 @@ const friendlySuggestionObjectParameterList = `[${[...suggestionObjectParameters
|
|
163
165
|
|
164
166
|
const hasOwnProperty = Function.call.bind(Object.hasOwnProperty);
|
165
167
|
|
168
|
+
const DEPRECATED_SOURCECODE_PASSTHROUGHS = {
|
169
|
+
getSource: "getText",
|
170
|
+
getSourceLines: "getLines",
|
171
|
+
getAllComments: "getAllComments",
|
172
|
+
getNodeByRangeIndex: "getNodeByRangeIndex",
|
173
|
+
|
174
|
+
// getComments: "getComments", -- already handled by a separate error
|
175
|
+
getCommentsBefore: "getCommentsBefore",
|
176
|
+
getCommentsAfter: "getCommentsAfter",
|
177
|
+
getCommentsInside: "getCommentsInside",
|
178
|
+
getJSDocComment: "getJSDocComment",
|
179
|
+
getFirstToken: "getFirstToken",
|
180
|
+
getFirstTokens: "getFirstTokens",
|
181
|
+
getLastToken: "getLastToken",
|
182
|
+
getLastTokens: "getLastTokens",
|
183
|
+
getTokenAfter: "getTokenAfter",
|
184
|
+
getTokenBefore: "getTokenBefore",
|
185
|
+
getTokenByRangeStart: "getTokenByRangeStart",
|
186
|
+
getTokens: "getTokens",
|
187
|
+
getTokensAfter: "getTokensAfter",
|
188
|
+
getTokensBefore: "getTokensBefore",
|
189
|
+
getTokensBetween: "getTokensBetween"
|
190
|
+
};
|
191
|
+
|
166
192
|
/**
|
167
193
|
* Clones a given value deeply.
|
168
194
|
* Note: This ignores `parent` property.
|
@@ -334,6 +360,37 @@ function emitMissingSchemaWarning(ruleName) {
|
|
334
360
|
}
|
335
361
|
}
|
336
362
|
|
363
|
+
/**
|
364
|
+
* Emit a deprecation warning if a rule uses a deprecated `context` method.
|
365
|
+
* @param {string} ruleName Name of the rule.
|
366
|
+
* @param {string} methodName The name of the method on `context` that was used.
|
367
|
+
* @returns {void}
|
368
|
+
*/
|
369
|
+
function emitDeprecatedContextMethodWarning(ruleName, methodName) {
|
370
|
+
if (!emitDeprecatedContextMethodWarning[`warned-${ruleName}-${methodName}`]) {
|
371
|
+
emitDeprecatedContextMethodWarning[`warned-${ruleName}-${methodName}`] = true;
|
372
|
+
process.emitWarning(
|
373
|
+
`"${ruleName}" rule is using \`context.${methodName}()\`, which is deprecated and will be removed in ESLint v9. Please use \`sourceCode.${DEPRECATED_SOURCECODE_PASSTHROUGHS[methodName]}()\` instead.`,
|
374
|
+
"DeprecationWarning"
|
375
|
+
);
|
376
|
+
}
|
377
|
+
}
|
378
|
+
|
379
|
+
/**
|
380
|
+
* Emit a deprecation warning if rule uses CodePath#currentSegments.
|
381
|
+
* @param {string} ruleName Name of the rule.
|
382
|
+
* @returns {void}
|
383
|
+
*/
|
384
|
+
function emitCodePathCurrentSegmentsWarning(ruleName) {
|
385
|
+
if (!emitCodePathCurrentSegmentsWarning[`warned-${ruleName}`]) {
|
386
|
+
emitCodePathCurrentSegmentsWarning[`warned-${ruleName}`] = true;
|
387
|
+
process.emitWarning(
|
388
|
+
`"${ruleName}" rule uses CodePath#currentSegments and will stop working in ESLint v9. Please read the documentation for how to update your code: https://eslint.org/docs/latest/extend/code-path-analysis#usage-examples`,
|
389
|
+
"DeprecationWarning"
|
390
|
+
);
|
391
|
+
}
|
392
|
+
}
|
393
|
+
|
337
394
|
//------------------------------------------------------------------------------
|
338
395
|
// Public Interface
|
339
396
|
//------------------------------------------------------------------------------
|
@@ -508,17 +565,20 @@ class RuleTester {
|
|
508
565
|
/**
|
509
566
|
* Define a rule for one particular run of tests.
|
510
567
|
* @param {string} name The name of the rule to define.
|
511
|
-
* @param {Function} rule The rule definition.
|
568
|
+
* @param {Function | Rule} rule The rule definition.
|
512
569
|
* @returns {void}
|
513
570
|
*/
|
514
571
|
defineRule(name, rule) {
|
572
|
+
if (typeof rule === "function") {
|
573
|
+
emitLegacyRuleAPIWarning(name);
|
574
|
+
}
|
515
575
|
this.rules[name] = rule;
|
516
576
|
}
|
517
577
|
|
518
578
|
/**
|
519
579
|
* Adds a new rule test to execute.
|
520
580
|
* @param {string} ruleName The name of the rule to run.
|
521
|
-
* @param {Function} rule The rule to test.
|
581
|
+
* @param {Function | Rule} rule The rule to test.
|
522
582
|
* @param {{
|
523
583
|
* valid: (ValidTestCase | string)[],
|
524
584
|
* invalid: InvalidTestCase[]
|
@@ -562,7 +622,27 @@ class RuleTester {
|
|
562
622
|
freezeDeeply(context.settings);
|
563
623
|
freezeDeeply(context.parserOptions);
|
564
624
|
|
565
|
-
|
625
|
+
const newContext = Object.freeze(
|
626
|
+
Object.create(
|
627
|
+
context,
|
628
|
+
Object.fromEntries(Object.keys(DEPRECATED_SOURCECODE_PASSTHROUGHS).map(methodName => [
|
629
|
+
methodName,
|
630
|
+
{
|
631
|
+
value(...args) {
|
632
|
+
|
633
|
+
// emit deprecation warning
|
634
|
+
emitDeprecatedContextMethodWarning(ruleName, methodName);
|
635
|
+
|
636
|
+
// call the original method
|
637
|
+
return context[methodName].call(this, ...args);
|
638
|
+
},
|
639
|
+
enumerable: true
|
640
|
+
}
|
641
|
+
]))
|
642
|
+
)
|
643
|
+
);
|
644
|
+
|
645
|
+
return (typeof rule === "function" ? rule : rule.create)(newContext);
|
566
646
|
}
|
567
647
|
}));
|
568
648
|
|
@@ -682,13 +762,22 @@ class RuleTester {
|
|
682
762
|
|
683
763
|
// Verify the code.
|
684
764
|
const { getComments } = SourceCode.prototype;
|
765
|
+
const originalCurrentSegments = Object.getOwnPropertyDescriptor(CodePath.prototype, "currentSegments");
|
685
766
|
let messages;
|
686
767
|
|
687
768
|
try {
|
688
769
|
SourceCode.prototype.getComments = getCommentsDeprecation;
|
770
|
+
Object.defineProperty(CodePath.prototype, "currentSegments", {
|
771
|
+
get() {
|
772
|
+
emitCodePathCurrentSegmentsWarning(ruleName);
|
773
|
+
return originalCurrentSegments.get.call(this);
|
774
|
+
}
|
775
|
+
});
|
776
|
+
|
689
777
|
messages = linter.verify(code, config, filename);
|
690
778
|
} finally {
|
691
779
|
SourceCode.prototype.getComments = getComments;
|
780
|
+
Object.defineProperty(CodePath.prototype, "currentSegments", originalCurrentSegments);
|
692
781
|
}
|
693
782
|
|
694
783
|
const fatalErrorMessage = messages.find(m => m.fatal);
|
@@ -1021,29 +1110,35 @@ class RuleTester {
|
|
1021
1110
|
/*
|
1022
1111
|
* This creates a mocha test suite and pipes all supplied info through
|
1023
1112
|
* one of the templates above.
|
1113
|
+
* The test suites for valid/invalid are created conditionally as
|
1114
|
+
* test runners (eg. vitest) fail for empty test suites.
|
1024
1115
|
*/
|
1025
1116
|
this.constructor.describe(ruleName, () => {
|
1026
|
-
|
1027
|
-
|
1028
|
-
|
1029
|
-
|
1030
|
-
|
1031
|
-
|
1032
|
-
|
1033
|
-
|
1117
|
+
if (test.valid.length > 0) {
|
1118
|
+
this.constructor.describe("valid", () => {
|
1119
|
+
test.valid.forEach(valid => {
|
1120
|
+
this.constructor[valid.only ? "itOnly" : "it"](
|
1121
|
+
sanitize(typeof valid === "object" ? valid.name || valid.code : valid),
|
1122
|
+
() => {
|
1123
|
+
testValidTemplate(valid);
|
1124
|
+
}
|
1125
|
+
);
|
1126
|
+
});
|
1034
1127
|
});
|
1035
|
-
}
|
1128
|
+
}
|
1036
1129
|
|
1037
|
-
|
1038
|
-
|
1039
|
-
|
1040
|
-
|
1041
|
-
|
1042
|
-
|
1043
|
-
|
1044
|
-
|
1130
|
+
if (test.invalid.length > 0) {
|
1131
|
+
this.constructor.describe("invalid", () => {
|
1132
|
+
test.invalid.forEach(invalid => {
|
1133
|
+
this.constructor[invalid.only ? "itOnly" : "it"](
|
1134
|
+
sanitize(invalid.name || invalid.code),
|
1135
|
+
() => {
|
1136
|
+
testInvalidTemplate(invalid);
|
1137
|
+
}
|
1138
|
+
);
|
1139
|
+
});
|
1045
1140
|
});
|
1046
|
-
}
|
1141
|
+
}
|
1047
1142
|
});
|
1048
1143
|
}
|
1049
1144
|
}
|
@@ -18,15 +18,6 @@ const astUtils = require("./utils/ast-utils");
|
|
18
18
|
const TARGET_NODE_TYPE = /^(?:Arrow)?FunctionExpression$/u;
|
19
19
|
const TARGET_METHODS = /^(?:every|filter|find(?:Last)?(?:Index)?|flatMap|forEach|map|reduce(?:Right)?|some|sort|toSorted)$/u;
|
20
20
|
|
21
|
-
/**
|
22
|
-
* Checks a given code path segment is reachable.
|
23
|
-
* @param {CodePathSegment} segment A segment to check.
|
24
|
-
* @returns {boolean} `true` if the segment is reachable.
|
25
|
-
*/
|
26
|
-
function isReachable(segment) {
|
27
|
-
return segment.reachable;
|
28
|
-
}
|
29
|
-
|
30
21
|
/**
|
31
22
|
* Checks a given node is a member access which has the specified name's
|
32
23
|
* property.
|
@@ -38,6 +29,22 @@ function isTargetMethod(node) {
|
|
38
29
|
return astUtils.isSpecificMemberAccess(node, null, TARGET_METHODS);
|
39
30
|
}
|
40
31
|
|
32
|
+
/**
|
33
|
+
* Checks all segments in a set and returns true if any are reachable.
|
34
|
+
* @param {Set<CodePathSegment>} segments The segments to check.
|
35
|
+
* @returns {boolean} True if any segment is reachable; false otherwise.
|
36
|
+
*/
|
37
|
+
function isAnySegmentReachable(segments) {
|
38
|
+
|
39
|
+
for (const segment of segments) {
|
40
|
+
if (segment.reachable) {
|
41
|
+
return true;
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
return false;
|
46
|
+
}
|
47
|
+
|
41
48
|
/**
|
42
49
|
* Returns a human-legible description of an array method
|
43
50
|
* @param {string} arrayMethodName A method name to fully qualify
|
@@ -205,7 +212,7 @@ module.exports = {
|
|
205
212
|
messageId = "expectedNoReturnValue";
|
206
213
|
}
|
207
214
|
} else {
|
208
|
-
if (node.body.type === "BlockStatement" && funcInfo.
|
215
|
+
if (node.body.type === "BlockStatement" && isAnySegmentReachable(funcInfo.currentSegments)) {
|
209
216
|
messageId = funcInfo.hasReturn ? "expectedAtEnd" : "expectedInside";
|
210
217
|
}
|
211
218
|
}
|
@@ -242,7 +249,8 @@ module.exports = {
|
|
242
249
|
methodName &&
|
243
250
|
!node.async &&
|
244
251
|
!node.generator,
|
245
|
-
node
|
252
|
+
node,
|
253
|
+
currentSegments: new Set()
|
246
254
|
};
|
247
255
|
},
|
248
256
|
|
@@ -251,6 +259,23 @@ module.exports = {
|
|
251
259
|
funcInfo = funcInfo.upper;
|
252
260
|
},
|
253
261
|
|
262
|
+
onUnreachableCodePathSegmentStart(segment) {
|
263
|
+
funcInfo.currentSegments.add(segment);
|
264
|
+
},
|
265
|
+
|
266
|
+
onUnreachableCodePathSegmentEnd(segment) {
|
267
|
+
funcInfo.currentSegments.delete(segment);
|
268
|
+
},
|
269
|
+
|
270
|
+
onCodePathSegmentStart(segment) {
|
271
|
+
funcInfo.currentSegments.add(segment);
|
272
|
+
},
|
273
|
+
|
274
|
+
onCodePathSegmentEnd(segment) {
|
275
|
+
funcInfo.currentSegments.delete(segment);
|
276
|
+
},
|
277
|
+
|
278
|
+
|
254
279
|
// Checks the return statement is valid.
|
255
280
|
ReturnStatement(node) {
|
256
281
|
|
@@ -16,12 +16,19 @@ const { upperCaseFirst } = require("../shared/string-utils");
|
|
16
16
|
//------------------------------------------------------------------------------
|
17
17
|
|
18
18
|
/**
|
19
|
-
* Checks
|
20
|
-
* @param {CodePathSegment}
|
21
|
-
* @returns {boolean}
|
19
|
+
* Checks all segments in a set and returns true if all are unreachable.
|
20
|
+
* @param {Set<CodePathSegment>} segments The segments to check.
|
21
|
+
* @returns {boolean} True if all segments are unreachable; false otherwise.
|
22
22
|
*/
|
23
|
-
function
|
24
|
-
|
23
|
+
function areAllSegmentsUnreachable(segments) {
|
24
|
+
|
25
|
+
for (const segment of segments) {
|
26
|
+
if (segment.reachable) {
|
27
|
+
return false;
|
28
|
+
}
|
29
|
+
}
|
30
|
+
|
31
|
+
return true;
|
25
32
|
}
|
26
33
|
|
27
34
|
/**
|
@@ -88,7 +95,7 @@ module.exports = {
|
|
88
95
|
* When unreachable, all paths are returned or thrown.
|
89
96
|
*/
|
90
97
|
if (!funcInfo.hasReturnValue ||
|
91
|
-
funcInfo.
|
98
|
+
areAllSegmentsUnreachable(funcInfo.currentSegments) ||
|
92
99
|
astUtils.isES5Constructor(node) ||
|
93
100
|
isClassConstructor(node)
|
94
101
|
) {
|
@@ -141,13 +148,31 @@ module.exports = {
|
|
141
148
|
hasReturn: false,
|
142
149
|
hasReturnValue: false,
|
143
150
|
messageId: "",
|
144
|
-
node
|
151
|
+
node,
|
152
|
+
currentSegments: new Set()
|
145
153
|
};
|
146
154
|
},
|
147
155
|
onCodePathEnd() {
|
148
156
|
funcInfo = funcInfo.upper;
|
149
157
|
},
|
150
158
|
|
159
|
+
onUnreachableCodePathSegmentStart(segment) {
|
160
|
+
funcInfo.currentSegments.add(segment);
|
161
|
+
},
|
162
|
+
|
163
|
+
onUnreachableCodePathSegmentEnd(segment) {
|
164
|
+
funcInfo.currentSegments.delete(segment);
|
165
|
+
},
|
166
|
+
|
167
|
+
onCodePathSegmentStart(segment) {
|
168
|
+
funcInfo.currentSegments.add(segment);
|
169
|
+
},
|
170
|
+
|
171
|
+
onCodePathSegmentEnd(segment) {
|
172
|
+
funcInfo.currentSegments.delete(segment);
|
173
|
+
},
|
174
|
+
|
175
|
+
|
151
176
|
// Reports a given return statement if it's inconsistent.
|
152
177
|
ReturnStatement(node) {
|
153
178
|
const argument = node.argument;
|
@@ -10,12 +10,19 @@
|
|
10
10
|
//------------------------------------------------------------------------------
|
11
11
|
|
12
12
|
/**
|
13
|
-
* Checks
|
14
|
-
* @param {CodePathSegment}
|
15
|
-
* @returns {boolean}
|
13
|
+
* Checks all segments in a set and returns true if any are reachable.
|
14
|
+
* @param {Set<CodePathSegment>} segments The segments to check.
|
15
|
+
* @returns {boolean} True if any segment is reachable; false otherwise.
|
16
16
|
*/
|
17
|
-
function
|
18
|
-
|
17
|
+
function isAnySegmentReachable(segments) {
|
18
|
+
|
19
|
+
for (const segment of segments) {
|
20
|
+
if (segment.reachable) {
|
21
|
+
return true;
|
22
|
+
}
|
23
|
+
}
|
24
|
+
|
25
|
+
return false;
|
19
26
|
}
|
20
27
|
|
21
28
|
/**
|
@@ -210,7 +217,8 @@ module.exports = {
|
|
210
217
|
isConstructor: true,
|
211
218
|
hasExtends: Boolean(superClass),
|
212
219
|
superIsConstructor: isPossibleConstructor(superClass),
|
213
|
-
codePath
|
220
|
+
codePath,
|
221
|
+
currentSegments: new Set()
|
214
222
|
};
|
215
223
|
} else {
|
216
224
|
funcInfo = {
|
@@ -218,7 +226,8 @@ module.exports = {
|
|
218
226
|
isConstructor: false,
|
219
227
|
hasExtends: false,
|
220
228
|
superIsConstructor: false,
|
221
|
-
codePath
|
229
|
+
codePath,
|
230
|
+
currentSegments: new Set()
|
222
231
|
};
|
223
232
|
}
|
224
233
|
},
|
@@ -261,6 +270,9 @@ module.exports = {
|
|
261
270
|
* @returns {void}
|
262
271
|
*/
|
263
272
|
onCodePathSegmentStart(segment) {
|
273
|
+
|
274
|
+
funcInfo.currentSegments.add(segment);
|
275
|
+
|
264
276
|
if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) {
|
265
277
|
return;
|
266
278
|
}
|
@@ -281,6 +293,19 @@ module.exports = {
|
|
281
293
|
}
|
282
294
|
},
|
283
295
|
|
296
|
+
onUnreachableCodePathSegmentStart(segment) {
|
297
|
+
funcInfo.currentSegments.add(segment);
|
298
|
+
},
|
299
|
+
|
300
|
+
onUnreachableCodePathSegmentEnd(segment) {
|
301
|
+
funcInfo.currentSegments.delete(segment);
|
302
|
+
},
|
303
|
+
|
304
|
+
onCodePathSegmentEnd(segment) {
|
305
|
+
funcInfo.currentSegments.delete(segment);
|
306
|
+
},
|
307
|
+
|
308
|
+
|
284
309
|
/**
|
285
310
|
* Update information of the code path segment when a code path was
|
286
311
|
* looped.
|
@@ -344,12 +369,11 @@ module.exports = {
|
|
344
369
|
|
345
370
|
// Reports if needed.
|
346
371
|
if (funcInfo.hasExtends) {
|
347
|
-
const segments = funcInfo.
|
372
|
+
const segments = funcInfo.currentSegments;
|
348
373
|
let duplicate = false;
|
349
374
|
let info = null;
|
350
375
|
|
351
|
-
for (
|
352
|
-
const segment = segments[i];
|
376
|
+
for (const segment of segments) {
|
353
377
|
|
354
378
|
if (segment.reachable) {
|
355
379
|
info = segInfoMap[segment.id];
|
@@ -374,7 +398,7 @@ module.exports = {
|
|
374
398
|
info.validNodes.push(node);
|
375
399
|
}
|
376
400
|
}
|
377
|
-
} else if (funcInfo.
|
401
|
+
} else if (isAnySegmentReachable(funcInfo.currentSegments)) {
|
378
402
|
context.report({
|
379
403
|
messageId: "unexpected",
|
380
404
|
node
|
@@ -398,10 +422,9 @@ module.exports = {
|
|
398
422
|
}
|
399
423
|
|
400
424
|
// Returning argument is a substitute of 'super()'.
|
401
|
-
const segments = funcInfo.
|
425
|
+
const segments = funcInfo.currentSegments;
|
402
426
|
|
403
|
-
for (
|
404
|
-
const segment = segments[i];
|
427
|
+
for (const segment of segments) {
|
405
428
|
|
406
429
|
if (segment.reachable) {
|
407
430
|
const info = segInfoMap[segment.id];
|
@@ -5,6 +5,12 @@
|
|
5
5
|
|
6
6
|
"use strict";
|
7
7
|
|
8
|
+
//------------------------------------------------------------------------------
|
9
|
+
// Requirements
|
10
|
+
//------------------------------------------------------------------------------
|
11
|
+
|
12
|
+
const { getStaticValue } = require("@eslint-community/eslint-utils");
|
13
|
+
|
8
14
|
//------------------------------------------------------------------------------
|
9
15
|
// Rule Definition
|
10
16
|
//------------------------------------------------------------------------------
|
@@ -29,6 +35,7 @@ module.exports = {
|
|
29
35
|
},
|
30
36
|
|
31
37
|
create(context) {
|
38
|
+
const { sourceCode } = context;
|
32
39
|
|
33
40
|
/**
|
34
41
|
* report an error.
|
@@ -46,17 +53,17 @@ module.exports = {
|
|
46
53
|
* check the right side of the assignment
|
47
54
|
* @param {ASTNode} update UpdateExpression to check
|
48
55
|
* @param {int} dir expected direction that could either be turned around or invalidated
|
49
|
-
* @returns {int} return dir, the negated dir or zero if
|
56
|
+
* @returns {int} return dir, the negated dir, or zero if the counter does not change or the direction is not clear
|
50
57
|
*/
|
51
58
|
function getRightDirection(update, dir) {
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
return
|
59
|
+
const staticValue = getStaticValue(update.right, sourceCode.getScope(update));
|
60
|
+
|
61
|
+
if (staticValue && ["bigint", "boolean", "number"].includes(typeof staticValue.value)) {
|
62
|
+
const sign = Math.sign(Number(staticValue.value)) || 0; // convert NaN to 0
|
63
|
+
|
64
|
+
return dir * sign;
|
58
65
|
}
|
59
|
-
return
|
66
|
+
return 0;
|
60
67
|
}
|
61
68
|
|
62
69
|
/**
|
@@ -14,15 +14,23 @@ const astUtils = require("./utils/ast-utils");
|
|
14
14
|
//------------------------------------------------------------------------------
|
15
15
|
// Helpers
|
16
16
|
//------------------------------------------------------------------------------
|
17
|
+
|
17
18
|
const TARGET_NODE_TYPE = /^(?:Arrow)?FunctionExpression$/u;
|
18
19
|
|
19
20
|
/**
|
20
|
-
* Checks a
|
21
|
-
* @param {CodePathSegment}
|
22
|
-
* @returns {boolean}
|
21
|
+
* Checks all segments in a set and returns true if any are reachable.
|
22
|
+
* @param {Set<CodePathSegment>} segments The segments to check.
|
23
|
+
* @returns {boolean} True if any segment is reachable; false otherwise.
|
23
24
|
*/
|
24
|
-
function
|
25
|
-
|
25
|
+
function isAnySegmentReachable(segments) {
|
26
|
+
|
27
|
+
for (const segment of segments) {
|
28
|
+
if (segment.reachable) {
|
29
|
+
return true;
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
return false;
|
26
34
|
}
|
27
35
|
|
28
36
|
//------------------------------------------------------------------------------
|
@@ -71,7 +79,8 @@ module.exports = {
|
|
71
79
|
codePath: null,
|
72
80
|
hasReturn: false,
|
73
81
|
shouldCheck: false,
|
74
|
-
node: null
|
82
|
+
node: null,
|
83
|
+
currentSegments: []
|
75
84
|
};
|
76
85
|
|
77
86
|
/**
|
@@ -85,7 +94,7 @@ module.exports = {
|
|
85
94
|
*/
|
86
95
|
function checkLastSegment(node) {
|
87
96
|
if (funcInfo.shouldCheck &&
|
88
|
-
funcInfo.
|
97
|
+
isAnySegmentReachable(funcInfo.currentSegments)
|
89
98
|
) {
|
90
99
|
context.report({
|
91
100
|
node,
|
@@ -144,7 +153,8 @@ module.exports = {
|
|
144
153
|
codePath,
|
145
154
|
hasReturn: false,
|
146
155
|
shouldCheck: isGetter(node),
|
147
|
-
node
|
156
|
+
node,
|
157
|
+
currentSegments: new Set()
|
148
158
|
};
|
149
159
|
},
|
150
160
|
|
@@ -152,6 +162,21 @@ module.exports = {
|
|
152
162
|
onCodePathEnd() {
|
153
163
|
funcInfo = funcInfo.upper;
|
154
164
|
},
|
165
|
+
onUnreachableCodePathSegmentStart(segment) {
|
166
|
+
funcInfo.currentSegments.add(segment);
|
167
|
+
},
|
168
|
+
|
169
|
+
onUnreachableCodePathSegmentEnd(segment) {
|
170
|
+
funcInfo.currentSegments.delete(segment);
|
171
|
+
},
|
172
|
+
|
173
|
+
onCodePathSegmentStart(segment) {
|
174
|
+
funcInfo.currentSegments.add(segment);
|
175
|
+
},
|
176
|
+
|
177
|
+
onCodePathSegmentEnd(segment) {
|
178
|
+
funcInfo.currentSegments.delete(segment);
|
179
|
+
},
|
155
180
|
|
156
181
|
// Checks the return statement is valid.
|
157
182
|
ReturnStatement(node) {
|