Skip to content

Commit da09f4e

Browse files
nzakasmdjermanovic
andauthoredSep 7, 2023
feat: Implement onUnreachableCodePathStart/End (#17511)
* feat: Implement onUnreachableCodePathStart/End refs # 17457 * Finish up onUnreachable* work * Refactor to account for out-of-order events * Update lib/rules/no-unreachable.js Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com> * Update lib/rules/no-unreachable.js Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com> * Update tests/lib/linter/code-path-analysis/code-path-analyzer.js Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com> * Incorporate feedback * Clean up rules and docs * Update docs * Fix code example * Update docs/src/extend/code-path-analysis.md Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com> * Update docs/src/extend/code-path-analysis.md Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com> * Update docs/src/extend/code-path-analysis.md Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com> * Update lib/rules/consistent-return.js Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com> * Update lib/rules/no-this-before-super.js Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com> * Fix examples * Add deprecation notices to RuleTester/FlatRuleTester * Update config * Add deprecation notices to RuleTester/FlatRuleTester * Fix lint warning * Update docs/src/extend/code-path-analysis.md Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com> * Update docs/src/extend/code-path-analysis.md Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com> * Update docs/src/extend/code-path-analysis.md Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com> * Fix test --------- Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com>
1 parent de86b3b commit da09f4e

20 files changed

+990
-287
lines changed
 

‎docs/src/extend/code-path-analysis.md

+320-162
Large diffs are not rendered by default.

‎eslint.config.js

-1
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,6 @@ module.exports = [
226226
files: [INTERNAL_FILES.RULE_TESTER_PATTERN],
227227
rules: {
228228
"n/no-restricted-require": ["error", [
229-
...createInternalFilesPatterns(INTERNAL_FILES.RULE_TESTER_PATTERN),
230229
resolveAbsolutePath("lib/cli-engine/index.js")
231230
]]
232231
}

‎lib/linter/code-path-analysis/code-path-analyzer.js

+32-24
Original file line numberDiff line numberDiff line change
@@ -192,15 +192,18 @@ function forwardCurrentToHead(analyzer, node) {
192192
headSegment = headSegments[i];
193193

194194
if (currentSegment !== headSegment && currentSegment) {
195-
debug.dump(`onCodePathSegmentEnd ${currentSegment.id}`);
196195

197-
if (currentSegment.reachable) {
198-
analyzer.emitter.emit(
199-
"onCodePathSegmentEnd",
200-
currentSegment,
201-
node
202-
);
203-
}
196+
const eventName = currentSegment.reachable
197+
? "onCodePathSegmentEnd"
198+
: "onUnreachableCodePathSegmentEnd";
199+
200+
debug.dump(`${eventName} ${currentSegment.id}`);
201+
202+
analyzer.emitter.emit(
203+
eventName,
204+
currentSegment,
205+
node
206+
);
204207
}
205208
}
206209

@@ -213,16 +216,19 @@ function forwardCurrentToHead(analyzer, node) {
213216
headSegment = headSegments[i];
214217

215218
if (currentSegment !== headSegment && headSegment) {
216-
debug.dump(`onCodePathSegmentStart ${headSegment.id}`);
219+
220+
const eventName = headSegment.reachable
221+
? "onCodePathSegmentStart"
222+
: "onUnreachableCodePathSegmentStart";
223+
224+
debug.dump(`${eventName} ${headSegment.id}`);
217225

218226
CodePathSegment.markUsed(headSegment);
219-
if (headSegment.reachable) {
220-
analyzer.emitter.emit(
221-
"onCodePathSegmentStart",
222-
headSegment,
223-
node
224-
);
225-
}
227+
analyzer.emitter.emit(
228+
eventName,
229+
headSegment,
230+
node
231+
);
226232
}
227233
}
228234

@@ -241,15 +247,17 @@ function leaveFromCurrentSegment(analyzer, node) {
241247

242248
for (let i = 0; i < currentSegments.length; ++i) {
243249
const currentSegment = currentSegments[i];
250+
const eventName = currentSegment.reachable
251+
? "onCodePathSegmentEnd"
252+
: "onUnreachableCodePathSegmentEnd";
244253

245-
debug.dump(`onCodePathSegmentEnd ${currentSegment.id}`);
246-
if (currentSegment.reachable) {
247-
analyzer.emitter.emit(
248-
"onCodePathSegmentEnd",
249-
currentSegment,
250-
node
251-
);
252-
}
254+
debug.dump(`${eventName} ${currentSegment.id}`);
255+
256+
analyzer.emitter.emit(
257+
eventName,
258+
currentSegment,
259+
node
260+
);
253261
}
254262

255263
state.currentSegments = [];

‎lib/linter/code-path-analysis/code-path.js

+1
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ class CodePath {
117117
/**
118118
* Current code path segments.
119119
* @type {CodePathSegment[]}
120+
* @deprecated
120121
*/
121122
get currentSegments() {
122123
return this.internal.currentSegments;

‎lib/linter/linter.js

+1
Original file line numberDiff line numberDiff line change
@@ -898,6 +898,7 @@ const DEPRECATED_SOURCECODE_PASSTHROUGHS = {
898898
getTokensBetween: "getTokensBetween"
899899
};
900900

901+
901902
const BASE_TRAVERSAL_CONTEXT = Object.freeze(
902903
Object.keys(DEPRECATED_SOURCECODE_PASSTHROUGHS).reduce(
903904
(contextInfo, methodName) =>

‎lib/rule-tester/flat-rule-tester.js

+28-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ const
1616
equal = require("fast-deep-equal"),
1717
Traverser = require("../shared/traverser"),
1818
{ getRuleOptionsSchema } = require("../config/flat-config-helpers"),
19-
{ Linter, SourceCodeFixer, interpolate } = require("../linter");
19+
{ Linter, SourceCodeFixer, interpolate } = require("../linter"),
20+
CodePath = require("../linter/code-path-analysis/code-path");
21+
2022
const { FlatConfigArray } = require("../config/flat-config-array");
2123
const { defaultConfig } = require("../config/default-config");
2224

@@ -274,6 +276,21 @@ function getCommentsDeprecation() {
274276
);
275277
}
276278

279+
/**
280+
* Emit a deprecation warning if rule uses CodePath#currentSegments.
281+
* @param {string} ruleName Name of the rule.
282+
* @returns {void}
283+
*/
284+
function emitCodePathCurrentSegmentsWarning(ruleName) {
285+
if (!emitCodePathCurrentSegmentsWarning[`warned-${ruleName}`]) {
286+
emitCodePathCurrentSegmentsWarning[`warned-${ruleName}`] = true;
287+
process.emitWarning(
288+
`"${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`,
289+
"DeprecationWarning"
290+
);
291+
}
292+
}
293+
277294
//------------------------------------------------------------------------------
278295
// Public Interface
279296
//------------------------------------------------------------------------------
@@ -664,6 +681,7 @@ class FlatRuleTester {
664681

665682
// Verify the code.
666683
const { getComments } = SourceCode.prototype;
684+
const originalCurrentSegments = Object.getOwnPropertyDescriptor(CodePath.prototype, "currentSegments");
667685
let messages;
668686

669687
// check for validation errors
@@ -677,11 +695,20 @@ class FlatRuleTester {
677695

678696
try {
679697
SourceCode.prototype.getComments = getCommentsDeprecation;
698+
Object.defineProperty(CodePath.prototype, "currentSegments", {
699+
get() {
700+
emitCodePathCurrentSegmentsWarning(ruleName);
701+
return originalCurrentSegments.get.call(this);
702+
}
703+
});
704+
680705
messages = linter.verify(code, configs, filename);
681706
} finally {
682707
SourceCode.prototype.getComments = getComments;
708+
Object.defineProperty(CodePath.prototype, "currentSegments", originalCurrentSegments);
683709
}
684710

711+
685712
const fatalErrorMessage = messages.find(m => m.fatal);
686713

687714
assert(!fatalErrorMessage, `A fatal parsing error occurred: ${fatalErrorMessage && fatalErrorMessage.message}`);

‎lib/rule-tester/rule-tester.js

+26-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ const
4848
equal = require("fast-deep-equal"),
4949
Traverser = require("../../lib/shared/traverser"),
5050
{ 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");
5253

5354
const ajv = require("../shared/ajv")({ strictDefaults: true });
5455

@@ -375,6 +376,21 @@ function emitDeprecatedContextMethodWarning(ruleName, methodName) {
375376
}
376377
}
377378

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+
378394
//------------------------------------------------------------------------------
379395
// Public Interface
380396
//------------------------------------------------------------------------------
@@ -746,13 +762,22 @@ class RuleTester {
746762

747763
// Verify the code.
748764
const { getComments } = SourceCode.prototype;
765+
const originalCurrentSegments = Object.getOwnPropertyDescriptor(CodePath.prototype, "currentSegments");
749766
let messages;
750767

751768
try {
752769
SourceCode.prototype.getComments = getCommentsDeprecation;
770+
Object.defineProperty(CodePath.prototype, "currentSegments", {
771+
get() {
772+
emitCodePathCurrentSegmentsWarning(ruleName);
773+
return originalCurrentSegments.get.call(this);
774+
}
775+
});
776+
753777
messages = linter.verify(code, config, filename);
754778
} finally {
755779
SourceCode.prototype.getComments = getComments;
780+
Object.defineProperty(CodePath.prototype, "currentSegments", originalCurrentSegments);
756781
}
757782

758783
const fatalErrorMessage = messages.find(m => m.fatal);

‎lib/rules/array-callback-return.js

+36-11
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,6 @@ const astUtils = require("./utils/ast-utils");
1818
const TARGET_NODE_TYPE = /^(?:Arrow)?FunctionExpression$/u;
1919
const TARGET_METHODS = /^(?:every|filter|find(?:Last)?(?:Index)?|flatMap|forEach|map|reduce(?:Right)?|some|sort|toSorted)$/u;
2020

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-
3021
/**
3122
* Checks a given node is a member access which has the specified name's
3223
* property.
@@ -38,6 +29,22 @@ function isTargetMethod(node) {
3829
return astUtils.isSpecificMemberAccess(node, null, TARGET_METHODS);
3930
}
4031

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+
4148
/**
4249
* Returns a human-legible description of an array method
4350
* @param {string} arrayMethodName A method name to fully qualify
@@ -205,7 +212,7 @@ module.exports = {
205212
messageId = "expectedNoReturnValue";
206213
}
207214
} else {
208-
if (node.body.type === "BlockStatement" && funcInfo.codePath.currentSegments.some(isReachable)) {
215+
if (node.body.type === "BlockStatement" && isAnySegmentReachable(funcInfo.currentSegments)) {
209216
messageId = funcInfo.hasReturn ? "expectedAtEnd" : "expectedInside";
210217
}
211218
}
@@ -242,7 +249,8 @@ module.exports = {
242249
methodName &&
243250
!node.async &&
244251
!node.generator,
245-
node
252+
node,
253+
currentSegments: new Set()
246254
};
247255
},
248256

@@ -251,6 +259,23 @@ module.exports = {
251259
funcInfo = funcInfo.upper;
252260
},
253261

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+
254279
// Checks the return statement is valid.
255280
ReturnStatement(node) {
256281

‎lib/rules/consistent-return.js

+32-7
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,19 @@ const { upperCaseFirst } = require("../shared/string-utils");
1616
//------------------------------------------------------------------------------
1717

1818
/**
19-
* Checks whether or not a given code path segment is unreachable.
20-
* @param {CodePathSegment} segment A CodePathSegment to check.
21-
* @returns {boolean} `true` if the segment is unreachable.
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.
2222
*/
23-
function isUnreachable(segment) {
24-
return !segment.reachable;
23+
function areAllSegmentsUnreachable(segments) {
24+
25+
for (const segment of segments) {
26+
if (segment.reachable) {
27+
return false;
28+
}
29+
}
30+
31+
return true;
2532
}
2633

2734
/**
@@ -88,7 +95,7 @@ module.exports = {
8895
* When unreachable, all paths are returned or thrown.
8996
*/
9097
if (!funcInfo.hasReturnValue ||
91-
funcInfo.codePath.currentSegments.every(isUnreachable) ||
98+
areAllSegmentsUnreachable(funcInfo.currentSegments) ||
9299
astUtils.isES5Constructor(node) ||
93100
isClassConstructor(node)
94101
) {
@@ -141,13 +148,31 @@ module.exports = {
141148
hasReturn: false,
142149
hasReturnValue: false,
143150
messageId: "",
144-
node
151+
node,
152+
currentSegments: new Set()
145153
};
146154
},
147155
onCodePathEnd() {
148156
funcInfo = funcInfo.upper;
149157
},
150158

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+
151176
// Reports a given return statement if it's inconsistent.
152177
ReturnStatement(node) {
153178
const argument = node.argument;

‎lib/rules/constructor-super.js

+37-14
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,19 @@
1010
//------------------------------------------------------------------------------
1111

1212
/**
13-
* Checks whether a given code path segment is reachable or not.
14-
* @param {CodePathSegment} segment A code path segment to check.
15-
* @returns {boolean} `true` if the segment is reachable.
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.
1616
*/
17-
function isReachable(segment) {
18-
return segment.reachable;
17+
function isAnySegmentReachable(segments) {
18+
19+
for (const segment of segments) {
20+
if (segment.reachable) {
21+
return true;
22+
}
23+
}
24+
25+
return false;
1926
}
2027

2128
/**
@@ -210,15 +217,17 @@ module.exports = {
210217
isConstructor: true,
211218
hasExtends: Boolean(superClass),
212219
superIsConstructor: isPossibleConstructor(superClass),
213-
codePath
220+
codePath,
221+
currentSegments: new Set()
214222
};
215223
} else {
216224
funcInfo = {
217225
upper: funcInfo,
218226
isConstructor: false,
219227
hasExtends: false,
220228
superIsConstructor: false,
221-
codePath
229+
codePath,
230+
currentSegments: new Set()
222231
};
223232
}
224233
},
@@ -261,6 +270,9 @@ module.exports = {
261270
* @returns {void}
262271
*/
263272
onCodePathSegmentStart(segment) {
273+
274+
funcInfo.currentSegments.add(segment);
275+
264276
if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) {
265277
return;
266278
}
@@ -281,6 +293,19 @@ module.exports = {
281293
}
282294
},
283295

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+
284309
/**
285310
* Update information of the code path segment when a code path was
286311
* looped.
@@ -344,12 +369,11 @@ module.exports = {
344369

345370
// Reports if needed.
346371
if (funcInfo.hasExtends) {
347-
const segments = funcInfo.codePath.currentSegments;
372+
const segments = funcInfo.currentSegments;
348373
let duplicate = false;
349374
let info = null;
350375

351-
for (let i = 0; i < segments.length; ++i) {
352-
const segment = segments[i];
376+
for (const segment of segments) {
353377

354378
if (segment.reachable) {
355379
info = segInfoMap[segment.id];
@@ -374,7 +398,7 @@ module.exports = {
374398
info.validNodes.push(node);
375399
}
376400
}
377-
} else if (funcInfo.codePath.currentSegments.some(isReachable)) {
401+
} else if (isAnySegmentReachable(funcInfo.currentSegments)) {
378402
context.report({
379403
messageId: "unexpected",
380404
node
@@ -398,10 +422,9 @@ module.exports = {
398422
}
399423

400424
// Returning argument is a substitute of 'super()'.
401-
const segments = funcInfo.codePath.currentSegments;
425+
const segments = funcInfo.currentSegments;
402426

403-
for (let i = 0; i < segments.length; ++i) {
404-
const segment = segments[i];
427+
for (const segment of segments) {
405428

406429
if (segment.reachable) {
407430
const info = segInfoMap[segment.id];

‎lib/rules/getter-return.js

+33-8
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,23 @@ const astUtils = require("./utils/ast-utils");
1414
//------------------------------------------------------------------------------
1515
// Helpers
1616
//------------------------------------------------------------------------------
17+
1718
const TARGET_NODE_TYPE = /^(?:Arrow)?FunctionExpression$/u;
1819

1920
/**
20-
* Checks a given code path segment is reachable.
21-
* @param {CodePathSegment} segment A segment to check.
22-
* @returns {boolean} `true` if the segment is reachable.
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.
2324
*/
24-
function isReachable(segment) {
25-
return segment.reachable;
25+
function isAnySegmentReachable(segments) {
26+
27+
for (const segment of segments) {
28+
if (segment.reachable) {
29+
return true;
30+
}
31+
}
32+
33+
return false;
2634
}
2735

2836
//------------------------------------------------------------------------------
@@ -71,7 +79,8 @@ module.exports = {
7179
codePath: null,
7280
hasReturn: false,
7381
shouldCheck: false,
74-
node: null
82+
node: null,
83+
currentSegments: []
7584
};
7685

7786
/**
@@ -85,7 +94,7 @@ module.exports = {
8594
*/
8695
function checkLastSegment(node) {
8796
if (funcInfo.shouldCheck &&
88-
funcInfo.codePath.currentSegments.some(isReachable)
97+
isAnySegmentReachable(funcInfo.currentSegments)
8998
) {
9099
context.report({
91100
node,
@@ -144,14 +153,30 @@ module.exports = {
144153
codePath,
145154
hasReturn: false,
146155
shouldCheck: isGetter(node),
147-
node
156+
node,
157+
currentSegments: new Set()
148158
};
149159
},
150160

151161
// Pops this function's information.
152162
onCodePathEnd() {
153163
funcInfo = funcInfo.upper;
154164
},
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+
},
155180

156181
// Checks the return statement is valid.
157182
ReturnStatement(node) {

‎lib/rules/no-fallthrough.js

+42-14
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,22 @@ const { directivesPattern } = require("../shared/directives");
1616

1717
const DEFAULT_FALLTHROUGH_COMMENT = /falls?\s?through/iu;
1818

19+
/**
20+
* Checks all segments in a set and returns true if any are reachable.
21+
* @param {Set<CodePathSegment>} segments The segments to check.
22+
* @returns {boolean} True if any segment is reachable; false otherwise.
23+
*/
24+
function isAnySegmentReachable(segments) {
25+
26+
for (const segment of segments) {
27+
if (segment.reachable) {
28+
return true;
29+
}
30+
}
31+
32+
return false;
33+
}
34+
1935
/**
2036
* Checks whether or not a given comment string is really a fallthrough comment and not an ESLint directive.
2137
* @param {string} comment The comment string to check.
@@ -51,15 +67,6 @@ function hasFallthroughComment(caseWhichFallsThrough, subsequentCase, context, f
5167
return Boolean(comment && isFallThroughComment(comment.value, fallthroughCommentPattern));
5268
}
5369

54-
/**
55-
* Checks whether or not a given code path segment is reachable.
56-
* @param {CodePathSegment} segment A CodePathSegment to check.
57-
* @returns {boolean} `true` if the segment is reachable.
58-
*/
59-
function isReachable(segment) {
60-
return segment.reachable;
61-
}
62-
6370
/**
6471
* Checks whether a node and a token are separated by blank lines
6572
* @param {ASTNode} node The node to check
@@ -109,7 +116,8 @@ module.exports = {
109116

110117
create(context) {
111118
const options = context.options[0] || {};
112-
let currentCodePath = null;
119+
const codePathSegments = [];
120+
let currentCodePathSegments = new Set();
113121
const sourceCode = context.sourceCode;
114122
const allowEmptyCase = options.allowEmptyCase || false;
115123

@@ -126,13 +134,33 @@ module.exports = {
126134
fallthroughCommentPattern = DEFAULT_FALLTHROUGH_COMMENT;
127135
}
128136
return {
129-
onCodePathStart(codePath) {
130-
currentCodePath = codePath;
137+
138+
onCodePathStart() {
139+
codePathSegments.push(currentCodePathSegments);
140+
currentCodePathSegments = new Set();
131141
},
142+
132143
onCodePathEnd() {
133-
currentCodePath = currentCodePath.upper;
144+
currentCodePathSegments = codePathSegments.pop();
145+
},
146+
147+
onUnreachableCodePathSegmentStart(segment) {
148+
currentCodePathSegments.add(segment);
149+
},
150+
151+
onUnreachableCodePathSegmentEnd(segment) {
152+
currentCodePathSegments.delete(segment);
153+
},
154+
155+
onCodePathSegmentStart(segment) {
156+
currentCodePathSegments.add(segment);
134157
},
135158

159+
onCodePathSegmentEnd(segment) {
160+
currentCodePathSegments.delete(segment);
161+
},
162+
163+
136164
SwitchCase(node) {
137165

138166
/*
@@ -157,7 +185,7 @@ module.exports = {
157185
* `break`, `return`, or `throw` are unreachable.
158186
* And allows empty cases and the last case.
159187
*/
160-
if (currentCodePath.currentSegments.some(isReachable) &&
188+
if (isAnySegmentReachable(currentCodePathSegments) &&
161189
(node.consequent.length > 0 || (!allowEmptyCase && hasBlankLinesBetween(node, nextToken))) &&
162190
node.parent.cases[node.parent.cases.length - 1] !== node) {
163191
fallthroughCase = node;

‎lib/rules/no-this-before-super.js

+38-11
Original file line numberDiff line numberDiff line change
@@ -90,14 +90,29 @@ module.exports = {
9090
return Boolean(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends);
9191
}
9292

93+
/**
94+
* Determines if every segment in a set has been called.
95+
* @param {Set<CodePathSegment>} segments The segments to search.
96+
* @returns {boolean} True if every segment has been called; false otherwise.
97+
*/
98+
function isEverySegmentCalled(segments) {
99+
for (const segment of segments) {
100+
if (!isCalled(segment)) {
101+
return false;
102+
}
103+
}
104+
105+
return true;
106+
}
107+
93108
/**
94109
* Checks whether or not this is before `super()` is called.
95110
* @returns {boolean} `true` if this is before `super()` is called.
96111
*/
97112
function isBeforeCallOfSuper() {
98113
return (
99114
isInConstructorOfDerivedClass() &&
100-
!funcInfo.codePath.currentSegments.every(isCalled)
115+
!isEverySegmentCalled(funcInfo.currentSegments)
101116
);
102117
}
103118

@@ -108,11 +123,9 @@ module.exports = {
108123
* @returns {void}
109124
*/
110125
function setInvalid(node) {
111-
const segments = funcInfo.codePath.currentSegments;
112-
113-
for (let i = 0; i < segments.length; ++i) {
114-
const segment = segments[i];
126+
const segments = funcInfo.currentSegments;
115127

128+
for (const segment of segments) {
116129
if (segment.reachable) {
117130
segInfoMap[segment.id].invalidNodes.push(node);
118131
}
@@ -124,11 +137,9 @@ module.exports = {
124137
* @returns {void}
125138
*/
126139
function setSuperCalled() {
127-
const segments = funcInfo.codePath.currentSegments;
128-
129-
for (let i = 0; i < segments.length; ++i) {
130-
const segment = segments[i];
140+
const segments = funcInfo.currentSegments;
131141

142+
for (const segment of segments) {
132143
if (segment.reachable) {
133144
segInfoMap[segment.id].superCalled = true;
134145
}
@@ -156,14 +167,16 @@ module.exports = {
156167
classNode.superClass &&
157168
!astUtils.isNullOrUndefined(classNode.superClass)
158169
),
159-
codePath
170+
codePath,
171+
currentSegments: new Set()
160172
};
161173
} else {
162174
funcInfo = {
163175
upper: funcInfo,
164176
isConstructor: false,
165177
hasExtends: false,
166-
codePath
178+
codePath,
179+
currentSegments: new Set()
167180
};
168181
}
169182
},
@@ -211,6 +224,8 @@ module.exports = {
211224
* @returns {void}
212225
*/
213226
onCodePathSegmentStart(segment) {
227+
funcInfo.currentSegments.add(segment);
228+
214229
if (!isInConstructorOfDerivedClass()) {
215230
return;
216231
}
@@ -225,6 +240,18 @@ module.exports = {
225240
};
226241
},
227242

243+
onUnreachableCodePathSegmentStart(segment) {
244+
funcInfo.currentSegments.add(segment);
245+
},
246+
247+
onUnreachableCodePathSegmentEnd(segment) {
248+
funcInfo.currentSegments.delete(segment);
249+
},
250+
251+
onCodePathSegmentEnd(segment) {
252+
funcInfo.currentSegments.delete(segment);
253+
},
254+
228255
/**
229256
* Update information of the code path segment when a code path was
230257
* looped.

‎lib/rules/no-unreachable-loop.js

+47-12
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,22 @@
1111

1212
const allLoopTypes = ["WhileStatement", "DoWhileStatement", "ForStatement", "ForInStatement", "ForOfStatement"];
1313

14+
/**
15+
* Checks all segments in a set and returns true if any are reachable.
16+
* @param {Set<CodePathSegment>} segments The segments to check.
17+
* @returns {boolean} True if any segment is reachable; false otherwise.
18+
*/
19+
function isAnySegmentReachable(segments) {
20+
21+
for (const segment of segments) {
22+
if (segment.reachable) {
23+
return true;
24+
}
25+
}
26+
27+
return false;
28+
}
29+
1430
/**
1531
* Determines whether the given node is the first node in the code path to which a loop statement
1632
* 'loops' for the next iteration.
@@ -90,29 +106,36 @@ module.exports = {
90106
loopsByTargetSegments = new Map(),
91107
loopsToReport = new Set();
92108

93-
let currentCodePath = null;
109+
const codePathSegments = [];
110+
let currentCodePathSegments = new Set();
94111

95112
return {
96-
onCodePathStart(codePath) {
97-
currentCodePath = codePath;
113+
114+
onCodePathStart() {
115+
codePathSegments.push(currentCodePathSegments);
116+
currentCodePathSegments = new Set();
98117
},
99118

100119
onCodePathEnd() {
101-
currentCodePath = currentCodePath.upper;
120+
currentCodePathSegments = codePathSegments.pop();
102121
},
103122

104-
[loopSelector](node) {
123+
onUnreachableCodePathSegmentStart(segment) {
124+
currentCodePathSegments.add(segment);
125+
},
105126

106-
/**
107-
* Ignore unreachable loop statements to avoid unnecessary complexity in the implementation, or false positives otherwise.
108-
* For unreachable segments, the code path analysis does not raise events required for this implementation.
109-
*/
110-
if (currentCodePath.currentSegments.some(segment => segment.reachable)) {
111-
loopsToReport.add(node);
112-
}
127+
onUnreachableCodePathSegmentEnd(segment) {
128+
currentCodePathSegments.delete(segment);
129+
},
130+
131+
onCodePathSegmentEnd(segment) {
132+
currentCodePathSegments.delete(segment);
113133
},
114134

115135
onCodePathSegmentStart(segment, node) {
136+
137+
currentCodePathSegments.add(segment);
138+
116139
if (isLoopingTarget(node)) {
117140
const loop = node.parent;
118141

@@ -140,6 +163,18 @@ module.exports = {
140163
}
141164
},
142165

166+
[loopSelector](node) {
167+
168+
/**
169+
* Ignore unreachable loop statements to avoid unnecessary complexity in the implementation, or false positives otherwise.
170+
* For unreachable segments, the code path analysis does not raise events required for this implementation.
171+
*/
172+
if (isAnySegmentReachable(currentCodePathSegments)) {
173+
loopsToReport.add(node);
174+
}
175+
},
176+
177+
143178
"Program:exit"() {
144179
loopsToReport.forEach(
145180
node => context.report({ node, messageId: "invalid" })

‎lib/rules/no-unreachable.js

+39-10
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,19 @@ function isInitialized(node) {
2424
}
2525

2626
/**
27-
* Checks whether or not a given code path segment is unreachable.
28-
* @param {CodePathSegment} segment A CodePathSegment to check.
29-
* @returns {boolean} `true` if the segment is unreachable.
27+
* Checks all segments in a set and returns true if all are unreachable.
28+
* @param {Set<CodePathSegment>} segments The segments to check.
29+
* @returns {boolean} True if all segments are unreachable; false otherwise.
3030
*/
31-
function isUnreachable(segment) {
32-
return !segment.reachable;
31+
function areAllSegmentsUnreachable(segments) {
32+
33+
for (const segment of segments) {
34+
if (segment.reachable) {
35+
return false;
36+
}
37+
}
38+
39+
return true;
3340
}
3441

3542
/**
@@ -124,14 +131,19 @@ module.exports = {
124131
},
125132

126133
create(context) {
127-
let currentCodePath = null;
128134

129135
/** @type {ConstructorInfo | null} */
130136
let constructorInfo = null;
131137

132138
/** @type {ConsecutiveRange} */
133139
const range = new ConsecutiveRange(context.sourceCode);
134140

141+
/** @type {Array<Set<CodePathSegment>>} */
142+
const codePathSegments = [];
143+
144+
/** @type {Set<CodePathSegment>} */
145+
let currentCodePathSegments = new Set();
146+
135147
/**
136148
* Reports a given node if it's unreachable.
137149
* @param {ASTNode} node A statement node to report.
@@ -140,7 +152,7 @@ module.exports = {
140152
function reportIfUnreachable(node) {
141153
let nextNode = null;
142154

143-
if (node && (node.type === "PropertyDefinition" || currentCodePath.currentSegments.every(isUnreachable))) {
155+
if (node && (node.type === "PropertyDefinition" || areAllSegmentsUnreachable(currentCodePathSegments))) {
144156

145157
// Store this statement to distinguish consecutive statements.
146158
if (range.isEmpty) {
@@ -181,12 +193,29 @@ module.exports = {
181193
return {
182194

183195
// Manages the current code path.
184-
onCodePathStart(codePath) {
185-
currentCodePath = codePath;
196+
onCodePathStart() {
197+
codePathSegments.push(currentCodePathSegments);
198+
currentCodePathSegments = new Set();
186199
},
187200

188201
onCodePathEnd() {
189-
currentCodePath = currentCodePath.upper;
202+
currentCodePathSegments = codePathSegments.pop();
203+
},
204+
205+
onUnreachableCodePathSegmentStart(segment) {
206+
currentCodePathSegments.add(segment);
207+
},
208+
209+
onUnreachableCodePathSegmentEnd(segment) {
210+
currentCodePathSegments.delete(segment);
211+
},
212+
213+
onCodePathSegmentEnd(segment) {
214+
currentCodePathSegments.delete(segment);
215+
},
216+
217+
onCodePathSegmentStart(segment) {
218+
currentCodePathSegments.add(segment);
190219
},
191220

192221
// Registers for all statement nodes (excludes FunctionDeclaration).

‎lib/rules/no-useless-return.js

+35-4
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,22 @@ function isInFinally(node) {
5757
return false;
5858
}
5959

60+
/**
61+
* Checks all segments in a set and returns true if any are reachable.
62+
* @param {Set<CodePathSegment>} segments The segments to check.
63+
* @returns {boolean} True if any segment is reachable; false otherwise.
64+
*/
65+
function isAnySegmentReachable(segments) {
66+
67+
for (const segment of segments) {
68+
if (segment.reachable) {
69+
return true;
70+
}
71+
}
72+
73+
return false;
74+
}
75+
6076
//------------------------------------------------------------------------------
6177
// Rule Definition
6278
//------------------------------------------------------------------------------
@@ -205,7 +221,6 @@ module.exports = {
205221
*/
206222
function markReturnStatementsOnCurrentSegmentsAsUsed() {
207223
scopeInfo
208-
.codePath
209224
.currentSegments
210225
.forEach(segment => markReturnStatementsOnSegmentAsUsed(segment, new Set()));
211226
}
@@ -222,7 +237,8 @@ module.exports = {
222237
upper: scopeInfo,
223238
uselessReturns: [],
224239
traversedTryBlockStatements: [],
225-
codePath
240+
codePath,
241+
currentSegments: new Set()
226242
};
227243
},
228244

@@ -259,6 +275,9 @@ module.exports = {
259275
* NOTE: This event is notified for only reachable segments.
260276
*/
261277
onCodePathSegmentStart(segment) {
278+
279+
scopeInfo.currentSegments.add(segment);
280+
262281
const info = {
263282
uselessReturns: getUselessReturns([], segment.allPrevSegments),
264283
returned: false
@@ -268,6 +287,18 @@ module.exports = {
268287
segmentInfoMap.set(segment, info);
269288
},
270289

290+
onUnreachableCodePathSegmentStart(segment) {
291+
scopeInfo.currentSegments.add(segment);
292+
},
293+
294+
onUnreachableCodePathSegmentEnd(segment) {
295+
scopeInfo.currentSegments.delete(segment);
296+
},
297+
298+
onCodePathSegmentEnd(segment) {
299+
scopeInfo.currentSegments.delete(segment);
300+
},
301+
271302
// Adds ReturnStatement node to check whether it's useless or not.
272303
ReturnStatement(node) {
273304
if (node.argument) {
@@ -279,12 +310,12 @@ module.exports = {
279310
isInFinally(node) ||
280311

281312
// Ignore `return` statements in unreachable places (https://github.com/eslint/eslint/issues/11647).
282-
!scopeInfo.codePath.currentSegments.some(s => s.reachable)
313+
!isAnySegmentReachable(scopeInfo.currentSegments)
283314
) {
284315
return;
285316
}
286317

287-
for (const segment of scopeInfo.codePath.currentSegments) {
318+
for (const segment of scopeInfo.currentSegments) {
288319
const info = segmentInfoMap.get(segment);
289320

290321
if (info) {

‎lib/rules/require-atomic-updates.js

+21-7
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,8 @@ module.exports = {
213213
stack = {
214214
upper: stack,
215215
codePath,
216-
referenceMap: shouldVerify ? createReferenceMap(scope) : null
216+
referenceMap: shouldVerify ? createReferenceMap(scope) : null,
217+
currentSegments: new Set()
217218
};
218219
},
219220
onCodePathEnd() {
@@ -223,11 +224,25 @@ module.exports = {
223224
// Initialize the segment information.
224225
onCodePathSegmentStart(segment) {
225226
segmentInfo.initialize(segment);
227+
stack.currentSegments.add(segment);
226228
},
227229

230+
onUnreachableCodePathSegmentStart(segment) {
231+
stack.currentSegments.add(segment);
232+
},
233+
234+
onUnreachableCodePathSegmentEnd(segment) {
235+
stack.currentSegments.delete(segment);
236+
},
237+
238+
onCodePathSegmentEnd(segment) {
239+
stack.currentSegments.delete(segment);
240+
},
241+
242+
228243
// Handle references to prepare verification.
229244
Identifier(node) {
230-
const { codePath, referenceMap } = stack;
245+
const { referenceMap } = stack;
231246
const reference = referenceMap && referenceMap.get(node);
232247

233248
// Ignore if this is not a valid variable reference.
@@ -240,7 +255,7 @@ module.exports = {
240255

241256
// Add a fresh read variable.
242257
if (reference.isRead() && !(writeExpr && writeExpr.parent.operator === "=")) {
243-
segmentInfo.markAsRead(codePath.currentSegments, variable);
258+
segmentInfo.markAsRead(stack.currentSegments, variable);
244259
}
245260

246261
/*
@@ -267,16 +282,15 @@ module.exports = {
267282
* If the reference exists in `outdatedReadVariables` list, report it.
268283
*/
269284
":expression:exit"(node) {
270-
const { codePath, referenceMap } = stack;
271285

272286
// referenceMap exists if this is in a resumable function scope.
273-
if (!referenceMap) {
287+
if (!stack.referenceMap) {
274288
return;
275289
}
276290

277291
// Mark the read variables on this code path as outdated.
278292
if (node.type === "AwaitExpression" || node.type === "YieldExpression") {
279-
segmentInfo.makeOutdated(codePath.currentSegments);
293+
segmentInfo.makeOutdated(stack.currentSegments);
280294
}
281295

282296
// Verify.
@@ -288,7 +302,7 @@ module.exports = {
288302
for (const reference of references) {
289303
const variable = reference.resolved;
290304

291-
if (segmentInfo.isOutdated(codePath.currentSegments, variable)) {
305+
if (segmentInfo.isOutdated(stack.currentSegments, variable)) {
292306
if (node.parent.left === reference.identifier) {
293307
context.report({
294308
node: node.parent,

‎tests/lib/linter/code-path-analysis/code-path-analyzer.js

+158
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,164 @@ describe("CodePathAnalyzer", () => {
439439
});
440440
});
441441

442+
describe("onUnreachableCodePathSegmentStart", () => {
443+
it("should be fired after a throw", () => {
444+
let lastCodePathNodeType = null;
445+
446+
linter.defineRule("test", {
447+
create: () => ({
448+
onUnreachableCodePathSegmentStart(segment, node) {
449+
lastCodePathNodeType = node.type;
450+
451+
assert(segment instanceof CodePathSegment);
452+
assert.strictEqual(node.type, "ExpressionStatement");
453+
},
454+
ExpressionStatement() {
455+
assert.strictEqual(lastCodePathNodeType, "ExpressionStatement");
456+
}
457+
})
458+
});
459+
linter.verify(
460+
"throw 'boom'; foo();",
461+
{ rules: { test: 2 } }
462+
);
463+
464+
});
465+
466+
it("should be fired after a return", () => {
467+
let lastCodePathNodeType = null;
468+
469+
linter.defineRule("test", {
470+
create: () => ({
471+
onUnreachableCodePathSegmentStart(segment, node) {
472+
lastCodePathNodeType = node.type;
473+
474+
assert(segment instanceof CodePathSegment);
475+
assert.strictEqual(node.type, "ExpressionStatement");
476+
},
477+
ExpressionStatement() {
478+
assert.strictEqual(lastCodePathNodeType, "ExpressionStatement");
479+
}
480+
})
481+
});
482+
linter.verify(
483+
"function foo() { return; foo(); }",
484+
{ rules: { test: 2 } }
485+
);
486+
487+
});
488+
});
489+
490+
describe("onUnreachableCodePathSegmentEnd", () => {
491+
it("should be fired after a throw", () => {
492+
let lastCodePathNodeType = null;
493+
494+
linter.defineRule("test", {
495+
create: () => ({
496+
onUnreachableCodePathSegmentEnd(segment, node) {
497+
lastCodePathNodeType = node.type;
498+
499+
assert(segment instanceof CodePathSegment);
500+
assert.strictEqual(node.type, "Program");
501+
}
502+
})
503+
});
504+
linter.verify(
505+
"throw 'boom'; foo();",
506+
{ rules: { test: 2 } }
507+
);
508+
509+
assert.strictEqual(lastCodePathNodeType, "Program");
510+
});
511+
512+
it("should be fired after a return", () => {
513+
let lastCodePathNodeType = null;
514+
515+
linter.defineRule("test", {
516+
create: () => ({
517+
onUnreachableCodePathSegmentEnd(segment, node) {
518+
lastCodePathNodeType = node.type;
519+
assert(segment instanceof CodePathSegment);
520+
assert.strictEqual(node.type, "FunctionDeclaration");
521+
},
522+
"Program:exit"() {
523+
assert.strictEqual(lastCodePathNodeType, "FunctionDeclaration");
524+
}
525+
})
526+
});
527+
linter.verify(
528+
"function foo() { return; foo(); }",
529+
{ rules: { test: 2 } }
530+
);
531+
532+
});
533+
534+
it("should be fired after a return inside of function and if statement", () => {
535+
let lastCodePathNodeType = null;
536+
537+
linter.defineRule("test", {
538+
create: () => ({
539+
onUnreachableCodePathSegmentEnd(segment, node) {
540+
lastCodePathNodeType = node.type;
541+
assert(segment instanceof CodePathSegment);
542+
assert.strictEqual(node.type, "BlockStatement");
543+
},
544+
"Program:exit"() {
545+
assert.strictEqual(lastCodePathNodeType, "BlockStatement");
546+
}
547+
})
548+
});
549+
linter.verify(
550+
"function foo() { if (bar) { return; foo(); } else {} }",
551+
{ rules: { test: 2 } }
552+
);
553+
554+
});
555+
556+
it("should be fired at the end of programs/functions for the final segment", () => {
557+
let count = 0;
558+
let lastNodeType = null;
559+
560+
linter.defineRule("test", {
561+
create: () => ({
562+
onUnreachableCodePathSegmentEnd(cp, node) {
563+
count += 1;
564+
565+
assert(cp instanceof CodePathSegment);
566+
if (count === 4) {
567+
assert(node.type === "Program");
568+
} else if (count === 1) {
569+
assert(node.type === "FunctionDeclaration");
570+
} else if (count === 2) {
571+
assert(node.type === "FunctionExpression");
572+
} else if (count === 3) {
573+
assert(node.type === "ArrowFunctionExpression");
574+
}
575+
assert(node.type === lastNodeType);
576+
},
577+
"Program:exit"() {
578+
lastNodeType = "Program";
579+
},
580+
"FunctionDeclaration:exit"() {
581+
lastNodeType = "FunctionDeclaration";
582+
},
583+
"FunctionExpression:exit"() {
584+
lastNodeType = "FunctionExpression";
585+
},
586+
"ArrowFunctionExpression:exit"() {
587+
lastNodeType = "ArrowFunctionExpression";
588+
}
589+
})
590+
});
591+
linter.verify(
592+
"foo(); function foo() { return; } var foo = function() { return; }; var foo = () => { return; }; throw 'boom';",
593+
{ rules: { test: 2 }, env: { es6: true } }
594+
);
595+
596+
assert(count === 4);
597+
});
598+
});
599+
442600
describe("onCodePathSegmentLoop", () => {
443601
it("should be fired in `while` loops", () => {
444602
let count = 0;

‎tests/lib/rule-tester/flat-rule-tester.js

+39
Original file line numberDiff line numberDiff line change
@@ -2248,6 +2248,45 @@ describe("FlatRuleTester", () => {
22482248
});
22492249
});
22502250

2251+
describe("deprecations", () => {
2252+
let processStub;
2253+
2254+
beforeEach(() => {
2255+
processStub = sinon.stub(process, "emitWarning");
2256+
});
2257+
2258+
afterEach(() => {
2259+
processStub.restore();
2260+
});
2261+
2262+
it("should emit a deprecation warning when CodePath#currentSegments is accessed", () => {
2263+
2264+
const useCurrentSegmentsRule = {
2265+
create: () => ({
2266+
onCodePathStart(codePath) {
2267+
codePath.currentSegments.forEach(() => { });
2268+
}
2269+
})
2270+
};
2271+
2272+
ruleTester.run("use-current-segments", useCurrentSegmentsRule, {
2273+
valid: ["foo"],
2274+
invalid: []
2275+
});
2276+
2277+
assert.strictEqual(processStub.callCount, 1, "calls `process.emitWarning()` once");
2278+
assert.deepStrictEqual(
2279+
processStub.getCall(0).args,
2280+
[
2281+
"\"use-current-segments\" 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",
2282+
"DeprecationWarning"
2283+
]
2284+
);
2285+
2286+
});
2287+
2288+
});
2289+
22512290
/**
22522291
* Asserts that a particular value will be emitted from an EventEmitter.
22532292
* @param {EventEmitter} emitter The emitter that should emit a value

‎tests/lib/rule-tester/rule-tester.js

+25
Original file line numberDiff line numberDiff line change
@@ -2490,6 +2490,31 @@ describe("RuleTester", () => {
24902490
assert.strictEqual(processStub.callCount, 0, "never calls `process.emitWarning()`");
24912491
});
24922492

2493+
it("should emit a deprecation warning when CodePath#currentSegments is accessed", () => {
2494+
2495+
const useCurrentSegmentsRule = {
2496+
create: () => ({
2497+
onCodePathStart(codePath) {
2498+
codePath.currentSegments.forEach(() => {});
2499+
}
2500+
})
2501+
};
2502+
2503+
ruleTester.run("use-current-segments", useCurrentSegmentsRule, {
2504+
valid: ["foo"],
2505+
invalid: []
2506+
});
2507+
2508+
assert.strictEqual(processStub.callCount, 1, "calls `process.emitWarning()` once");
2509+
assert.deepStrictEqual(
2510+
processStub.getCall(0).args,
2511+
[
2512+
"\"use-current-segments\" 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",
2513+
"DeprecationWarning"
2514+
]
2515+
);
2516+
});
2517+
24932518
Object.entries({
24942519
getSource: "getText",
24952520
getSourceLines: "getLines",

0 commit comments

Comments
 (0)
Please sign in to comment.