eslint 8.47.0 → 8.57.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.
Files changed (118) hide show
  1. package/README.md +18 -13
  2. package/bin/eslint.js +38 -5
  3. package/conf/rule-type-list.json +25 -33
  4. package/lib/api.js +29 -1
  5. package/lib/cli-engine/cli-engine.js +2 -2
  6. package/lib/cli-engine/lint-result-cache.js +18 -6
  7. package/lib/cli.js +36 -6
  8. package/lib/config/flat-config-schema.js +124 -61
  9. package/lib/config/rule-validator.js +2 -1
  10. package/lib/eslint/eslint-helpers.js +9 -11
  11. package/lib/eslint/eslint.js +7 -0
  12. package/lib/eslint/flat-eslint.js +33 -18
  13. package/lib/linter/apply-disable-directives.js +127 -13
  14. package/lib/linter/code-path-analysis/code-path-analyzer.js +32 -24
  15. package/lib/linter/code-path-analysis/code-path-segment.js +52 -24
  16. package/lib/linter/code-path-analysis/code-path-state.js +1108 -243
  17. package/lib/linter/code-path-analysis/code-path.js +128 -33
  18. package/lib/linter/code-path-analysis/fork-context.js +173 -72
  19. package/lib/linter/config-comment-parser.js +36 -2
  20. package/lib/linter/linter.js +183 -82
  21. package/lib/options.js +24 -3
  22. package/lib/rule-tester/flat-rule-tester.js +113 -25
  23. package/lib/rule-tester/rule-tester.js +176 -23
  24. package/lib/rules/array-bracket-newline.js +3 -0
  25. package/lib/rules/array-bracket-spacing.js +3 -0
  26. package/lib/rules/array-callback-return.js +175 -25
  27. package/lib/rules/array-element-newline.js +3 -0
  28. package/lib/rules/arrow-parens.js +3 -0
  29. package/lib/rules/arrow-spacing.js +3 -0
  30. package/lib/rules/block-spacing.js +3 -0
  31. package/lib/rules/brace-style.js +3 -0
  32. package/lib/rules/comma-dangle.js +3 -0
  33. package/lib/rules/comma-spacing.js +3 -0
  34. package/lib/rules/comma-style.js +3 -0
  35. package/lib/rules/computed-property-spacing.js +3 -0
  36. package/lib/rules/consistent-return.js +32 -7
  37. package/lib/rules/constructor-super.js +37 -14
  38. package/lib/rules/dot-location.js +3 -0
  39. package/lib/rules/eol-last.js +3 -0
  40. package/lib/rules/for-direction.js +38 -24
  41. package/lib/rules/func-call-spacing.js +3 -0
  42. package/lib/rules/function-call-argument-newline.js +3 -0
  43. package/lib/rules/function-paren-newline.js +3 -0
  44. package/lib/rules/generator-star-spacing.js +3 -0
  45. package/lib/rules/getter-return.js +33 -8
  46. package/lib/rules/implicit-arrow-linebreak.js +3 -0
  47. package/lib/rules/indent.js +3 -0
  48. package/lib/rules/index.js +1 -0
  49. package/lib/rules/jsx-quotes.js +3 -0
  50. package/lib/rules/key-spacing.js +3 -0
  51. package/lib/rules/keyword-spacing.js +3 -0
  52. package/lib/rules/linebreak-style.js +3 -0
  53. package/lib/rules/lines-around-comment.js +3 -0
  54. package/lib/rules/lines-between-class-members.js +95 -7
  55. package/lib/rules/logical-assignment-operators.js +31 -3
  56. package/lib/rules/max-len.js +3 -0
  57. package/lib/rules/max-statements-per-line.js +3 -0
  58. package/lib/rules/multiline-ternary.js +3 -0
  59. package/lib/rules/new-parens.js +3 -0
  60. package/lib/rules/newline-per-chained-call.js +3 -0
  61. package/lib/rules/no-array-constructor.js +85 -6
  62. package/lib/rules/no-confusing-arrow.js +3 -0
  63. package/lib/rules/no-console.js +74 -2
  64. package/lib/rules/no-extra-parens.js +3 -0
  65. package/lib/rules/no-extra-semi.js +3 -0
  66. package/lib/rules/no-fallthrough.js +42 -14
  67. package/lib/rules/no-floating-decimal.js +3 -0
  68. package/lib/rules/no-invalid-this.js +1 -1
  69. package/lib/rules/no-misleading-character-class.js +65 -15
  70. package/lib/rules/no-mixed-operators.js +3 -0
  71. package/lib/rules/no-mixed-spaces-and-tabs.js +3 -0
  72. package/lib/rules/no-multi-spaces.js +3 -0
  73. package/lib/rules/no-multiple-empty-lines.js +3 -0
  74. package/lib/rules/no-new-object.js +7 -0
  75. package/lib/rules/no-object-constructor.js +117 -0
  76. package/lib/rules/no-promise-executor-return.js +157 -16
  77. package/lib/rules/no-prototype-builtins.js +90 -2
  78. package/lib/rules/no-restricted-imports.js +54 -31
  79. package/lib/rules/no-restricted-properties.js +15 -28
  80. package/lib/rules/no-tabs.js +3 -0
  81. package/lib/rules/no-this-before-super.js +38 -11
  82. package/lib/rules/no-trailing-spaces.js +3 -0
  83. package/lib/rules/no-unreachable-loop.js +47 -12
  84. package/lib/rules/no-unreachable.js +39 -10
  85. package/lib/rules/no-useless-return.js +35 -4
  86. package/lib/rules/no-whitespace-before-property.js +3 -0
  87. package/lib/rules/nonblock-statement-body-position.js +3 -0
  88. package/lib/rules/object-curly-newline.js +3 -0
  89. package/lib/rules/object-curly-spacing.js +3 -0
  90. package/lib/rules/object-property-newline.js +3 -0
  91. package/lib/rules/one-var-declaration-per-line.js +3 -0
  92. package/lib/rules/operator-linebreak.js +3 -0
  93. package/lib/rules/padded-blocks.js +3 -0
  94. package/lib/rules/padding-line-between-statements.js +3 -0
  95. package/lib/rules/quote-props.js +3 -0
  96. package/lib/rules/quotes.js +3 -0
  97. package/lib/rules/require-atomic-updates.js +21 -7
  98. package/lib/rules/rest-spread-spacing.js +3 -0
  99. package/lib/rules/semi-spacing.js +3 -0
  100. package/lib/rules/semi-style.js +3 -0
  101. package/lib/rules/semi.js +3 -0
  102. package/lib/rules/space-before-blocks.js +3 -0
  103. package/lib/rules/space-before-function-paren.js +3 -0
  104. package/lib/rules/space-in-parens.js +3 -0
  105. package/lib/rules/space-infix-ops.js +3 -0
  106. package/lib/rules/space-unary-ops.js +3 -0
  107. package/lib/rules/spaced-comment.js +3 -0
  108. package/lib/rules/switch-colon-spacing.js +3 -0
  109. package/lib/rules/template-curly-spacing.js +3 -0
  110. package/lib/rules/template-tag-spacing.js +3 -0
  111. package/lib/rules/utils/ast-utils.js +111 -1
  112. package/lib/rules/wrap-iife.js +3 -0
  113. package/lib/rules/wrap-regex.js +3 -0
  114. package/lib/rules/yield-star-spacing.js +3 -0
  115. package/lib/shared/severity.js +49 -0
  116. package/lib/source-code/source-code.js +329 -3
  117. package/messages/eslintrc-incompat.js +1 -1
  118. package/package.json +24 -17
