eslint 9.0.0-beta.2 → 9.0.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 (38) hide show
  1. package/README.md +8 -12
  2. package/bin/eslint.js +14 -1
  3. package/lib/cli.js +30 -11
  4. package/lib/config/flat-config-schema.js +1 -2
  5. package/lib/eslint/eslint-helpers.js +5 -1
  6. package/lib/eslint/eslint.js +16 -1
  7. package/lib/linter/code-path-analysis/code-path-analyzer.js +0 -1
  8. package/lib/linter/index.js +1 -3
  9. package/lib/linter/linter.js +184 -40
  10. package/lib/linter/timing.js +16 -8
  11. package/lib/options.js +25 -1
  12. package/lib/rule-tester/index.js +3 -1
  13. package/lib/rule-tester/rule-tester.js +18 -2
  14. package/lib/rules/camelcase.js +3 -5
  15. package/lib/rules/constructor-super.js +98 -99
  16. package/lib/rules/no-fallthrough.js +41 -16
  17. package/lib/rules/no-lone-blocks.js +1 -1
  18. package/lib/rules/no-this-before-super.js +28 -9
  19. package/lib/rules/no-unused-vars.js +179 -29
  20. package/lib/rules/no-useless-return.js +7 -2
  21. package/lib/rules/use-isnan.js +2 -2
  22. package/lib/rules/utils/lazy-loading-rule-map.js +1 -1
  23. package/lib/rules/utils/unicode/index.js +9 -4
  24. package/lib/shared/runtime-info.js +1 -0
  25. package/lib/shared/stats.js +30 -0
  26. package/lib/shared/types.js +34 -0
  27. package/lib/source-code/index.js +3 -1
  28. package/lib/source-code/source-code.js +165 -1
  29. package/lib/source-code/token-store/backward-token-cursor.js +3 -3
  30. package/lib/source-code/token-store/cursors.js +4 -2
  31. package/lib/source-code/token-store/forward-token-comment-cursor.js +3 -3
  32. package/lib/source-code/token-store/forward-token-cursor.js +3 -3
  33. package/messages/plugin-conflict.js +1 -1
  34. package/messages/plugin-invalid.js +1 -1
  35. package/messages/plugin-missing.js +1 -1
  36. package/package.json +12 -8
  37. package/lib/cli-engine/xml-escape.js +0 -34
  38. package/lib/shared/deprecation-warnings.js +0 -58
@@ -9,22 +9,6 @@
9
9
  // Helpers
10
10
  //------------------------------------------------------------------------------
11
11
 
12
- /**
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
- */
17
- function isAnySegmentReachable(segments) {
18
-
19
- for (const segment of segments) {
20
- if (segment.reachable) {
21
- return true;
22
- }
23
- }
24
-
25
- return false;
26
- }
27
-
28
12
  /**
29
13
  * Checks whether or not a given node is a constructor.
30
14
  * @param {ASTNode} node A node to check. This node type is one of
@@ -119,6 +103,30 @@ function isPossibleConstructor(node) {
119
103
  }
120
104
  }
121
105
 
106
+ /**
107
+ * A class to store information about a code path segment.
108
+ */
109
+ class SegmentInfo {
110
+
111
+ /**
112
+ * Indicates if super() is called in all code paths.
113
+ * @type {boolean}
114
+ */
115
+ calledInEveryPaths = false;
116
+
117
+ /**
118
+ * Indicates if super() is called in any code paths.
119
+ * @type {boolean}
120
+ */
121
+ calledInSomePaths = false;
122
+
123
+ /**
124
+ * The nodes which have been validated and don't need to be reconsidered.
125
+ * @type {ASTNode[]}
126
+ */
127
+ validNodes = [];
128
+ }
129
+
122
130
  //------------------------------------------------------------------------------
123
131
  // Rule Definition
124
132
  //------------------------------------------------------------------------------
@@ -141,8 +149,7 @@ module.exports = {
141
149
  missingAll: "Expected to call 'super()'.",
142
150
 
143
151
  duplicate: "Unexpected duplicate 'super()'.",
144
- badSuper: "Unexpected 'super()' because 'super' is not a constructor.",
145
- unexpected: "Unexpected 'super()'."
152
+ badSuper: "Unexpected 'super()' because 'super' is not a constructor."
146
153
  }
147
154
  },
148
155
 
