eslint 9.0.0-alpha.0 → 9.0.0-alpha.2

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 (89) hide show
  1. package/README.md +6 -1
  2. package/conf/ecma-version.js +16 -0
  3. package/lib/cli-engine/cli-engine.js +1 -1
  4. package/lib/cli-engine/lint-result-cache.js +2 -2
  5. package/lib/cli.js +14 -16
  6. package/lib/eslint/eslint.js +7 -0
  7. package/lib/linter/apply-disable-directives.js +2 -2
  8. package/lib/linter/code-path-analysis/code-path.js +32 -30
  9. package/lib/linter/code-path-analysis/fork-context.js +1 -1
  10. package/lib/linter/config-comment-parser.js +7 -10
  11. package/lib/linter/linter.js +105 -4
  12. package/lib/linter/report-translator.js +2 -2
  13. package/lib/linter/source-code-fixer.js +1 -1
  14. package/lib/rule-tester/rule-tester.js +45 -26
  15. package/lib/rules/array-bracket-newline.js +1 -1
  16. package/lib/rules/array-bracket-spacing.js +1 -1
  17. package/lib/rules/block-scoped-var.js +1 -1
  18. package/lib/rules/callback-return.js +2 -2
  19. package/lib/rules/comma-dangle.js +1 -1
  20. package/lib/rules/comma-style.js +2 -2
  21. package/lib/rules/complexity.js +1 -1
  22. package/lib/rules/constructor-super.js +1 -1
  23. package/lib/rules/default-case.js +1 -1
  24. package/lib/rules/eol-last.js +2 -2
  25. package/lib/rules/function-paren-newline.js +2 -2
  26. package/lib/rules/indent-legacy.js +5 -5
  27. package/lib/rules/indent.js +5 -5
  28. package/lib/rules/index.js +1 -0
  29. package/lib/rules/key-spacing.js +2 -2
  30. package/lib/rules/line-comment-position.js +1 -1
  31. package/lib/rules/lines-around-directive.js +2 -2
  32. package/lib/rules/max-depth.js +1 -1
  33. package/lib/rules/max-len.js +3 -3
  34. package/lib/rules/max-lines.js +3 -3
  35. package/lib/rules/max-nested-callbacks.js +1 -1
  36. package/lib/rules/max-params.js +1 -1
  37. package/lib/rules/max-statements.js +1 -1
  38. package/lib/rules/multiline-comment-style.js +7 -7
  39. package/lib/rules/new-cap.js +1 -1
  40. package/lib/rules/newline-after-var.js +1 -1
  41. package/lib/rules/newline-before-return.js +1 -1
  42. package/lib/rules/no-constant-binary-expression.js +5 -5
  43. package/lib/rules/no-constructor-return.js +1 -1
  44. package/lib/rules/no-dupe-class-members.js +2 -2
  45. package/lib/rules/no-else-return.js +1 -1
  46. package/lib/rules/no-empty-function.js +2 -2
  47. package/lib/rules/no-fallthrough.js +1 -1
  48. package/lib/rules/no-implicit-coercion.js +51 -25
  49. package/lib/rules/no-inner-declarations.js +22 -1
  50. package/lib/rules/no-invalid-this.js +1 -1
  51. package/lib/rules/no-lone-blocks.js +2 -2
  52. package/lib/rules/no-loss-of-precision.js +1 -1
  53. package/lib/rules/no-misleading-character-class.js +174 -65
  54. package/lib/rules/no-multiple-empty-lines.js +1 -1
  55. package/lib/rules/no-restricted-globals.js +1 -1
  56. package/lib/rules/no-restricted-imports.js +54 -44
  57. package/lib/rules/no-restricted-modules.js +2 -2
  58. package/lib/rules/no-return-await.js +1 -1
  59. package/lib/rules/no-this-before-super.js +17 -4
  60. package/lib/rules/no-trailing-spaces.js +2 -3
  61. package/lib/rules/no-unneeded-ternary.js +1 -1
  62. package/lib/rules/no-unsafe-optional-chaining.js +1 -1
  63. package/lib/rules/no-unused-vars.js +6 -8
  64. package/lib/rules/no-useless-assignment.js +566 -0
  65. package/lib/rules/no-useless-backreference.js +1 -1
  66. package/lib/rules/object-curly-spacing.js +3 -3
  67. package/lib/rules/object-property-newline.js +1 -1
  68. package/lib/rules/one-var.js +5 -5
  69. package/lib/rules/padded-blocks.js +7 -7
  70. package/lib/rules/prefer-arrow-callback.js +3 -3
  71. package/lib/rules/prefer-reflect.js +1 -1
  72. package/lib/rules/prefer-regex-literals.js +1 -1
  73. package/lib/rules/prefer-template.js +1 -1
  74. package/lib/rules/radix.js +2 -2
  75. package/lib/rules/semi-style.js +1 -1
  76. package/lib/rules/sort-imports.js +1 -1
  77. package/lib/rules/sort-keys.js +1 -1
  78. package/lib/rules/sort-vars.js +1 -1
  79. package/lib/rules/space-unary-ops.js +1 -1
  80. package/lib/rules/strict.js +1 -1
  81. package/lib/rules/utils/ast-utils.js +7 -7
  82. package/lib/rules/yield-star-spacing.js +1 -1
  83. package/lib/shared/serialization.js +55 -0
  84. package/lib/source-code/source-code.js +4 -4
  85. package/lib/source-code/token-store/index.js +2 -2
  86. package/package.json +7 -7
  87. package/conf/config-schema.js +0 -93
  88. package/lib/shared/config-validator.js +0 -380
  89. package/lib/shared/relative-module-resolver.js +0 -50
