eslint 8.57.0 → 9.2.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 (156) hide show
  1. package/README.md +31 -28
  2. package/bin/eslint.js +4 -3
  3. package/conf/ecma-version.js +16 -0
  4. package/conf/globals.js +1 -0
  5. package/conf/rule-type-list.json +3 -1
  6. package/lib/api.js +7 -11
  7. package/lib/cli-engine/cli-engine.js +14 -3
  8. package/lib/cli-engine/formatters/formatters-meta.json +1 -29
  9. package/lib/cli-engine/lint-result-cache.js +2 -2
  10. package/lib/cli.js +115 -36
  11. package/lib/config/default-config.js +3 -0
  12. package/lib/config/flat-config-array.js +110 -24
  13. package/lib/config/flat-config-helpers.js +41 -20
  14. package/lib/config/flat-config-schema.js +1 -7
  15. package/lib/config/rule-validator.js +42 -6
  16. package/lib/eslint/eslint-helpers.js +116 -58
  17. package/lib/eslint/eslint.js +892 -377
  18. package/lib/eslint/index.js +2 -2
  19. package/lib/eslint/legacy-eslint.js +728 -0
  20. package/lib/linter/apply-disable-directives.js +59 -31
  21. package/lib/linter/code-path-analysis/code-path-analyzer.js +0 -1
  22. package/lib/linter/code-path-analysis/code-path.js +32 -30
  23. package/lib/linter/code-path-analysis/fork-context.js +1 -1
  24. package/lib/linter/config-comment-parser.js +8 -11
  25. package/lib/linter/index.js +1 -3
  26. package/lib/linter/interpolate.js +24 -2
  27. package/lib/linter/linter.js +428 -207
  28. package/lib/linter/report-translator.js +3 -3
  29. package/lib/linter/rules.js +6 -15
  30. package/lib/linter/source-code-fixer.js +1 -1
  31. package/lib/linter/timing.js +16 -8
  32. package/lib/options.js +35 -3
  33. package/lib/rule-tester/index.js +3 -1
  34. package/lib/rule-tester/rule-tester.js +424 -347
  35. package/lib/rules/array-bracket-newline.js +1 -1
  36. package/lib/rules/array-bracket-spacing.js +1 -1
  37. package/lib/rules/block-scoped-var.js +1 -1
  38. package/lib/rules/callback-return.js +2 -2
  39. package/lib/rules/camelcase.js +3 -5
  40. package/lib/rules/capitalized-comments.js +10 -7
  41. package/lib/rules/comma-dangle.js +1 -1
  42. package/lib/rules/comma-style.js +2 -2
  43. package/lib/rules/complexity.js +14 -1
  44. package/lib/rules/constructor-super.js +99 -100
  45. package/lib/rules/default-case.js +1 -1
  46. package/lib/rules/eol-last.js +2 -2
  47. package/lib/rules/function-paren-newline.js +2 -2
  48. package/lib/rules/indent-legacy.js +5 -5
  49. package/lib/rules/indent.js +5 -5
  50. package/lib/rules/index.js +1 -2
  51. package/lib/rules/key-spacing.js +2 -2
  52. package/lib/rules/line-comment-position.js +1 -1
  53. package/lib/rules/lines-around-directive.js +2 -2
  54. package/lib/rules/max-depth.js +1 -1
  55. package/lib/rules/max-len.js +3 -3
  56. package/lib/rules/max-lines.js +3 -3
  57. package/lib/rules/max-nested-callbacks.js +1 -1
  58. package/lib/rules/max-params.js +1 -1
  59. package/lib/rules/max-statements.js +1 -1
  60. package/lib/rules/multiline-comment-style.js +7 -7
  61. package/lib/rules/new-cap.js +1 -1
  62. package/lib/rules/newline-after-var.js +1 -1
  63. package/lib/rules/newline-before-return.js +1 -1
  64. package/lib/rules/no-case-declarations.js +13 -1
  65. package/lib/rules/no-constant-binary-expression.js +7 -8
  66. package/lib/rules/no-constant-condition.js +18 -7
  67. package/lib/rules/no-constructor-return.js +2 -2
  68. package/lib/rules/no-dupe-class-members.js +2 -2
  69. package/lib/rules/no-else-return.js +1 -1
  70. package/lib/rules/no-empty-function.js +2 -2
  71. package/lib/rules/no-empty-static-block.js +1 -1
  72. package/lib/rules/no-extend-native.js +1 -2
  73. package/lib/rules/no-extra-semi.js +1 -1
  74. package/lib/rules/no-fallthrough.js +41 -16
  75. package/lib/rules/no-implicit-coercion.js +66 -24
  76. package/lib/rules/no-inner-declarations.js +23 -2
  77. package/lib/rules/no-invalid-regexp.js +1 -1
  78. package/lib/rules/no-invalid-this.js +1 -1
  79. package/lib/rules/no-lone-blocks.js +3 -3
  80. package/lib/rules/no-loss-of-precision.js +1 -1
  81. package/lib/rules/no-misleading-character-class.js +225 -69
  82. package/lib/rules/no-mixed-spaces-and-tabs.js +1 -1
  83. package/lib/rules/no-multiple-empty-lines.js +1 -1
  84. package/lib/rules/no-new-native-nonconstructor.js +1 -1
  85. package/lib/rules/no-new-symbol.js +8 -1
  86. package/lib/rules/no-restricted-globals.js +1 -1
  87. package/lib/rules/no-restricted-imports.js +186 -40
  88. package/lib/rules/no-restricted-modules.js +2 -2
  89. package/lib/rules/no-return-await.js +1 -1
  90. package/lib/rules/no-sequences.js +1 -0
  91. package/lib/rules/no-this-before-super.js +45 -13
  92. package/lib/rules/no-trailing-spaces.js +2 -3
  93. package/lib/rules/no-unneeded-ternary.js +1 -1
  94. package/lib/rules/no-unsafe-optional-chaining.js +1 -1
  95. package/lib/rules/no-unused-private-class-members.js +1 -1
  96. package/lib/rules/no-unused-vars.js +197 -36
  97. package/lib/rules/no-useless-assignment.js +566 -0
  98. package/lib/rules/no-useless-backreference.js +1 -1
  99. package/lib/rules/no-useless-computed-key.js +2 -2
  100. package/lib/rules/no-useless-return.js +7 -2
  101. package/lib/rules/object-curly-spacing.js +3 -3
  102. package/lib/rules/object-property-newline.js +1 -1
  103. package/lib/rules/one-var.js +5 -5
  104. package/lib/rules/padded-blocks.js +7 -7
  105. package/lib/rules/prefer-arrow-callback.js +3 -3
  106. package/lib/rules/prefer-reflect.js +1 -1
  107. package/lib/rules/prefer-regex-literals.js +1 -1
  108. package/lib/rules/prefer-template.js +1 -1
  109. package/lib/rules/radix.js +2 -2
  110. package/lib/rules/semi-style.js +1 -1
  111. package/lib/rules/sort-imports.js +1 -1
  112. package/lib/rules/sort-keys.js +1 -1
  113. package/lib/rules/sort-vars.js +1 -1
  114. package/lib/rules/space-unary-ops.js +1 -1
  115. package/lib/rules/strict.js +1 -1
  116. package/lib/rules/use-isnan.js +101 -7
  117. package/lib/rules/utils/ast-utils.js +16 -7
  118. package/lib/rules/utils/char-source.js +240 -0
  119. package/lib/rules/utils/lazy-loading-rule-map.js +1 -1
  120. package/lib/rules/utils/unicode/index.js +9 -4
  121. package/lib/rules/yield-star-spacing.js +1 -1
  122. package/lib/shared/runtime-info.js +1 -0
  123. package/lib/shared/serialization.js +55 -0
  124. package/lib/shared/stats.js +30 -0
  125. package/lib/shared/string-utils.js +9 -11
  126. package/lib/shared/types.js +35 -1
  127. package/lib/source-code/index.js +3 -1
  128. package/lib/source-code/source-code.js +299 -85
  129. package/lib/source-code/token-store/backward-token-cursor.js +3 -3
  130. package/lib/source-code/token-store/cursors.js +4 -2
  131. package/lib/source-code/token-store/forward-token-comment-cursor.js +3 -3
  132. package/lib/source-code/token-store/forward-token-cursor.js +3 -3
  133. package/lib/source-code/token-store/index.js +2 -2
  134. package/lib/unsupported-api.js +3 -5
  135. package/messages/no-config-found.js +1 -1
  136. package/messages/plugin-conflict.js +1 -1
  137. package/messages/plugin-invalid.js +1 -1
  138. package/messages/plugin-missing.js +1 -1
  139. package/package.json +32 -29
  140. package/conf/config-schema.js +0 -93
  141. package/lib/cli-engine/formatters/checkstyle.js +0 -60
  142. package/lib/cli-engine/formatters/compact.js +0 -60
  143. package/lib/cli-engine/formatters/jslint-xml.js +0 -41
  144. package/lib/cli-engine/formatters/junit.js +0 -82
  145. package/lib/cli-engine/formatters/tap.js +0 -95
  146. package/lib/cli-engine/formatters/unix.js +0 -58
  147. package/lib/cli-engine/formatters/visualstudio.js +0 -63
  148. package/lib/cli-engine/xml-escape.js +0 -34
  149. package/lib/eslint/flat-eslint.js +0 -1155
  150. package/lib/rule-tester/flat-rule-tester.js +0 -1131
  151. package/lib/rules/require-jsdoc.js +0 -122
  152. package/lib/rules/utils/patterns/letters.js +0 -36
  153. package/lib/rules/valid-jsdoc.js +0 -516
  154. package/lib/shared/config-validator.js +0 -347
  155. package/lib/shared/deprecation-warnings.js +0 -58
  156. package/lib/shared/relative-module-resolver.js +0 -50