@@ -159,14 +166,10 @@ module.exports = {
159
166
  */
160
167
  let funcInfo = null;
161
168
 
162
- /*
163
- * {Map<string, {calledInSomePaths: boolean, calledInEveryPaths: boolean}>}
164
- * Information for each code path segment.
165
- * - calledInSomePaths: A flag of be called `super()` in some code paths.
166
- * - calledInEveryPaths: A flag of be called `super()` in all code paths.
167
- * - validNodes:
169
+ /**
170
+ * @type {Record<string, SegmentInfo>}
168
171
  */
169
- let segInfoMap = Object.create(null);
172
+ const segInfoMap = Object.create(null);
170
173
 
171
174
  /**
172
175
  * Gets the flag which shows `super()` is called in some paths.
@@ -177,23 +180,21 @@ module.exports = {
177
180
  return segment.reachable && segInfoMap[segment.id].calledInSomePaths;
178
181
  }
179
182
 
183
+ /**
184
+ * Determines if a segment has been seen in the traversal.
185
+ * @param {CodePathSegment} segment A code path segment to check.
186
+ * @returns {boolean} `true` if the segment has been seen.
187
+ */
188
+ function hasSegmentBeenSeen(segment) {
189
+ return !!segInfoMap[segment.id];
190
+ }
191
+
180
192
  /**
181
193
  * Gets the flag which shows `super()` is called in all paths.
182
194
  * @param {CodePathSegment} segment A code path segment to get.
183
195
  * @returns {boolean} The flag which shows `super()` is called in all paths.
184
196
  */
185
197
  function isCalledInEveryPath(segment) {
186
-
187
- /*
188
- * If specific segment is the looped segment of the current segment,
189
- * skip the segment.
190
- * If not skipped, this never becomes true after a loop.
191
- */
192
- if (segment.nextSegments.length === 1 &&
193
- segment.nextSegments[0].isLoopedPrevSegment(segment)
194
- ) {
195
- return true;
196
- }
197
198
  return segment.reachable && segInfoMap[segment.id].calledInEveryPaths;
198
199
  }
199
200
 
@@ -250,9 +251,9 @@ module.exports = {
250
251
  }
251
252
 
252
253
  // Reports if `super()` lacked.
253
- const segments = codePath.returnedSegments;
254
- const calledInEveryPaths = segments.every(isCalledInEveryPath);
255
- const calledInSomePaths = segments.some(isCalledInSomePath);
254
+ const returnedSegments = codePath.returnedSegments;
255
+ const calledInEveryPaths = returnedSegments.every(isCalledInEveryPath);
256
+ const calledInSomePaths = returnedSegments.some(isCalledInSomePath);
256
257
 
257
258
  if (!calledInEveryPaths) {
258
259
  context.report({
@@ -267,29 +268,37 @@ module.exports = {
267
268
  /**
268
269
  * Initialize information of a given code path segment.
269
270
  * @param {CodePathSegment} segment A code path segment to initialize.
271
+ * @param {CodePathSegment} node Node that starts the segment.
270
272
  * @returns {void}
271
273
  */
272
- onCodePathSegmentStart(segment) {
274
+ onCodePathSegmentStart(segment, node) {
273
275
 
274
276
  funcInfo.currentSegments.add(segment);
275
277
 
276
- if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) {
278
+ if (!(funcInfo.isConstructor && funcInfo.hasExtends)) {
277
279
  return;
278
280
  }
279
281
 
280
282
  // Initialize info.
281
- const info = segInfoMap[segment.id] = {
282
- calledInSomePaths: false,
283
- calledInEveryPaths: false,
284
- validNodes: []
285
- };
283
+ const info = segInfoMap[segment.id] = new SegmentInfo();
284
+
285
+ const seenPrevSegments = segment.prevSegments.filter(hasSegmentBeenSeen);
286
286
 
287
287
  // When there are previous segments, aggregates these.
288
- const prevSegments = segment.prevSegments;
288
+ if (seenPrevSegments.length > 0) {
289
+ info.calledInSomePaths = seenPrevSegments.some(isCalledInSomePath);
290
+ info.calledInEveryPaths = seenPrevSegments.every(isCalledInEveryPath);
291
+ }
289
292
 
290
- if (prevSegments.length > 0) {
291
- info.calledInSomePaths = prevSegments.some(isCalledInSomePath);
292
- info.calledInEveryPaths = prevSegments.every(isCalledInEveryPath);
293
+ /*
294
+ * ForStatement > *.update segments are a special case as they are created in advance,
295
+ * without seen previous segments. Since they logically don't affect `calledInEveryPaths`
296
+ * calculations, and they can never be a lone previous segment of another one, we'll set
297
+ * their `calledInEveryPaths` to `true` to effectively ignore them in those calculations.
298
+ * .
299
+ */
300
+ if (node.parent && node.parent.type === "ForStatement" && node.parent.update === node) {
301
+ info.calledInEveryPaths = true;
293
302
  }
294
303
  },
295
304
 
@@ -316,25 +325,30 @@ module.exports = {
316
325
  * @returns {void}
317
326
  */
318
327
  onCodePathSegmentLoop(fromSegment, toSegment) {
319
- if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) {
328
+ if (!(funcInfo.isConstructor && funcInfo.hasExtends)) {
320
329
  return;
321
330
  }
322
331
 
323
- // Update information inside of the loop.
324
- const isRealLoop = toSegment.prevSegments.length >= 2;
325
-
326
332
  funcInfo.codePath.traverseSegments(
327
333
  { first: toSegment, last: fromSegment },
328
- segment => {
334
+ (segment, controller) => {
329
335
  const info = segInfoMap[segment.id];
330
- const prevSegments = segment.prevSegments;
331
336
 
332
- // Updates flags.
333
- info.calledInSomePaths = prevSegments.some(isCalledInSomePath);
334
- info.calledInEveryPaths = prevSegments.every(isCalledInEveryPath);
337
+ // skip segments after the loop
338
+ if (!info) {
339
+ controller.skip();
340
+ return;
341
+ }
342
+
343
+ const seenPrevSegments = segment.prevSegments.filter(hasSegmentBeenSeen);
344
+ const calledInSomePreviousPaths = seenPrevSegments.some(isCalledInSomePath);
345
+ const calledInEveryPreviousPaths = seenPrevSegments.every(isCalledInEveryPath);
346
+
347
+ info.calledInSomePaths ||= calledInSomePreviousPaths;
348
+ info.calledInEveryPaths ||= calledInEveryPreviousPaths;
335
349
 
336
350
  // If flags become true anew, reports the valid nodes.
337
- if (info.calledInSomePaths || isRealLoop) {
351
+ if (calledInSomePreviousPaths) {
338
352
  const nodes = info.validNodes;
339
353
 
340
354
  info.validNodes = [];
@@ -358,7 +372,7 @@ module.exports = {
358
372
  * @returns {void}
359
373
  */
360
374
  "CallExpression:exit"(node) {
361
- if (!(funcInfo && funcInfo.isConstructor)) {
375
+ if (!(funcInfo.isConstructor && funcInfo.hasExtends)) {
362
376
  return;
363
377
  }
364
378
 
@@ -368,41 +382,34 @@ module.exports = {
368
382
  }
369
383
 
370
384
  // Reports if needed.
371
- if (funcInfo.hasExtends) {
372
- const segments = funcInfo.currentSegments;
373
- let duplicate = false;
374
- let info = null;
385
+ const segments = funcInfo.currentSegments;
386
+ let duplicate = false;
387
+ let info = null;
375
388
 
376
- for (const segment of segments) {
389
+ for (const segment of segments) {
377
390
 
378
- if (segment.reachable) {
379
- info = segInfoMap[segment.id];
391
+ if (segment.reachable) {
392
+ info = segInfoMap[segment.id];
380
393
 
381
- duplicate = duplicate || info.calledInSomePaths;
382
- info.calledInSomePaths = info.calledInEveryPaths = true;
383
- }
394
+ duplicate = duplicate || info.calledInSomePaths;
395
+ info.calledInSomePaths = info.calledInEveryPaths = true;
384
396
  }
397
+ }
385
398
 
386
- if (info) {
387
- if (duplicate) {
388
- context.report({
389
- messageId: "duplicate",
390
- node
391
- });
392
- } else if (!funcInfo.superIsConstructor) {
393
- context.report({
394
- messageId: "badSuper",
395
- node
396
- });
397
- } else {
398
- info.validNodes.push(node);
399
- }
399
+ if (info) {
400
+ if (duplicate) {
401
+ context.report({
402
+ messageId: "duplicate",
403
+ node
404
+ });
405
+ } else if (!funcInfo.superIsConstructor) {
406
+ context.report({
407
+ messageId: "badSuper",
408
+ node
409
+ });
410
+ } else {
411
+ info.validNodes.push(node);
400
412
  }
401
- } else if (isAnySegmentReachable(funcInfo.currentSegments)) {
402
- context.report({
403
- messageId: "unexpected",
404
- node
405
- });
406
413
  }
407
414
  },
408
415
 
@@ -412,7 +419,7 @@ module.exports = {
412
419
  * @returns {void}
413
420
  */
414
421
  ReturnStatement(node) {
415
- if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) {
422
+ if (!(funcInfo.isConstructor && funcInfo.hasExtends)) {
416
423
  return;
417
424
  }
418
425
 
@@ -432,14 +439,6 @@ module.exports = {
432
439
  info.calledInSomePaths = info.calledInEveryPaths = true;
433
440
  }
434
441
  }
435
- },
436
-
437
- /**
438
- * Resets state.
439
- * @returns {void}
440
- */
441
- "Program:exit"() {
442
- segInfoMap = Object.create(null);
443
442
  }
444
443
  };
445
444
  }
@@ -48,9 +48,9 @@ function isFallThroughComment(comment, fallthroughCommentPattern) {
48
48
  * @param {ASTNode} subsequentCase The case after caseWhichFallsThrough.
49
49
  * @param {RuleContext} context A rule context which stores comments.
50
50
  * @param {RegExp} fallthroughCommentPattern A pattern to match comment to.
51
- * @returns {boolean} `true` if the case has a valid fallthrough comment.
51
+ * @returns {null | object} the comment if the case has a valid fallthrough comment, otherwise null
52
52
  */
53
- function hasFallthroughComment(caseWhichFallsThrough, subsequentCase, context, fallthroughCommentPattern) {
53
+ function getFallthroughComment(caseWhichFallsThrough, subsequentCase, context, fallthroughCommentPattern) {
54
54
  const sourceCode = context.sourceCode;
55
55
 
56
56
  if (caseWhichFallsThrough.consequent.length === 1 && caseWhichFallsThrough.consequent[0].type === "BlockStatement") {
@@ -58,13 +58,17 @@ function hasFallthroughComment(caseWhichFallsThrough, subsequentCase, context, f
58
58
  const commentInBlock = sourceCode.getCommentsBefore(trailingCloseBrace).pop();
59
59
 
60
60
  if (commentInBlock && isFallThroughComment(commentInBlock.value, fallthroughCommentPattern)) {
61
- return true;
61
+ return commentInBlock;
62
62
  }
63
63
  }
64
64
 
65
65
  const comment = sourceCode.getCommentsBefore(subsequentCase).pop();
66
66
 
67
- return Boolean(comment && isFallThroughComment(comment.value, fallthroughCommentPattern));
67
+ if (comment && isFallThroughComment(comment.value, fallthroughCommentPattern)) {
68
+ return comment;
69
+ }
70
+
71
+ return null;
68
72
  }
69
73
 
70
74
  /**
@@ -103,12 +107,17 @@ module.exports = {
103
107
  allowEmptyCase: {
104
108
  type: "boolean",
105
109
  default: false
110
+ },
111
+ reportUnusedFallthroughComment: {
112
+ type: "boolean",
113
+ default: false
106
114
  }
107
115
  },
108
116
  additionalProperties: false
109
117
  }
110
118
  ],
111
119
  messages: {
120
+ unusedFallthroughComment: "Found a comment that would permit fallthrough, but case cannot fall through.",
112
121
  case: "Expected a 'break' statement before 'case'.",
113
122
  default: "Expected a 'break' statement before 'default'."
114
123
  }
@@ -120,12 +129,13 @@ module.exports = {
120
129
  let currentCodePathSegments = new Set();
121
130
  const sourceCode = context.sourceCode;
122
131
  const allowEmptyCase = options.allowEmptyCase || false;
132
+ const reportUnusedFallthroughComment = options.reportUnusedFallthroughComment || false;
123
133
 
124
134
  /*
125
135
  * We need to use leading comments of the next SwitchCase node because
126
136
  * trailing comments is wrong if semicolons are omitted.
127
137
  */
128
- let fallthroughCase = null;
138
+ let previousCase = null;
129
139
  let fallthroughCommentPattern = null;
130
140
 
131
141
  if (options.commentPattern) {
@@ -168,13 +178,23 @@ module.exports = {
168
178
  * And reports the previous fallthrough node if that does not exist.
169
179
  */
170
180
 
171
- if (fallthroughCase && (!hasFallthroughComment(fallthroughCase, node, context, fallthroughCommentPattern))) {
172
- context.report({
173
- messageId: node.test ? "case" : "default",
174
- node
175
- });
181
+ if (previousCase && previousCase.node.parent === node.parent) {
182
+ const previousCaseFallthroughComment = getFallthroughComment(previousCase.node, node, context, fallthroughCommentPattern);
183
+
184
+ if (previousCase.isFallthrough && !(previousCaseFallthroughComment)) {
185
+ context.report({
186
+ messageId: node.test ? "case" : "default",
187
+ node
188
+ });
189
+ } else if (reportUnusedFallthroughComment && !previousCase.isSwitchExitReachable && previousCaseFallthroughComment) {
190
+ context.report({
191
+ messageId: "unusedFallthroughComment",
192
+ node: previousCaseFallthroughComment
193
+ });
194
+ }
195
+
176
196
  }
177
- fallthroughCase = null;
197
+ previousCase = null;
178
198
  },
179
199
 
180
200
  "SwitchCase:exit"(node) {
@@ -185,11 +205,16 @@ module.exports = {
185
205
  * `break`, `return`, or `throw` are unreachable.
186
206
  * And allows empty cases and the last case.
187
207
  */
188
- if (isAnySegmentReachable(currentCodePathSegments) &&
189
- (node.consequent.length > 0 || (!allowEmptyCase && hasBlankLinesBetween(node, nextToken))) &&
190
- node.parent.cases.at(-1) !== node) {
191
- fallthroughCase = node;
192
- }
208
+ const isSwitchExitReachable = isAnySegmentReachable(currentCodePathSegments);
209
+ const isFallthrough = isSwitchExitReachable && (node.consequent.length > 0 || (!allowEmptyCase && hasBlankLinesBetween(node, nextToken))) &&
210
+ node.parent.cases.at(-1) !== node;
211
+
212
+ previousCase = {
213
+ node,
214
+ isSwitchExitReachable,
215
+ isFallthrough
216
+ };
217
+
193
218
  }
194
219
  };
195
220
  }
@@ -117,7 +117,7 @@ module.exports = {
117
117
  };
118
118
 
119
119
  ruleDef.VariableDeclaration = function(node) {
120
- if (node.kind === "let" || node.kind === "const") {
120
+ if (node.kind !== "var") {
121
121
  markLoneBlock(node);
122
122
  }
123
123
  };
@@ -30,6 +30,29 @@ function isConstructorFunction(node) {
30
30
  );
31
31
  }
32
32
 
33
+ /*
34
+ * Information for each code path segment.
35
+ * - superCalled: The flag which shows `super()` called in all code paths.
36
+ * - invalidNodes: The array of invalid ThisExpression and Super nodes.
37
+ */
38
+ /**
39
+ *
40
+ */
41
+ class SegmentInfo {
42
+
43
+ /**
44
+ * Indicates whether `super()` is called in all code paths.
45
+ * @type {boolean}
46
+ */
47
+ superCalled = false;
48
+
49
+ /**
50
+ * The array of invalid ThisExpression and Super nodes.
51
+ * @type {ASTNode[]}
52
+ */
53
+ invalidNodes = [];
54
+ }
55
+
33
56
  //------------------------------------------------------------------------------
34
57
  // Rule Definition
35
58
  //------------------------------------------------------------------------------
@@ -64,13 +87,7 @@ module.exports = {
64
87
  */
65
88
  let funcInfo = null;
66
89
 
67
- /*
68
- * Information for each code path segment.
69
- * Each key is the id of a code path segment.
70
- * Each value is an object:
71
- * - superCalled: The flag which shows `super()` called in all code paths.
72
- * - invalidNodes: The array of invalid ThisExpression and Super nodes.
73
- */
90
+ /** @type {Record<string, SegmentInfo>} */
74
91
  let segInfoMap = Object.create(null);
75
92
 
76
93
  /**
@@ -79,7 +96,7 @@ module.exports = {
79
96
  * @returns {boolean} `true` if `super()` is called.
80
97
  */
81
98
  function isCalled(segment) {
82
- return !segment.reachable || segInfoMap[segment.id].superCalled;
99
+ return !segment.reachable || segInfoMap[segment.id]?.superCalled;
83
100
  }
84
101
 
85
102
  /**
@@ -285,7 +302,7 @@ module.exports = {
285
302
  funcInfo.codePath.traverseSegments(
286
303
  { first: toSegment, last: fromSegment },
287
304
  (segment, controller) => {
288
- const info = segInfoMap[segment.id];
305
+ const info = segInfoMap[segment.id] ?? new SegmentInfo();
289
306
 
290
307
  if (info.superCalled) {
291
308
  controller.skip();
@@ -295,6 +312,8 @@ module.exports = {
295
312
  ) {
296
313
  info.superCalled = true;
297
314
  }
315
+
316
+ segInfoMap[segment.id] = info;
298
317
  }
299
318
  );
300
319
  },