package/README.md CHANGED
@@ -117,7 +117,7 @@ Yes, ESLint natively supports parsing JSX syntax (this must be enabled in [confi
117
117
 
118
118
  ### What ECMAScript versions does ESLint support?
119
119
 
120
- ESLint has full support for ECMAScript 3, 5 (default), 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, and 2023. You can set your desired ECMAScript syntax (and other settings, like global variables or your target environments) through [configuration](https://eslint.org/docs/latest/use/configure).
120
+ ESLint has full support for ECMAScript 3, 5, and every year from 2015 up until the most recent stage 4 specification (the default). You can set your desired ECMAScript syntax and other settings (like global variables) through [configuration](https://eslint.org/docs/latest/use/configure).
121
121
 
122
122
  ### What about experimental features?
123
123
 
@@ -245,6 +245,11 @@ The people who review and fix bugs and help triage issues.
245
245
  Bryan Mishkin
246
246
  </a>
247
247
  </td><td align="center" valign="top" width="11%">
248
+ <a href="https://github.com/JoshuaKGoldberg">
249
+ <img src="https://github.com/JoshuaKGoldberg.png?s=75" width="75" height="75" alt="Josh Goldberg ✨'s Avatar"><br />
250
+ Josh Goldberg ✨
251
+ </a>
252
+ </td><td align="center" valign="top" width="11%">
248
253
  <a href="https://github.com/fasttime">
249
254
  <img src="https://github.com/fasttime.png?s=75" width="75" height="75" alt="Francesco Trotta's Avatar"><br />
250
255
  Francesco Trotta
@@ -0,0 +1,16 @@
1
+ /**
2
+ * @fileoverview Configuration related to ECMAScript versions
3
+ * @author Milos Djermanovic
4
+ */
5
+
6
+ "use strict";
7
+
8
+ /**
9
+ * The latest ECMAScript version supported by ESLint.
10
+ * @type {number} year-based ECMAScript version
11
+ */
12
+ const LATEST_ECMA_VERSION = 2024;
13
+
14
+ module.exports = {
15
+ LATEST_ECMA_VERSION
16
+ };
@@ -732,7 +732,7 @@ class CLIEngine {
732
732
  * @returns {void}
733
733
  */
734
734
  static outputFixes(report) {
735
- report.results.filter(result => Object.prototype.hasOwnProperty.call(result, "output")).forEach(result => {
735
+ report.results.filter(result => Object.hasOwn(result, "output")).forEach(result => {
736
736
  fs.writeFileSync(result.filePath, result.output);
737
737
  });
738
738
  }
@@ -164,7 +164,7 @@ class LintResultCache {
164
164
  * @returns {void}
165
165
  */
166
166
  setCachedLintResults(filePath, config, result) {
167
- if (result && Object.prototype.hasOwnProperty.call(result, "output")) {
167
+ if (result && Object.hasOwn(result, "output")) {
168
168
  return;
169
169
  }
170
170
 
@@ -181,7 +181,7 @@ class LintResultCache {
181
181
  * In `getCachedLintResults`, if source is explicitly null, we will
182
182
  * read the file from the filesystem to set the value again.
183
183
  */
184
- if (Object.prototype.hasOwnProperty.call(resultToSerialize, "source")) {
184
+ if (Object.hasOwn(resultToSerialize, "source")) {
185
185
  resultToSerialize.source = null;
186
186
  }
187
187
 
package/lib/cli.js CHANGED
@@ -285,25 +285,23 @@ async function printResults(engine, results, format, outputFile, resultsMeta) {
285
285
 
286
286
  const output = await formatter.format(results, resultsMeta);
287
287
 
288
- if (output) {
289
- if (outputFile) {
290
- const filePath = path.resolve(process.cwd(), outputFile);
288
+ if (outputFile) {
289
+ const filePath = path.resolve(process.cwd(), outputFile);
291
290
 
292
- if (await isDirectory(filePath)) {
293
- log.error("Cannot write to output file path, it is a directory: %s", outputFile);
294
- return false;
295
- }
291
+ if (await isDirectory(filePath)) {
292
+ log.error("Cannot write to output file path, it is a directory: %s", outputFile);
293
+ return false;
294
+ }
296
295
 
297
- try {
298
- await mkdir(path.dirname(filePath), { recursive: true });
299
- await writeFile(filePath, output);
300
- } catch (ex) {
301
- log.error("There was a problem writing the output file:\n%s", ex);
302
- return false;
303
- }
304
- } else {
305
- log.info(output);
296
+ try {
297
+ await mkdir(path.dirname(filePath), { recursive: true });
298
+ await writeFile(filePath, output);
299
+ } catch (ex) {
300
+ log.error("There was a problem writing the output file:\n%s", ex);
301
+ return false;
306
302
  }
303
+ } else if (output) {
304
+ log.info(output);
307
305
  }
308
306
 
309
307
  return true;
@@ -613,6 +613,13 @@ class ESLint {
613
613
  });
614
614
  }
615
615
 
616
+ // Check for the .eslintignore file, and warn if it's present.
617
+ if (existsSync(path.resolve(processedOptions.cwd, ".eslintignore"))) {
618
+ process.emitWarning(
619
+ "The \".eslintignore\" file is no longer supported. Switch to using the \"ignores\" property in \"eslint.config.js\": https://eslint.org/docs/latest/use/configure/migration-guide#ignoring-files",
620
+ "ESLintIgnoreWarning"
621
+ );
622
+ }
616
623
  }
617
624
 
618
625
  /**
@@ -171,7 +171,7 @@ function createCommentRemoval(directives, commentToken) {
171
171
  return {
172
172
  description: ruleIds.length <= 2
173
173
  ? ruleIds.join(" or ")
174
- : `${ruleIds.slice(0, ruleIds.length - 1).join(", ")}, or ${ruleIds[ruleIds.length - 1]}`,
174
+ : `${ruleIds.slice(0, ruleIds.length - 1).join(", ")}, or ${ruleIds.at(-1)}`,
175
175
  fix: {
176
176
  range,
177
177
  text: " "
@@ -342,7 +342,7 @@ function applyDirectives(options) {
342
342
  problem.suppressions = problem.suppressions.concat(suppressions);
343
343
  } else {
344
344
  problem.suppressions = suppressions;
345
- usedDisableDirectives.add(disableDirectivesForProblem[disableDirectivesForProblem.length - 1]);
345
+ usedDisableDirectives.add(disableDirectivesForProblem.at(-1));
346
346
  }
347
347
  }
348
348
 
@@ -125,20 +125,6 @@ class CodePath {
125
125
  return this.internal.thrownForkContext;
126
126
  }
127
127
 
128
- /**
129
- * Tracks the traversal of the code path through each segment. This array
130
- * starts empty and segments are added or removed as the code path is
131
- * traversed. This array always ends up empty at the end of a code path
132
- * traversal. The `CodePathState` uses this to track its progress through
133
- * the code path.
134
- * This is a passthrough to the underlying `CodePathState`.
135
- * @type {CodePathSegment[]}
136
- * @deprecated
137
- */
138
- get currentSegments() {
139
- return this.internal.currentSegments;
140
- }
141
-
142
128
  /**
143
129
  * Traverses all segments in this code path.
144
130
  *
@@ -180,9 +166,9 @@ class CodePath {
180
166
  const lastSegment = resolvedOptions.last;
181
167
 
182
168
  // set up initial location information
183
- let record = null;
184
- let index = 0;
185
- let end = 0;
169
+ let record;
170
+ let index;
171
+ let end;
186
172
  let segment = null;
187
173
 
188
174
  // segments that have already been visited during traversal
@@ -191,8 +177,8 @@ class CodePath {
191
177
  // tracks the traversal steps
192
178
  const stack = [[startSegment, 0]];
193
179
 
194
- // tracks the last skipped segment during traversal
195
- let skippedSegment = null;
180
+ // segments that have been skipped during traversal
181
+ const skipped = new Set();
196
182
 
197
183
  // indicates if we exited early from the traversal
198
184
  let broken = false;
@@ -207,11 +193,7 @@ class CodePath {
207
193
  * @returns {void}
208
194
  */
209
195
  skip() {
210
- if (stack.length <= 1) {
211
- broken = true;
212
- } else {
213
- skippedSegment = stack[stack.length - 2][0];
214
- }
196
+ skipped.add(segment);
215
197
  },
216
198
 
217
199
  /**
@@ -236,6 +218,18 @@ class CodePath {
236
218
  );
237
219
  }
238
220
 
221
+ /**
222
+ * Checks if a given previous segment has been skipped.
223
+ * @param {CodePathSegment} prevSegment A previous segment to check.
224
+ * @returns {boolean} `true` if the segment has been skipped.
225
+ */
226
+ function isSkipped(prevSegment) {
227
+ return (
228
+ skipped.has(prevSegment) ||
229
+ segment.isLoopedPrevSegment(prevSegment)
230
+ );
231
+ }
232
+
239
233
  // the traversal
240
234
  while (stack.length > 0) {
241
235
 
@@ -251,7 +245,7 @@ class CodePath {
251
245
  * Otherwise, we just read the value and sometimes modify the
252
246
  * record as we traverse.
253
247
  */
254
- record = stack[stack.length - 1];
248
+ record = stack.at(-1);
255
249
  segment = record[0];
256
250
  index = record[1];
257
251
 
@@ -272,17 +266,21 @@ class CodePath {
272
266
  continue;
273
267
  }
274
268
 
275
- // Reset the skipping flag if all branches have been skipped.
276
- if (skippedSegment && segment.prevSegments.includes(skippedSegment)) {
277
- skippedSegment = null;
278
- }
279
269
  visited.add(segment);
280
270
 
271
+
272
+ // Skips the segment if all previous segments have been skipped.
273
+ const shouldSkip = (
274
+ skipped.size > 0 &&
275
+ segment.prevSegments.length > 0 &&
276
+ segment.prevSegments.every(isSkipped)
277
+ );
278
+
281
279
  /*
282
280
  * If the most recent segment hasn't been skipped, then we call
283
281
  * the callback, passing in the segment and the controller.
284
282
  */
285
- if (!skippedSegment) {
283
+ if (!shouldSkip) {
286
284
  resolvedCallback.call(this, segment, controller);
287
285
 
288
286
  // exit if we're at the last segment
@@ -298,6 +296,10 @@ class CodePath {
298
296
  if (broken) {
299
297
  break;
300
298
  }
299
+ } else {
300
+
301
+ // If the most recent segment has been skipped, then mark it as skipped.
302
+ skipped.add(segment);
301
303
  }
302
304
  }
303
305
 
@@ -207,7 +207,7 @@ class ForkContext {
207
207
  get head() {
208
208
  const list = this.segmentsList;
209
209
 
210
- return list.length === 0 ? [] : list[list.length - 1];
210
+ return list.length === 0 ? [] : list.at(-1);
211
211
  }
212
212
 
213
213
  /**
@@ -75,11 +75,9 @@ module.exports = class ConfigCommentParser {
75
75
  parseJsonConfig(string, location) {
76
76
  debug("Parsing JSON config");
77
77
 
78
- let items = {};
79
-
80
78
  // Parses a JSON-like comment by the same way as parsing CLI option.
81
79
  try {
82
- items = levn.parse("Object", string) || {};
80
+ const items = levn.parse("Object", string) || {};
83
81
 
84
82
  // Some tests say that it should ignore invalid comments such as `/*eslint no-alert:abc*/`.
85
83
  // Also, commaless notations have invalid severity:
@@ -102,11 +100,15 @@ module.exports = class ConfigCommentParser {
102
100
  * Optionator cannot parse commaless notations.
103
101
  * But we are supporting that. So this is a fallback for that.
104
102
  */
105
- items = {};
106
103
  const normalizedString = string.replace(/([-a-zA-Z0-9/]+):/gu, "\"$1\":").replace(/(\]|[0-9])\s+(?=")/u, "$1,");
107
104
 
108
105
  try {
109
- items = JSON.parse(`{${normalizedString}}`);
106
+ const items = JSON.parse(`{${normalizedString}}`);
107
+
108
+ return {
109
+ success: true,
110
+ config: items
111
+ };
110
112
  } catch (ex) {
111
113
  debug("Manual parsing failed.");
112
114
 
@@ -124,11 +126,6 @@ module.exports = class ConfigCommentParser {
124
126
  };
125
127
 
126
128
  }
127
-
128
- return {
129
- success: true,
130
- config: items
131
- };
132
129
  }
133
130
 
134
131
  /**
@@ -52,6 +52,7 @@ const DEFAULT_ECMA_VERSION = 5;
52
52
  const commentParser = new ConfigCommentParser();
53
53
  const DEFAULT_ERROR_LOC = { start: { line: 1, column: 0 }, end: { line: 1, column: 1 } };
54
54
  const parserSymbol = Symbol.for("eslint.RuleTester.parser");
55
+ const { LATEST_ECMA_VERSION } = require("../../conf/ecma-version");
55
56
 
56
57
  //------------------------------------------------------------------------------
57
58
  // Typedefs
@@ -231,7 +232,7 @@ function addDeclaredGlobals(globalScope, configGlobals, { exportedVariables, ena
231
232
  * @private
232
233
  */
233
234
  function createMissingRuleMessage(ruleId) {
234
- return Object.prototype.hasOwnProperty.call(ruleReplacements.rules, ruleId)
235
+ return Object.hasOwn(ruleReplacements.rules, ruleId)
235
236
  ? `Rule '${ruleId}' was removed and replaced by: ${ruleReplacements.rules[ruleId].join(", ")}`
236
237
  : `Definition for rule '${ruleId}' was not found.`;
237
238
  }
@@ -594,7 +595,7 @@ function normalizeEcmaVersionForLanguageOptions(ecmaVersion) {
594
595
  * that is used for a number of processes inside of ESLint. It's normally
595
596
  * safe to assume people want the latest unless otherwise specified.
596
597
  */
597
- return espree.latestEcmaVersion + 2009;
598
+ return LATEST_ECMA_VERSION;
598
599
  }
599
600
 
600
601
  const eslintEnvPattern = /\/\*\s*eslint-env\s(.+?)(?:\*\/|$)/gsu;
@@ -1331,7 +1332,56 @@ class Linter {
1331
1332
  { exportedVariables: commentDirectives.exportedVariables, enabledGlobals: commentDirectives.enabledGlobals }
1332
1333
  );
1333
1334
 
1335
+ /*
1336
+ * Now we determine the final configurations for rules.
1337
+ * First, let all inline rule configurations override those from the config.
1338
+ * Then, check for a special case: if a rule is configured in both places,
1339
+ * inline rule configuration that only has severity should retain options from
1340
+ * the config and just override the severity.
1341
+ *
1342
+ * Example:
1343
+ *
1344
+ * {
1345
+ * rules: {
1346
+ * curly: ["error", "multi"]
1347
+ * }
1348
+ * }
1349
+ *
1350
+ * /* eslint curly: ["warn"] * /
1351
+ *
1352
+ * Results in:
1353
+ *
1354
+ * curly: ["warn", "multi"]
1355
+ */
1334
1356
  const configuredRules = Object.assign({}, config.rules, commentDirectives.configuredRules);
1357
+
1358
+ if (config.rules) {
1359
+ for (const [ruleId, ruleInlineConfig] of Object.entries(commentDirectives.configuredRules)) {
1360
+ if (
1361
+
1362
+ /*
1363
+ * If inline config for the rule has only severity
1364
+ */
1365
+ (!Array.isArray(ruleInlineConfig) || ruleInlineConfig.length === 1) &&
1366
+
1367
+ /*
1368
+ * And provided config for the rule has options
1369
+ */
1370
+ Object.hasOwn(config.rules, ruleId) &&
1371
+ (Array.isArray(config.rules[ruleId]) && config.rules[ruleId].length > 1)
1372
+ ) {
1373
+
1374
+ /*
1375
+ * Then use severity from the inline config and options from the provided config
1376
+ */
1377
+ configuredRules[ruleId] = [
1378
+ Array.isArray(ruleInlineConfig) ? ruleInlineConfig[0] : ruleInlineConfig, // severity from the inline config
1379
+ ...config.rules[ruleId].slice(1) // options from the provided config
1380
+ ];
1381
+ }
1382
+ }
1383
+ }
1384
+
1335
1385
  let lintingProblems;
1336
1386
 
1337
1387
  try {
@@ -1674,7 +1724,7 @@ class Linter {
1674
1724
  [ruleId]: ruleOptions
1675
1725
  }
1676
1726
  });
1677
- mergedInlineConfig.rules[ruleId] = ruleValue;
1727
+ mergedInlineConfig.rules[ruleId] = ruleOptions;
1678
1728
  } catch (err) {
1679
1729
 
1680
1730
  /*
@@ -1713,7 +1763,58 @@ class Linter {
1713
1763
  )
1714
1764
  : { problems: [], disableDirectives: [] };
1715
1765
 
1766
+ /*
1767
+ * Now we determine the final configurations for rules.
1768
+ * First, let all inline rule configurations override those from the config.
1769
+ * Then, check for a special case: if a rule is configured in both places,
1770
+ * inline rule configuration that only has severity should retain options from
1771
+ * the config and just override the severity.
1772
+ *
1773
+ * Example:
1774
+ *
1775
+ * {
1776
+ * rules: {
1777
+ * curly: ["error", "multi"]
1778
+ * }
1779
+ * }
1780
+ *
1781
+ * /* eslint curly: ["warn"] * /
1782
+ *
1783
+ * Results in:
1784
+ *
1785
+ * curly: ["warn", "multi"]
1786
+ *
1787
+ * At this point, all rule configurations are normalized to arrays.
1788
+ */
1716
1789
  const configuredRules = Object.assign({}, config.rules, mergedInlineConfig.rules);
1790
+
1791
+ if (config.rules) {
1792
+ for (const [ruleId, ruleInlineConfig] of Object.entries(mergedInlineConfig.rules)) {
1793
+ if (
1794
+
1795
+ /*
1796
+ * If inline config for the rule has only severity
1797
+ */
1798
+ ruleInlineConfig.length === 1 &&
1799
+
1800
+ /*
1801
+ * And provided config for the rule has options
1802
+ */
1803
+ Object.hasOwn(config.rules, ruleId) &&
1804
+ config.rules[ruleId].length > 1
1805
+ ) {
1806
+
1807
+ /*
1808
+ * Then use severity from the inline config and options from the provided config
1809
+ */
1810
+ configuredRules[ruleId] = [
1811
+ ruleInlineConfig[0], // severity from the inline config
1812
+ ...config.rules[ruleId].slice(1) // options from the provided config
1813
+ ];
1814
+ }
1815
+ }
1816
+ }
1817
+
1717
1818
  let lintingProblems;
1718
1819
 
1719
1820
  sourceCode.finalize();
@@ -2039,7 +2140,7 @@ class Linter {
2039
2140
  * SourceCodeFixer.
2040
2141
  */
2041
2142
  verifyAndFix(text, config, options) {
2042
- let messages = [],
2143
+ let messages,
2043
2144
  fixedResult,
2044
2145
  fixed = false,
2045
2146
  passNumber = 0,
@@ -160,7 +160,7 @@ function mergeFixes(fixes, sourceCode) {
160
160
 
161
161
  const originalText = sourceCode.text;
162
162
  const start = fixes[0].range[0];
163
- const end = fixes[fixes.length - 1].range[1];
163
+ const end = fixes.at(-1).range[1];
164
164
  let text = "";
165
165
  let lastPos = Number.MIN_SAFE_INTEGER;
166
166
 
@@ -343,7 +343,7 @@ module.exports = function createReportTranslator(metadata) {
343
343
  if (descriptor.message) {
344
344
  throw new TypeError("context.report() called with a message and a messageId. Please only pass one.");
345
345
  }
346
- if (!messages || !Object.prototype.hasOwnProperty.call(messages, id)) {
346
+ if (!messages || !Object.hasOwn(messages, id)) {
347
347
  throw new TypeError(`context.report() called with a messageId of '${id}' which is not present in the 'messages' config: ${JSON.stringify(messages, null, 2)}`);
348
348
  }
349
349
  computedMessage = messages[id];
@@ -107,7 +107,7 @@ SourceCodeFixer.applyFixes = function(sourceText, messages, shouldFix) {
107
107
  }
108
108
 
109
109
  messages.forEach(problem => {
110
- if (Object.prototype.hasOwnProperty.call(problem, "fix")) {
110
+ if (Object.hasOwn(problem, "fix")) {
111
111
  fixes.push(problem);
112
112
  } else {
113
113
  remainingMessages.push(problem);
@@ -13,11 +13,12 @@
13
13
  const
14
14
  assert = require("assert"),
15
15
  util = require("util"),
16
+ path = require("path"),
16
17
  equal = require("fast-deep-equal"),
17
18
  Traverser = require("../shared/traverser"),
18
19
  { getRuleOptionsSchema } = require("../config/flat-config-helpers"),
19
20
  { Linter, SourceCodeFixer, interpolate } = require("../linter"),
20
- CodePath = require("../linter/code-path-analysis/code-path");
21
+ stringify = require("json-stable-stringify-without-jsonify");
21
22
 
22
23
  const { FlatConfigArray } = require("../config/flat-config-array");
23
24
  const { defaultConfig } = require("../config/default-config");
@@ -27,6 +28,7 @@ const ajv = require("../shared/ajv")({ strictDefaults: true });
27
28
  const parserSymbol = Symbol.for("eslint.RuleTester.parser");
28
29
  const { SourceCode } = require("../source-code");
29
30
  const { ConfigArraySymbol } = require("@humanwhocodes/config-array");
31
+ const { isSerializable } = require("../shared/serialization");
30
32
 
31
33
  //------------------------------------------------------------------------------
32
34
  // Typedefs
@@ -274,21 +276,6 @@ function wrapParser(parser) {
274
276
  };
275
277
  }
276
278
 
277
- /**
278
- * Emit a deprecation warning if rule uses CodePath#currentSegments.
279
- * @param {string} ruleName Name of the rule.
280
- * @returns {void}
281
- */
282
- function emitCodePathCurrentSegmentsWarning(ruleName) {
283
- if (!emitCodePathCurrentSegmentsWarning[`warned-${ruleName}`]) {
284
- emitCodePathCurrentSegmentsWarning[`warned-${ruleName}`] = true;
285
- process.emitWarning(
286
- `"${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`,
287
- "DeprecationWarning"
288
- );
289
- }
290
- }
291
-
292
279
  /**
293
280
  * Function to replace forbidden `SourceCode` methods. Allows just one call per method.
294
281
  * @param {string} methodName The name of the method to forbid.
@@ -515,6 +502,9 @@ class RuleTester {
515
502
  linter = this.linter,
516
503
  ruleId = `rule-to-test/${ruleName}`;
517
504
 
505
+ const seenValidTestCases = new Set();
506
+ const seenInvalidTestCases = new Set();
507
+
518
508
  if (!rule || typeof rule !== "object" || typeof rule.create !== "function") {
519
509
  throw new TypeError("Rule must be an object with a `create` method");
520
510
  }
@@ -593,7 +583,15 @@ class RuleTester {
593
583
  * @private
594
584
  */
595
585
  function runRuleForItem(item) {
596
- const configs = new FlatConfigArray(testerConfig, { baseConfig });
586
+ const flatConfigArrayOptions = {
587
+ baseConfig
588
+ };
589
+
590
+ if (item.filename) {
591
+ flatConfigArrayOptions.basePath = path.parse(item.filename).root;
592
+ }
593
+
594
+ const configs = new FlatConfigArray(testerConfig, flatConfigArrayOptions);
597
595
 
598
596
  /*
599
597
  * Modify the returned config so that the parser is wrapped to catch
@@ -753,24 +751,15 @@ class RuleTester {
753
751
 
754
752
  // Verify the code.
755
753
  const { applyLanguageOptions, applyInlineConfig, finalize } = SourceCode.prototype;
756
- const originalCurrentSegments = Object.getOwnPropertyDescriptor(CodePath.prototype, "currentSegments");
757
754
  let messages;
758
755
 
759
756
  try {
760
- Object.defineProperty(CodePath.prototype, "currentSegments", {
761
- get() {
762
- emitCodePathCurrentSegmentsWarning(ruleName);
763
- return originalCurrentSegments.get.call(this);
764
- }
765
- });
766
-
767
757
  forbiddenMethods.forEach(methodName => {
768
758
  SourceCode.prototype[methodName] = throwForbiddenMethodError(methodName, SourceCode.prototype);
769
759
  });
770
760
 
771
761
  messages = linter.verify(code, configs, filename);
772
762
  } finally {
773
- Object.defineProperty(CodePath.prototype, "currentSegments", originalCurrentSegments);
774
763
  SourceCode.prototype.applyInlineConfig = applyInlineConfig;
775
764
  SourceCode.prototype.applyLanguageOptions = applyLanguageOptions;
776
765
  SourceCode.prototype.finalize = finalize;
@@ -819,6 +808,32 @@ class RuleTester {
819
808
  }
820
809
  }
821
810
 
811
+ /**
812
+ * Check if this test case is a duplicate of one we have seen before.
813
+ * @param {Object} item test case object
814
+ * @param {Set<string>} seenTestCases set of serialized test cases we have seen so far (managed by this function)
815
+ * @returns {void}
816
+ * @private
817
+ */
818
+ function checkDuplicateTestCase(item, seenTestCases) {
819
+ if (!isSerializable(item)) {
820
+
821
+ /*
822
+ * If we can't serialize a test case (because it contains a function, RegExp, etc), skip the check.
823
+ * This might happen with properties like: options, plugins, settings, languageOptions.parser, languageOptions.parserOptions.
824
+ */
825
+ return;
826
+ }
827
+
828
+ const serializedTestCase = stringify(item);
829
+
830
+ assert(
831
+ !seenTestCases.has(serializedTestCase),
832
+ "detected duplicate test case"
833
+ );
834
+ seenTestCases.add(serializedTestCase);
835
+ }
836
+
822
837
  /**
823
838
  * Check if the template is valid or not
824
839
  * all valid cases go through this
@@ -834,6 +849,8 @@ class RuleTester {
834
849
  assert.ok(typeof item.name === "string", "Optional test case property 'name' must be a string");
835
850
  }
836
851
 
852
+ checkDuplicateTestCase(item, seenValidTestCases);
853
+
837
854
  const result = runRuleForItem(item);
838
855
  const messages = result.messages;
839
856
 
@@ -885,6 +902,8 @@ class RuleTester {
885
902
  assert.fail("Invalid cases must have at least one error");
886
903
  }
887
904
 
905
+ checkDuplicateTestCase(item, seenInvalidTestCases);
906
+
888
907
  const ruleHasMetaMessages = hasOwnProperty(rule, "meta") && hasOwnProperty(rule.meta, "messages");
889
908
  const friendlyIDList = ruleHasMetaMessages ? `[${Object.keys(rule.meta.messages).map(key => `'${key}'`).join(", ")}]` : null;
890
909
 
@@ -74,7 +74,7 @@ module.exports = {
74
74
  function normalizeOptionValue(option) {
75
75
  let consistent = false;
76
76
  let multiline = false;
77
- let minItems = 0;
77
+ let minItems;
78
78
 
79
79
  if (option) {
80
80
  if (option === "consistent") {
@@ -199,7 +199,7 @@ module.exports = {
199
199
  : sourceCode.getLastToken(node),
200
200
  penultimate = sourceCode.getTokenBefore(last),
201
201
  firstElement = node.elements[0],
202
- lastElement = node.elements[node.elements.length - 1];
202
+ lastElement = node.elements.at(-1);
203
203
 
204
204
  const openingBracketMustBeSpaced =
205
205
  options.objectsInArraysException && isObjectType(firstElement) ||