@@ -30,7 +30,7 @@ function compareLocations(itemA, itemB) {
30
30
 
31
31
  /**
32
32
  * Groups a set of directives into sub-arrays by their parent comment.
33
- * @param {Directive[]} directives Unused directives to be removed.
33
+ * @param {Iterable<Directive>} directives Unused directives to be removed.
34
34
  * @returns {Directive[][]} Directives grouped by their parent comment.
35
35
  */
36
36
  function groupByParentComment(directives) {
@@ -87,7 +87,7 @@ function createIndividualDirectivesRemoval(directives, commentToken) {
87
87
  return directives.map(directive => {
88
88
  const { ruleId } = directive;
89
89
 
90
- const regex = new RegExp(String.raw`(?:^|\s*,\s*)${escapeRegExp(ruleId)}(?:\s*,\s*|$)`, "u");
90
+ const regex = new RegExp(String.raw`(?:^|\s*,\s*)(?<quote>['"]?)${escapeRegExp(ruleId)}\k<quote>(?:\s*,\s*|$)`, "u");
91
91
  const match = regex.exec(listText);
92
92
  const matchedText = match[0];
93
93
  const matchStartOffset = listStartOffset + match.index;
@@ -177,10 +177,10 @@ function createCommentRemoval(directives, commentToken) {
177
177
 
178
178
  /**
179
179
  * Parses details from directives to create output Problems.
180
- * @param {Directive[]} allDirectives Unused directives to be removed.
180
+ * @param {Iterable<Directive>} allDirectives Unused directives to be removed.
181
181
  * @returns {{ description, fix, unprocessedDirective }[]} Details for later creation of output Problems.
182
182
  */
183
- function processUnusedDisableDirectives(allDirectives) {
183
+ function processUnusedDirectives(allDirectives) {
184
184
  const directiveGroups = groupByParentComment(allDirectives);
185
185
 
186
186
  return directiveGroups.flatMap(
@@ -199,6 +199,95 @@ function processUnusedDisableDirectives(allDirectives) {
199
199
  );
200
200
  }
201
201
 
202
+ /**
203
+ * Collect eslint-enable comments that are removing suppressions by eslint-disable comments.
204
+ * @param {Directive[]} directives The directives to check.
205
+ * @returns {Set<Directive>} The used eslint-enable comments
206
+ */
207
+ function collectUsedEnableDirectives(directives) {
208
+
209
+ /**
210
+ * A Map of `eslint-enable` keyed by ruleIds that may be marked as used.
211
+ * If `eslint-enable` does not have a ruleId, the key will be `null`.
212
+ * @type {Map<string|null, Directive>}
213
+ */
214
+ const enabledRules = new Map();
215
+
216
+ /**
217
+ * A Set of `eslint-enable` marked as used.
218
+ * It is also the return value of `collectUsedEnableDirectives` function.
219
+ * @type {Set<Directive>}
220
+ */
221
+ const usedEnableDirectives = new Set();
222
+
223
+ /*
224
+ * Checks the directives backwards to see if the encountered `eslint-enable` is used by the previous `eslint-disable`,
225
+ * and if so, stores the `eslint-enable` in `usedEnableDirectives`.
226
+ */
227
+ for (let index = directives.length - 1; index >= 0; index--) {
228
+ const directive = directives[index];
229
+
230
+ if (directive.type === "disable") {
231
+ if (enabledRules.size === 0) {
232
+ continue;
233
+ }
234
+ if (directive.ruleId === null) {
235
+
236
+ // If encounter `eslint-disable` without ruleId,
237
+ // mark all `eslint-enable` currently held in enabledRules as used.
238
+ // e.g.
239
+ // /* eslint-disable */ <- current directive
240
+ // /* eslint-enable rule-id1 */ <- used
241
+ // /* eslint-enable rule-id2 */ <- used
242
+ // /* eslint-enable */ <- used
243
+ for (const enableDirective of enabledRules.values()) {
244
+ usedEnableDirectives.add(enableDirective);
245
+ }
246
+ enabledRules.clear();
247
+ } else {
248
+ const enableDirective = enabledRules.get(directive.ruleId);
249
+
250
+ if (enableDirective) {
251
+
252
+ // If encounter `eslint-disable` with ruleId, and there is an `eslint-enable` with the same ruleId in enabledRules,
253
+ // mark `eslint-enable` with ruleId as used.
254
+ // e.g.
255
+ // /* eslint-disable rule-id */ <- current directive
256
+ // /* eslint-enable rule-id */ <- used
257
+ usedEnableDirectives.add(enableDirective);
258
+ } else {
259
+ const enabledDirectiveWithoutRuleId = enabledRules.get(null);
260
+
261
+ if (enabledDirectiveWithoutRuleId) {
262
+
263
+ // If encounter `eslint-disable` with ruleId, and there is no `eslint-enable` with the same ruleId in enabledRules,
264
+ // mark `eslint-enable` without ruleId as used.
265
+ // e.g.
266
+ // /* eslint-disable rule-id */ <- current directive
267
+ // /* eslint-enable */ <- used
268
+ usedEnableDirectives.add(enabledDirectiveWithoutRuleId);
269
+ }
270
+ }
271
+ }
272
+ } else if (directive.type === "enable") {
273
+ if (directive.ruleId === null) {
274
+
275
+ // If encounter `eslint-enable` without ruleId, the `eslint-enable` that follows it are unused.
276
+ // So clear enabledRules.
277
+ // e.g.
278
+ // /* eslint-enable */ <- current directive
279
+ // /* eslint-enable rule-id *// <- unused
280
+ // /* eslint-enable */ <- unused
281
+ enabledRules.clear();
282
+ enabledRules.set(null, directive);
283
+ } else {
284
+ enabledRules.set(directive.ruleId, directive);
285
+ }
286
+ }
287
+ }
288
+ return usedEnableDirectives;
289
+ }
290
+
202
291
  /**
203
292
  * This is the same as the exported function, except that it
204
293
  * doesn't handle disable-line and disable-next-line directives, and it always reports unused
@@ -206,7 +295,7 @@ function processUnusedDisableDirectives(allDirectives) {
206
295
  * @param {Object} options options for applying directives. This is the same as the options
207
296
  * for the exported function, except that `reportUnusedDisableDirectives` is not supported
208
297
  * (this function always reports unused disable directives).
209
- * @returns {{problems: LintMessage[], unusedDisableDirectives: LintMessage[]}} An object with a list
298
+ * @returns {{problems: LintMessage[], unusedDirectives: LintMessage[]}} An object with a list
210
299
  * of problems (including suppressed ones) and unused eslint-disable directives
211
300
  */
212
301
  function applyDirectives(options) {
@@ -258,17 +347,42 @@ function applyDirectives(options) {
258
347
  const unusedDisableDirectivesToReport = options.directives
259
348
  .filter(directive => directive.type === "disable" && !usedDisableDirectives.has(directive));
260
349
 
261
- const processed = processUnusedDisableDirectives(unusedDisableDirectivesToReport);
262
350
 
263
- const unusedDisableDirectives = processed
351
+ const unusedEnableDirectivesToReport = new Set(
352
+ options.directives.filter(directive => directive.unprocessedDirective.type === "enable")
353
+ );
354
+
355
+ /*
356
+ * If directives has the eslint-enable directive,
357
+ * check whether the eslint-enable comment is used.
358
+ */
359
+ if (unusedEnableDirectivesToReport.size > 0) {
360
+ for (const directive of collectUsedEnableDirectives(options.directives)) {
361
+ unusedEnableDirectivesToReport.delete(directive);
362
+ }
363
+ }
364
+
365
+ const processed = processUnusedDirectives(unusedDisableDirectivesToReport)
366
+ .concat(processUnusedDirectives(unusedEnableDirectivesToReport));
367
+
368
+ const unusedDirectives = processed
264
369
  .map(({ description, fix, unprocessedDirective }) => {
265
370
  const { parentComment, type, line, column } = unprocessedDirective;
266
371
 
372
+ let message;
373
+
374
+ if (type === "enable") {
375
+ message = description
376
+ ? `Unused eslint-enable directive (no matching eslint-disable directives were found for ${description}).`
377
+ : "Unused eslint-enable directive (no matching eslint-disable directives were found).";
378
+ } else {
379
+ message = description
380
+ ? `Unused eslint-disable directive (no problems were reported from ${description}).`
381
+ : "Unused eslint-disable directive (no problems were reported).";
382
+ }
267
383
  return {
268
384
  ruleId: null,
269
- message: description
270
- ? `Unused eslint-disable directive (no problems were reported from ${description}).`
271
- : "Unused eslint-disable directive (no problems were reported).",
385
+ message,
272
386
  line: type === "disable-next-line" ? parentComment.commentToken.loc.start.line : line,
273
387
  column: type === "disable-next-line" ? parentComment.commentToken.loc.start.column + 1 : column,
274
388
  severity: options.reportUnusedDisableDirectives === "warn" ? 1 : 2,
@@ -277,7 +391,7 @@ function applyDirectives(options) {
277
391
  };
278
392
  });
279
393
 
280
- return { problems, unusedDisableDirectives };
394
+ return { problems, unusedDirectives };
281
395
  }
282
396
 
283
397
  /**
@@ -344,8 +458,8 @@ module.exports = ({ directives, disableFixes, problems, reportUnusedDisableDirec
344
458
 
345
459
  return reportUnusedDisableDirectives !== "off"
346
460
  ? lineDirectivesResult.problems
347
- .concat(blockDirectivesResult.unusedDisableDirectives)
348
- .concat(lineDirectivesResult.unusedDisableDirectives)
461
+ .concat(blockDirectivesResult.unusedDirectives)
462
+ .concat(lineDirectivesResult.unusedDirectives)
349
463
  .sort(compareLocations)
350
464
  : lineDirectivesResult.problems;
351
465
  };
@@ -192,15 +192,18 @@ function forwardCurrentToHead(analyzer, node) {
192
192
  headSegment = headSegments[i];
193
193
 
194
194
  if (currentSegment !== headSegment && currentSegment) {
195
- debug.dump(`onCodePathSegmentEnd ${currentSegment.id}`);
196
195
 
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
+ );
204
207
  }
205
208
  }
206
209
 
@@ -213,16 +216,19 @@ function forwardCurrentToHead(analyzer, node) {
213
216
  headSegment = headSegments[i];
214
217
 
215
218
  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}`);
217
225
 
218
226
  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
+ );
226
232
  }
227
233
  }
228
234
 
@@ -241,15 +247,17 @@ function leaveFromCurrentSegment(analyzer, node) {
241
247
 
242
248
  for (let i = 0; i < currentSegments.length; ++i) {
243
249
  const currentSegment = currentSegments[i];
250
+ const eventName = currentSegment.reachable
251
+ ? "onCodePathSegmentEnd"
252
+ : "onUnreachableCodePathSegmentEnd";
244
253
 
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
+ );
253
261
  }
254
262
 
255
263
  state.currentSegments = [];
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @fileoverview A class of the code path segment.
2
+ * @fileoverview The CodePathSegment class.
3
3
  * @author Toru Nagashima
4
4
  */
5
5
 
@@ -30,10 +30,22 @@ function isReachable(segment) {
30
30
 
31
31
  /**
32
32
  * A code path segment.
33
+ *
34
+ * Each segment is arranged in a series of linked lists (implemented by arrays)
35
+ * that keep track of the previous and next segments in a code path. In this way,
36
+ * you can navigate between all segments in any code path so long as you have a
37
+ * reference to any segment in that code path.
38
+ *
39
+ * When first created, the segment is in a detached state, meaning that it knows the
40
+ * segments that came before it but those segments don't know that this new segment
41
+ * follows it. Only when `CodePathSegment#markUsed()` is called on a segment does it
42
+ * officially become part of the code path by updating the previous segments to know
43
+ * that this new segment follows.
33
44
  */
34
45
  class CodePathSegment {
35
46
 
36
47
  /**
48
+ * Creates a new instance.
37
49
  * @param {string} id An identifier.
38
50
  * @param {CodePathSegment[]} allPrevSegments An array of the previous segments.
39
51
  * This array includes unreachable segments.
@@ -49,27 +61,25 @@ class CodePathSegment {
49
61
  this.id = id;
50
62
 
51
63
  /**
52
- * An array of the next segments.
64
+ * An array of the next reachable segments.
53
65
  * @type {CodePathSegment[]}
54
66
  */
55
67
  this.nextSegments = [];
56
68
 
57
69
  /**
58
- * An array of the previous segments.
70
+ * An array of the previous reachable segments.
59
71
  * @type {CodePathSegment[]}
60
72
  */
61
73
  this.prevSegments = allPrevSegments.filter(isReachable);
62
74
 
63
75
  /**
64
- * An array of the next segments.
65
- * This array includes unreachable segments.
76
+ * An array of all next segments including reachable and unreachable.
66
77
  * @type {CodePathSegment[]}
67
78
  */
68
79
  this.allNextSegments = [];
69
80
 
70
81
  /**
71
- * An array of the previous segments.
72
- * This array includes unreachable segments.
82
+ * An array of all previous segments including reachable and unreachable.
73
83
  * @type {CodePathSegment[]}
74
84
  */
75
85
  this.allPrevSegments = allPrevSegments;
@@ -83,7 +93,11 @@ class CodePathSegment {
83
93
  // Internal data.
84
94
  Object.defineProperty(this, "internal", {
85
95
  value: {
96
+
97
+ // determines if the segment has been attached to the code path
86
98
  used: false,
99
+
100
+ // array of previous segments coming from the end of a loop
87
101
  loopedPrevSegments: []
88
102
  }
89
103
  });
@@ -113,9 +127,10 @@ class CodePathSegment {
113
127
  }
114
128
 
115
129
  /**
116
- * Creates a segment that follows given segments.
130
+ * Creates a new segment and appends it after the given segments.
117
131
  * @param {string} id An identifier.
118
- * @param {CodePathSegment[]} allPrevSegments An array of the previous segments.
132
+ * @param {CodePathSegment[]} allPrevSegments An array of the previous segments
133
+ * to append to.
119
134
  * @returns {CodePathSegment} The created segment.
120
135
  */
121
136
  static newNext(id, allPrevSegments) {
@@ -127,7 +142,7 @@ class CodePathSegment {
127
142
  }
128
143
 
129
144
  /**
130
- * Creates an unreachable segment that follows given segments.
145
+ * Creates an unreachable segment and appends it after the given segments.
131
146
  * @param {string} id An identifier.
132
147
  * @param {CodePathSegment[]} allPrevSegments An array of the previous segments.
133
148
  * @returns {CodePathSegment} The created segment.
@@ -137,7 +152,7 @@ class CodePathSegment {
137
152
 
138
153
  /*
139
154
  * In `if (a) return a; foo();` case, the unreachable segment preceded by
140
- * the return statement is not used but must not be remove.
155
+ * the return statement is not used but must not be removed.
141
156
  */
142
157
  CodePathSegment.markUsed(segment);
143
158
 
@@ -157,7 +172,7 @@ class CodePathSegment {
157
172
  }
158
173
 
159
174
  /**
160
- * Makes a given segment being used.
175
+ * Marks a given segment as used.
161
176
  *
162
177
  * And this function registers the segment into the previous segments as a next.
163
178
  * @param {CodePathSegment} segment A segment to mark.
@@ -172,6 +187,13 @@ class CodePathSegment {
172
187
  let i;
173
188
 
174
189
  if (segment.reachable) {
190
+
191
+ /*
192
+ * If the segment is reachable, then it's officially part of the
193
+ * code path. This loops through all previous segments to update
194
+ * their list of next segments. Because the segment is reachable,
195
+ * it's added to both `nextSegments` and `allNextSegments`.
196
+ */
175
197
  for (i = 0; i < segment.allPrevSegments.length; ++i) {
176
198
  const prevSegment = segment.allPrevSegments[i];
177
199
 
@@ -179,6 +201,13 @@ class CodePathSegment {
179
201
  prevSegment.nextSegments.push(segment);
180
202
  }
181
203
  } else {
204
+
205
+ /*
206
+ * If the segment is not reachable, then it's not officially part of the
207
+ * code path. This loops through all previous segments to update
208
+ * their list of next segments. Because the segment is not reachable,
209
+ * it's added only to `allNextSegments`.
210
+ */
182
211
  for (i = 0; i < segment.allPrevSegments.length; ++i) {
183
212
  segment.allPrevSegments[i].allNextSegments.push(segment);
184
213
  }
@@ -196,19 +225,20 @@ class CodePathSegment {
196
225
  }
197
226
 
198
227
  /**
199
- * Replaces unused segments with the previous segments of each unused segment.
200
- * @param {CodePathSegment[]} segments An array of segments to replace.
201
- * @returns {CodePathSegment[]} The replaced array.
228
+ * Creates a new array based on an array of segments. If any segment in the
229
+ * array is unused, then it is replaced by all of its previous segments.
230
+ * All used segments are returned as-is without replacement.
231
+ * @param {CodePathSegment[]} segments The array of segments to flatten.
232
+ * @returns {CodePathSegment[]} The flattened array.
202
233
  */
203
234
  static flattenUnusedSegments(segments) {
204
- const done = Object.create(null);
205
- const retv = [];
235
+ const done = new Set();
206
236
 
207
237
  for (let i = 0; i < segments.length; ++i) {
208
238
  const segment = segments[i];
209
239
 
210
240
  // Ignores duplicated.
211
- if (done[segment.id]) {
241
+ if (done.has(segment)) {
212
242
  continue;
213
243
  }
214
244
 
@@ -217,18 +247,16 @@ class CodePathSegment {
217
247
  for (let j = 0; j < segment.allPrevSegments.length; ++j) {
218
248
  const prevSegment = segment.allPrevSegments[j];
219
249
 
220
- if (!done[prevSegment.id]) {
221
- done[prevSegment.id] = true;
222
- retv.push(prevSegment);
250
+ if (!done.has(prevSegment)) {
251
+ done.add(prevSegment);
223
252
  }
224
253
  }
225
254
  } else {
226
- done[segment.id] = true;
227
- retv.push(segment);
255
+ done.add(segment);
228
256
  }
229
257
  }
230
258
 
231
- return retv;
259
+ return [...done];
232
260
  }
233
261
  }
234
262