@@ -28,7 +28,7 @@ function containsLineTerminator(str) {
28
28
  * @returns {any} Last element of arr.
29
29
  */
30
30
  function last(arr) {
31
- return arr[arr.length - 1];
31
+ return arr.at(-1);
32
32
  }
33
33
 
34
34
  /**
@@ -489,7 +489,7 @@ module.exports = {
489
489
  }
490
490
  }
491
491
 
492
- let messageId = "";
492
+ let messageId;
493
493
 
494
494
  if (isExtra) {
495
495
  messageId = side === "key" ? "extraKey" : "extraValue";
@@ -68,7 +68,7 @@ module.exports = {
68
68
  above = !options.position || options.position === "above";
69
69
  ignorePattern = options.ignorePattern;
70
70
 
71
- if (Object.prototype.hasOwnProperty.call(options, "applyDefaultIgnorePatterns")) {
71
+ if (Object.hasOwn(options, "applyDefaultIgnorePatterns")) {
72
72
  applyDefaultIgnorePatterns = options.applyDefaultIgnorePatterns;
73
73
  } else {
74
74
  applyDefaultIgnorePatterns = options.applyDefaultPatterns !== false;
@@ -166,7 +166,7 @@ module.exports = {
166
166
  reportError(firstDirective, "before", false);
167
167
  }
168
168
 
169
- const lastDirective = directives[directives.length - 1];
169
+ const lastDirective = directives.at(-1);
170
170
  const statements = node.type === "Program" ? node.body : node.body.body;
171
171
 
172
172
  /*
@@ -174,7 +174,7 @@ module.exports = {
174
174
  * contains a directive prologue and isn't followed by a comment to ensure
175
175
  * this rule behaves well with padded-blocks.
176
176
  */
177
- if (lastDirective === statements[statements.length - 1] && !lastDirective.trailingComments) {
177
+ if (lastDirective === statements.at(-1) && !lastDirective.trailingComments) {
178
178
  return;
179
179
  }
180
180
 
@@ -61,7 +61,7 @@ module.exports = {
61
61
 
62
62
  if (
63
63
  typeof option === "object" &&
64
- (Object.prototype.hasOwnProperty.call(option, "maximum") || Object.prototype.hasOwnProperty.call(option, "max"))
64
+ (Object.hasOwn(option, "maximum") || Object.hasOwn(option, "max"))
65
65
  ) {
66
66
  maxDepth = option.maximum || option.max;
67
67
  }
@@ -124,7 +124,7 @@ module.exports = {
124
124
  }
125
125
 
126
126
  // The options object must be the last option specified…
127
- const options = Object.assign({}, context.options[context.options.length - 1]);
127
+ const options = Object.assign({}, context.options.at(-1));
128
128
 
129
129
  // …but max code length…
130
130
  if (typeof context.options[0] === "number") {
@@ -290,7 +290,7 @@ module.exports = {
290
290
  if (isJSXEmptyExpressionInSingleLineContainer(containingNode)) {
291
291
 
292
292
  // push a unique node only
293
- if (comments[comments.length - 1] !== containingNode.parent) {
293
+ if (comments.at(-1) !== containingNode.parent) {
294
294
  comments.push(containingNode.parent);
295
295
  }
296
296
  } else {
@@ -344,7 +344,7 @@ module.exports = {
344
344
  * comments to check.
345
345
  */
346
346
  if (commentsIndex < comments.length) {
347
- let comment = null;
347
+ let comment;
348
348
 
349
349
  // iterate over comments until we find one past the current line
350
350
  do {
@@ -77,7 +77,7 @@ module.exports = {
77
77
 
78
78
  if (
79
79
  typeof option === "object" &&
80
- Object.prototype.hasOwnProperty.call(option, "max")
80
+ Object.hasOwn(option, "max")
81
81
  ) {
82
82
  max = option.max;
83
83
  } else if (typeof option === "number") {
@@ -148,7 +148,7 @@ module.exports = {
148
148
  * If file ends with a linebreak, `sourceCode.lines` will have one extra empty line at the end.
149
149
  * That isn't a real line, so we shouldn't count it.
150
150
  */
151
- if (lines.length > 1 && lines[lines.length - 1].text === "") {
151
+ if (lines.length > 1 && lines.at(-1).text === "") {
152
152
  lines.pop();
153
153
  }
154
154
 
@@ -174,7 +174,7 @@ module.exports = {
174
174
  },
175
175
  end: {
176
176
  line: sourceCode.lines.length,
177
- column: sourceCode.lines[sourceCode.lines.length - 1].length
177
+ column: sourceCode.lines.at(-1).length
178
178
  }
179
179
  };
180
180
 
@@ -59,7 +59,7 @@ module.exports = {
59
59
 
60
60
  if (
61
61
  typeof option === "object" &&
62
- (Object.prototype.hasOwnProperty.call(option, "maximum") || Object.prototype.hasOwnProperty.call(option, "max"))
62
+ (Object.hasOwn(option, "maximum") || Object.hasOwn(option, "max"))
63
63
  ) {
64
64
  THRESHOLD = option.maximum || option.max;
65
65
  } else if (typeof option === "number") {
@@ -63,7 +63,7 @@ module.exports = {
63
63
 
64
64
  if (
65
65
  typeof option === "object" &&
66
- (Object.prototype.hasOwnProperty.call(option, "maximum") || Object.prototype.hasOwnProperty.call(option, "max"))
66
+ (Object.hasOwn(option, "maximum") || Object.hasOwn(option, "max"))
67
67
  ) {
68
68
  numParams = option.maximum || option.max;
69
69
  }
@@ -79,7 +79,7 @@ module.exports = {
79
79
 
80
80
  if (
81
81
  typeof option === "object" &&
82
- (Object.prototype.hasOwnProperty.call(option, "maximum") || Object.prototype.hasOwnProperty.call(option, "max"))
82
+ (Object.hasOwn(option, "maximum") || Object.hasOwn(option, "max"))
83
83
  ) {
84
84
  maxStatements = option.maximum || option.max;
85
85
  } else if (typeof option === "number") {
@@ -113,7 +113,7 @@ module.exports = {
113
113
 
114
114
  return /^\*\s*$/u.test(lines[0]) &&
115
115
  lines.slice(1, -1).every(line => /^\s* /u.test(line)) &&
116
- /^\s*$/u.test(lines[lines.length - 1]);
116
+ /^\s*$/u.test(lines.at(-1));
117
117
  }
118
118
 
119
119
  /**
@@ -272,11 +272,11 @@ module.exports = {
272
272
  context.report({
273
273
  loc: {
274
274
  start: firstComment.loc.start,
275
- end: commentGroup[commentGroup.length - 1].loc.end
275
+ end: commentGroup.at(-1).loc.end
276
276
  },
277
277
  messageId: "expectedBlock",
278
278
  fix(fixer) {
279
- const range = [firstComment.range[0], commentGroup[commentGroup.length - 1].range[1]];
279
+ const range = [firstComment.range[0], commentGroup.at(-1).range[1]];
280
280
 
281
281
  return commentLines.some(value => value.startsWith("/"))
282
282
  ? null
@@ -301,7 +301,7 @@ module.exports = {
301
301
  });
302
302
  }
303
303
 
304
- if (!/^\s*$/u.test(lines[lines.length - 1])) {
304
+ if (!/^\s*$/u.test(lines.at(-1))) {
305
305
  context.report({
306
306
  loc: {
307
307
  start: { line: firstComment.loc.end.line, column: firstComment.loc.end.column - 2 },
@@ -408,12 +408,12 @@ module.exports = {
408
408
  context.report({
409
409
  loc: {
410
410
  start: firstComment.loc.start,
411
- end: commentGroup[commentGroup.length - 1].loc.end
411
+ end: commentGroup.at(-1).loc.end
412
412
  },
413
413
  messageId: "expectedBlock",
414
414
  fix(fixer) {
415
415
  return fixer.replaceTextRange(
416
- [firstComment.range[0], commentGroup[commentGroup.length - 1].range[1]],
416
+ [firstComment.range[0], commentGroup.at(-1).range[1]],
417
417
  convertToBlock(firstComment, commentLines)
418
418
  );
419
419
  }
@@ -459,7 +459,7 @@ module.exports = {
459
459
  tokenBefore && tokenBefore.loc.end.line === comment.loc.start.line - 1 &&
460
460
  tokenBefore === commentList[index - 1]
461
461
  ) {
462
- commentGroups[commentGroups.length - 1].push(comment);
462
+ commentGroups.at(-1).push(comment);
463
463
  } else {
464
464
  commentGroups.push([comment]);
465
465
  }
@@ -40,7 +40,7 @@ const CAPS_ALLOWED = [
40
40
  function checkArray(obj, key, fallback) {
41
41
 
42
42
  /* c8 ignore start */
43
- if (Object.prototype.hasOwnProperty.call(obj, key) && !Array.isArray(obj[key])) {
43
+ if (Object.hasOwn(obj, key) && !Array.isArray(obj[key])) {
44
44
  throw new TypeError(`${key}, if provided, must be an Array`);
45
45
  }/* c8 ignore stop */
46
46
  return obj[key] || fallback;
@@ -215,7 +215,7 @@ module.exports = {
215
215
  fix(fixer) {
216
216
  const linesBetween = sourceCode.getText().slice(lastToken.range[1], nextToken.range[0]).split(astUtils.LINEBREAK_MATCHER);
217
217
 
218
- return fixer.replaceTextRange([lastToken.range[1], nextToken.range[0]], `${linesBetween.slice(0, -1).join("")}\n${linesBetween[linesBetween.length - 1]}`);
218
+ return fixer.replaceTextRange([lastToken.range[1], nextToken.range[0]], `${linesBetween.slice(0, -1).join("")}\n${linesBetween.at(-1)}`);
219
219
  }
220
220
  });
221
221
  }
@@ -166,7 +166,7 @@ module.exports = {
166
166
  */
167
167
  function canFix(node) {
168
168
  const leadingComments = sourceCode.getCommentsBefore(node);
169
- const lastLeadingComment = leadingComments[leadingComments.length - 1];
169
+ const lastLeadingComment = leadingComments.at(-1);
170
170
  const tokenBefore = sourceCode.getTokenBefore(node);
171
171
 
172
172
  if (leadingComments.length === 0) {
@@ -19,9 +19,12 @@ module.exports = {
19
19
  url: "https://eslint.org/docs/latest/rules/no-case-declarations"
20
20
  },
21
21
 
22
+ hasSuggestions: true,
23
+
22
24
  schema: [],
23
25
 
24
26
  messages: {
27
+ addBrackets: "Add {} brackets around the case block.",
25
28
  unexpected: "Unexpected lexical declaration in case block."
26
29
  }
27
30
  },
@@ -53,7 +56,16 @@ module.exports = {
53
56
  if (isLexicalDeclaration(statement)) {
54
57
  context.report({
55
58
  node: statement,
56
- messageId: "unexpected"
59
+ messageId: "unexpected",
60
+ suggest: [
61
+ {
62
+ messageId: "addBrackets",
63
+ fix: fixer => [
64
+ fixer.insertTextBefore(node.consequent[0], "{ "),
65
+ fixer.insertTextAfter(node.consequent.at(-1), " }")
66
+ ]
67
+ }
68
+ ]
57
69
  });
58
70
  }
59
71
  }
@@ -5,8 +5,7 @@
5
5
 
6
6
  "use strict";
7
7
 
8
- const globals = require("globals");
9
- const { isNullLiteral, isConstant, isReferenceToGlobalVariable, isLogicalAssignmentOperator } = require("./utils/ast-utils");
8
+ const { isNullLiteral, isConstant, isReferenceToGlobalVariable, isLogicalAssignmentOperator, ECMASCRIPT_GLOBALS } = require("./utils/ast-utils");
10
9
 
11
10
  const NUMERIC_OR_STRING_BINARY_OPERATORS = new Set(["+", "-", "*", "/", "%", "|", "^", "&", "**", "<<", ">>", ">>>"]);
12
11
 
@@ -103,7 +102,7 @@ function hasConstantNullishness(scope, node, nonNullish) {
103
102
 
104
103
  return true;
105
104
  case "SequenceExpression": {
106
- const last = node.expressions[node.expressions.length - 1];
105
+ const last = node.expressions.at(-1);
107
106
 
108
107
  return hasConstantNullishness(scope, last, nonNullish);
109
108
  }
@@ -248,7 +247,7 @@ function hasConstantLooseBooleanComparison(scope, node) {
248
247
  */
249
248
  return false;
250
249
  case "SequenceExpression": {
251
- const last = node.expressions[node.expressions.length - 1];
250
+ const last = node.expressions.at(-1);
252
251
 
253
252
  return hasConstantLooseBooleanComparison(scope, last);
254
253
  }
@@ -299,7 +298,7 @@ function hasConstantStrictBooleanComparison(scope, node) {
299
298
  return true;
300
299
  }
301
300
  case "SequenceExpression": {
302
- const last = node.expressions[node.expressions.length - 1];
301
+ const last = node.expressions.at(-1);
303
302
 
304
303
  return hasConstantStrictBooleanComparison(scope, last);
305
304
  }
@@ -376,7 +375,7 @@ function isAlwaysNew(scope, node) {
376
375
  * Catching these is especially useful for primitive constructors
377
376
  * which return boxed values, a surprising gotcha' in JavaScript.
378
377
  */
379
- return Object.hasOwnProperty.call(globals.builtin, node.callee.name) &&
378
+ return Object.hasOwn(ECMASCRIPT_GLOBALS, node.callee.name) &&
380
379
  isReferenceToGlobalVariable(scope, node.callee);
381
380
  }
382
381
  case "Literal":
@@ -384,7 +383,7 @@ function isAlwaysNew(scope, node) {
384
383
  // Regular expressions are objects, and thus always new
385
384
  return typeof node.regex === "object";
386
385
  case "SequenceExpression": {
387
- const last = node.expressions[node.expressions.length - 1];
386
+ const last = node.expressions.at(-1);
388
387
 
389
388
  return isAlwaysNew(scope, last);
390
389
  }
@@ -440,7 +439,7 @@ module.exports = {
440
439
  type: "problem",
441
440
  docs: {
442
441
  description: "Disallow expressions where the operation doesn't affect the value",
443
- recommended: false,
442
+ recommended: true,
444
443
  url: "https://eslint.org/docs/latest/rules/no-constant-binary-expression"
445
444
  },
446
445
  schema: [],
@@ -31,8 +31,7 @@ module.exports = {
31
31
  type: "object",
32
32
  properties: {
33
33
  checkLoops: {
34
- type: "boolean",
35
- default: true
34
+ enum: ["all", "allExceptWhileTrue", "none", true, false]
36
35
  }
37
36
  },
38
37
  additionalProperties: false
@@ -45,11 +44,17 @@ module.exports = {
45
44
  },
46
45
 
47
46
  create(context) {
48
- const options = context.options[0] || {},
49
- checkLoops = options.checkLoops !== false,
50
- loopSetStack = [];
47
+ const options = context.options[0] || {};
48
+ let checkLoops = options.checkLoops ?? "allExceptWhileTrue";
49
+ const loopSetStack = [];
51
50
  const sourceCode = context.sourceCode;
52
51
 
52
+ if (options.checkLoops === true) {
53
+ checkLoops = "all";
54
+ } else if (options.checkLoops === false) {
55
+ checkLoops = "none";
56
+ }
57
+
53
58
  let loopsInCurrentScope = new Set();
54
59
 
55
60
  //--------------------------------------------------------------------------
@@ -120,7 +125,7 @@ module.exports = {
120
125
  * @private
121
126
  */
122
127
  function checkLoop(node) {
123
- if (checkLoops) {
128
+ if (checkLoops === "all" || checkLoops === "allExceptWhileTrue") {
124
129
  trackConstantConditionLoop(node);
125
130
  }
126
131
  }
@@ -132,7 +137,13 @@ module.exports = {
132
137
  return {
133
138
  ConditionalExpression: reportIfConstant,
134
139
  IfStatement: reportIfConstant,
135
- WhileStatement: checkLoop,
140
+ WhileStatement(node) {
141
+ if (node.test.type === "Literal" && node.test.value === true && checkLoops === "allExceptWhileTrue") {
142
+ return;
143
+ }
144
+
145
+ checkLoop(node);
146
+ },
136
147
  "WhileStatement:exit": checkConstantConditionLoopInSet,
137
148
  DoWhileStatement: checkLoop,
138
149
  "DoWhileStatement:exit": checkConstantConditionLoopInSet,
@@ -20,7 +20,7 @@ module.exports = {
20
20
  url: "https://eslint.org/docs/latest/rules/no-constructor-return"
21
21
  },
22
22
 
23
- schema: {},
23
+ schema: [],
24
24
 
25
25
  fixable: null,
26
26
 
@@ -40,7 +40,7 @@ module.exports = {
40
40
  stack.pop();
41
41
  },
42
42
  ReturnStatement(node) {
43
- const last = stack[stack.length - 1];
43
+ const last = stack.at(-1);
44
44
 
45
45
  if (!last.parent) {
46
46
  return;
@@ -42,7 +42,7 @@ module.exports = {
42
42
  * - retv.set {boolean} A flag which shows the name is declared as setter.
43
43
  */
44
44
  function getState(name, isStatic) {
45
- const stateMap = stack[stack.length - 1];
45
+ const stateMap = stack.at(-1);
46
46
  const key = `$${name}`; // to avoid "__proto__".
47
47
 
48
48
  if (!stateMap[key]) {
@@ -82,7 +82,7 @@ module.exports = {
82
82
  }
83
83
 
84
84
  const state = getState(name, node.static);
85
- let isDuplicate = false;
85
+ let isDuplicate;
86
86
 
87
87
  if (kind === "get") {
88
88
  isDuplicate = (state.init || state.get);
@@ -270,7 +270,7 @@ module.exports = {
270
270
  function naiveHasReturn(node) {
271
271
  if (node.type === "BlockStatement") {
272
272
  const body = node.body,
273
- lastChildNode = body[body.length - 1];
273
+ lastChildNode = body.at(-1);
274
274
 
275
275
  return lastChildNode && checkForReturn(lastChildNode);
276
276
  }
@@ -40,7 +40,7 @@ const ALLOW_OPTIONS = Object.freeze([
40
40
  */
41
41
  function getKind(node) {
42
42
  const parent = node.parent;
43
- let kind = "";
43
+ let kind;
44
44
 
45
45
  if (node.type === "ArrowFunctionExpression") {
46
46
  return "arrowFunctions";
@@ -73,7 +73,7 @@ function getKind(node) {
73
73
  }
74
74
 
75
75
  // Detects prefix.
76
- let prefix = "";
76
+ let prefix;
77
77
 
78
78
  if (node.generator) {
79
79
  prefix = "generator";
@@ -15,7 +15,7 @@ module.exports = {
15
15
 
16
16
  docs: {
17
17
  description: "Disallow empty static blocks",
18
- recommended: false,
18
+ recommended: true,
19
19
  url: "https://eslint.org/docs/latest/rules/no-empty-static-block"
20
20
  },
21
21
 
@@ -10,7 +10,6 @@
10
10
  //------------------------------------------------------------------------------
11
11
 
12
12
  const astUtils = require("./utils/ast-utils");
13
- const globals = require("globals");
14
13
 
15
14
  //------------------------------------------------------------------------------
16
15
  // Rule Definition
@@ -54,7 +53,7 @@ module.exports = {
54
53
  const sourceCode = context.sourceCode;
55
54
  const exceptions = new Set(config.exceptions || []);
56
55
  const modifiedBuiltins = new Set(
57
- Object.keys(globals.builtin)
56
+ Object.keys(astUtils.ECMASCRIPT_GLOBALS)
58
57
  .filter(builtin => builtin[0].toUpperCase() === builtin[0])
59
58
  .filter(builtin => !exceptions.has(builtin))
60
59
  );
@@ -26,7 +26,7 @@ module.exports = {
26
26
 
27
27
  docs: {
28
28
  description: "Disallow unnecessary semicolons",
29
- recommended: true,
29
+ recommended: false,
30
30
  url: "https://eslint.org/docs/latest/rules/no-extra-semi"
31
31
  },
32
32
 
@@ -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[node.parent.cases.length - 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
  }