eslint 9.0.0-alpha.2 → 9.0.0-beta.1

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.
package/README.md CHANGED
@@ -103,7 +103,7 @@ We are now at or near 100% compatibility with JSCS. If you try ESLint and believ
103
103
 
104
104
  ### Does Prettier replace ESLint?
105
105
 
106
- No, ESLint and Prettier have diffent jobs: ESLint is a linter (looking for problematic patterns) and Prettier is a code formatter. Using both tools is common, refer to [Prettier's documentation](https://prettier.io/docs/en/install#eslint-and-other-linters) to learn how to configure them to work well with each other.
106
+ No, ESLint and Prettier have different jobs: ESLint is a linter (looking for problematic patterns) and Prettier is a code formatter. Using both tools is common, refer to [Prettier's documentation](https://prettier.io/docs/en/install#eslint-and-other-linters) to learn how to configure them to work well with each other.
107
107
 
108
108
  ### Why can't ESLint find my plugins?
109
109
 
@@ -255,11 +255,6 @@ Josh Goldberg ✨
255
255
  Francesco Trotta
256
256
  </a>
257
257
  </td><td align="center" valign="top" width="11%">
258
- <a href="https://github.com/ota-meshi">
259
- <img src="https://github.com/ota-meshi.png?s=75" width="75" height="75" alt="Yosuke Ota's Avatar"><br />
260
- Yosuke Ota
261
- </a>
262
- </td><td align="center" valign="top" width="11%">
263
258
  <a href="https://github.com/Tanujkanti4441">
264
259
  <img src="https://github.com/Tanujkanti4441.png?s=75" width="75" height="75" alt="Tanuj Kanti's Avatar"><br />
265
260
  Tanuj Kanti
@@ -299,7 +294,7 @@ The following companies, organizations, and individuals support ESLint's ongoing
299
294
  <p><a href="#"><img src="https://images.opencollective.com/2021-frameworks-fund/logo.png" alt="Chrome Frameworks Fund" height="undefined"></a> <a href="https://automattic.com"><img src="https://images.opencollective.com/automattic/d0ef3e1/logo.png" alt="Automattic" height="undefined"></a></p><h3>Gold Sponsors</h3>
300
295
  <p><a href="https://engineering.salesforce.com"><img src="https://images.opencollective.com/salesforce/ca8f997/logo.png" alt="Salesforce" height="96"></a> <a href="https://www.airbnb.com/"><img src="https://images.opencollective.com/airbnb/d327d66/logo.png" alt="Airbnb" height="96"></a></p><h3>Silver Sponsors</h3>
301
296
  <p><a href="https://www.jetbrains.com/"><img src="https://images.opencollective.com/jetbrains/eb04ddc/logo.png" alt="JetBrains" height="64"></a> <a href="https://liftoff.io/"><img src="https://images.opencollective.com/liftoff/5c4fa84/logo.png" alt="Liftoff" height="64"></a> <a href="https://americanexpress.io"><img src="https://avatars.githubusercontent.com/u/3853301?v=4" alt="American Express" height="64"></a> <a href="https://www.workleap.com"><img src="https://avatars.githubusercontent.com/u/53535748?u=d1e55d7661d724bf2281c1bfd33cb8f99fe2465f&v=4" alt="Workleap" height="64"></a></p><h3>Bronze Sponsors</h3>
302
- <p><a href="https://themeisle.com"><img src="https://images.opencollective.com/themeisle/d5592fe/logo.png" alt="ThemeIsle" height="32"></a> <a href="https://www.crosswordsolver.org/anagram-solver/"><img src="https://images.opencollective.com/anagram-solver/2666271/logo.png" alt="Anagram Solver" height="32"></a> <a href="https://icons8.com/"><img src="https://images.opencollective.com/icons8/7fa1641/logo.png" alt="Icons8" height="32"></a> <a href="https://discord.com"><img src="https://images.opencollective.com/discordapp/f9645d9/logo.png" alt="Discord" height="32"></a> <a href="https://transloadit.com/"><img src="https://avatars.githubusercontent.com/u/125754?v=4" alt="Transloadit" height="32"></a> <a href="https://www.ignitionapp.com"><img src="https://avatars.githubusercontent.com/u/5753491?v=4" alt="Ignition" height="32"></a> <a href="https://nx.dev"><img src="https://avatars.githubusercontent.com/u/23692104?v=4" alt="Nx" height="32"></a> <a href="https://herocoders.com"><img src="https://avatars.githubusercontent.com/u/37549774?v=4" alt="HeroCoders" height="32"></a></p>
297
+ <p><a href="https://www.notion.so"><img src="https://images.opencollective.com/notion/bf3b117/logo.png" alt="notion" height="32"></a> <a href="https://themeisle.com"><img src="https://images.opencollective.com/themeisle/d5592fe/logo.png" alt="ThemeIsle" height="32"></a> <a href="https://www.crosswordsolver.org/anagram-solver/"><img src="https://images.opencollective.com/anagram-solver/2666271/logo.png" alt="Anagram Solver" height="32"></a> <a href="https://icons8.com/"><img src="https://images.opencollective.com/icons8/7fa1641/logo.png" alt="Icons8" height="32"></a> <a href="https://discord.com"><img src="https://images.opencollective.com/discordapp/f9645d9/logo.png" alt="Discord" height="32"></a> <a href="https://transloadit.com/"><img src="https://avatars.githubusercontent.com/u/125754?v=4" alt="Transloadit" height="32"></a> <a href="https://www.ignitionapp.com"><img src="https://avatars.githubusercontent.com/u/5753491?v=4" alt="Ignition" height="32"></a> <a href="https://nx.dev"><img src="https://avatars.githubusercontent.com/u/23692104?v=4" alt="Nx" height="32"></a> <a href="https://herocoders.com"><img src="https://avatars.githubusercontent.com/u/37549774?v=4" alt="HeroCoders" height="32"></a> <a href="https://usenextbase.com"><img src="https://avatars.githubusercontent.com/u/145838380?v=4" alt="Nextbase Starter Kit" height="32"></a></p>
303
298
  <!--sponsorsend-->
304
299
 
305
300
  ## Technology Sponsors
package/lib/api.js CHANGED
@@ -9,17 +9,41 @@
9
9
  // Requirements
10
10
  //-----------------------------------------------------------------------------
11
11
 
12
- const { ESLint } = require("./eslint/eslint");
12
+ const { ESLint, shouldUseFlatConfig } = require("./eslint/eslint");
13
+ const { LegacyESLint } = require("./eslint/legacy-eslint");
13
14
  const { Linter } = require("./linter");
14
15
  const { RuleTester } = require("./rule-tester");
15
16
  const { SourceCode } = require("./source-code");
16
17
 
18
+ //-----------------------------------------------------------------------------
19
+ // Functions
20
+ //-----------------------------------------------------------------------------
21
+
22
+ /**
23
+ * Loads the correct ESLint constructor given the options.
24
+ * @param {Object} [options] The options object
25
+ * @param {boolean} [options.useFlatConfig] Whether or not to use a flat config
26
+ * @returns {Promise<ESLint|LegacyESLint>} The ESLint constructor
27
+ */
28
+ async function loadESLint({ useFlatConfig } = {}) {
29
+
30
+ /*
31
+ * Note: The v8.x version of this function also accepted a `cwd` option, but
32
+ * it is not used in this implementation so we silently ignore it.
33
+ */
34
+
35
+ const shouldESLintUseFlatConfig = useFlatConfig ?? (await shouldUseFlatConfig());
36
+
37
+ return shouldESLintUseFlatConfig ? ESLint : LegacyESLint;
38
+ }
39
+
17
40
  //-----------------------------------------------------------------------------
18
41
  // Exports
19
42
  //-----------------------------------------------------------------------------
20
43
 
21
44
  module.exports = {
22
45
  Linter,
46
+ loadESLint,
23
47
  ESLint,
24
48
  RuleTester,
25
49
  SourceCode
@@ -565,6 +565,12 @@ function createExtraneousResultsError() {
565
565
  */
566
566
  class ESLint {
567
567
 
568
+ /**
569
+ * The type of configuration used by this class.
570
+ * @type {string}
571
+ */
572
+ static configType = "flat";
573
+
568
574
  /**
569
575
  * Creates a new instance of the main ESLint API.
570
576
  * @param {ESLintOptions} options The options for this instance.
@@ -438,6 +438,12 @@ function compareResultsByFilePath(a, b) {
438
438
  */
439
439
  class LegacyESLint {
440
440
 
441
+ /**
442
+ * The type of configuration used by this class.
443
+ * @type {string}
444
+ */
445
+ static configType = "eslintrc";
446
+
441
447
  /**
442
448
  * Creates a new instance of the main ESLint API.
443
449
  * @param {LegacyESLintOptions} options The options for this instance.
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
 
3
3
  const { Linter } = require("./linter");
4
- const interpolate = require("./interpolate");
4
+ const { interpolate } = require("./interpolate");
5
5
  const SourceCodeFixer = require("./source-code-fixer");
6
6
 
7
7
  module.exports = {
@@ -9,13 +9,30 @@
9
9
  // Public Interface
10
10
  //------------------------------------------------------------------------------
11
11
 
12
- module.exports = (text, data) => {
12
+ /**
13
+ * Returns a global expression matching placeholders in messages.
14
+ * @returns {RegExp} Global regular expression matching placeholders
15
+ */
16
+ function getPlaceholderMatcher() {
17
+ return /\{\{([^{}]+?)\}\}/gu;
18
+ }
19
+
20
+ /**
21
+ * Replaces {{ placeholders }} in the message with the provided data.
22
+ * Does not replace placeholders not available in the data.
23
+ * @param {string} text Original message with potential placeholders
24
+ * @param {Record<string, string>} data Map of placeholder name to its value
25
+ * @returns {string} Message with replaced placeholders
26
+ */
27
+ function interpolate(text, data) {
13
28
  if (!data) {
14
29
  return text;
15
30
  }
16
31
 
32
+ const matcher = getPlaceholderMatcher();
33
+
17
34
  // Substitution content for any {{ }} markers.
18
- return text.replace(/\{\{([^{}]+?)\}\}/gu, (fullMatch, termWithWhitespace) => {
35
+ return text.replace(matcher, (fullMatch, termWithWhitespace) => {
19
36
  const term = termWithWhitespace.trim();
20
37
 
21
38
  if (term in data) {
@@ -25,4 +42,9 @@ module.exports = (text, data) => {
25
42
  // Preserve old behavior: If parameter name not provided, don't replace it.
26
43
  return fullMatch;
27
44
  });
45
+ }
46
+
47
+ module.exports = {
48
+ getPlaceholderMatcher,
49
+ interpolate
28
50
  };
@@ -43,7 +43,7 @@ const
43
43
  const { getRuleFromConfig } = require("../config/flat-config-helpers");
44
44
  const { FlatConfigArray } = require("../config/flat-config-array");
45
45
  const { RuleValidator } = require("../config/rule-validator");
46
- const { assertIsRuleOptions, assertIsRuleSeverity } = require("../config/flat-config-schema");
46
+ const { assertIsRuleSeverity } = require("../config/flat-config-schema");
47
47
  const { normalizeSeverityToString } = require("../shared/severity");
48
48
  const debug = require("debug")("eslint:linter");
49
49
  const MAX_AUTOFIX_PASSES = 10;
@@ -326,10 +326,11 @@ function createDisableDirectives(options) {
326
326
  * @param {SourceCode} sourceCode The SourceCode object to get comments from.
327
327
  * @param {function(string): {create: Function}} ruleMapper A map from rule IDs to defined rules
328
328
  * @param {string|null} warnInlineConfig If a string then it should warn directive comments as disabled. The string value is the config name what the setting came from.
329
+ * @param {ConfigData} config Provided config.
329
330
  * @returns {{configuredRules: Object, enabledGlobals: {value:string,comment:Token}[], exportedVariables: Object, problems: LintMessage[], disableDirectives: DisableDirective[]}}
330
331
  * A collection of the directive comments that were found, along with any problems that occurred when parsing
331
332
  */
332
- function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig) {
333
+ function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig, config) {
333
334
  const configuredRules = {};
334
335
  const enabledGlobals = Object.create(null);
335
336
  const exportedVariables = {};
@@ -438,8 +439,50 @@ function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig) {
438
439
  return;
439
440
  }
440
441
 
442
+ let ruleOptions = Array.isArray(ruleValue) ? ruleValue : [ruleValue];
443
+
444
+ /*
445
+ * If the rule was already configured, inline rule configuration that
446
+ * only has severity should retain options from the config and just override the severity.
447
+ *
448
+ * Example:
449
+ *
450
+ * {
451
+ * rules: {
452
+ * curly: ["error", "multi"]
453
+ * }
454
+ * }
455
+ *
456
+ * /* eslint curly: ["warn"] * /
457
+ *
458
+ * Results in:
459
+ *
460
+ * curly: ["warn", "multi"]
461
+ */
462
+ if (
463
+
464
+ /*
465
+ * If inline config for the rule has only severity
466
+ */
467
+ ruleOptions.length === 1 &&
468
+
469
+ /*
470
+ * And the rule was already configured
471
+ */
472
+ config.rules && Object.hasOwn(config.rules, name)
473
+ ) {
474
+
475
+ /*
476
+ * Then use severity from the inline config and options from the provided config
477
+ */
478
+ ruleOptions = [
479
+ ruleOptions[0], // severity from the inline config
480
+ ...Array.isArray(config.rules[name]) ? config.rules[name].slice(1) : [] // options from the provided config
481
+ ];
482
+ }
483
+
441
484
  try {
442
- validator.validateRuleOptions(rule, name, ruleValue);
485
+ validator.validateRuleOptions(rule, name, ruleOptions);
443
486
  } catch (err) {
444
487
 
445
488
  /*
@@ -460,7 +503,7 @@ function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig) {
460
503
  return;
461
504
  }
462
505
 
463
- configuredRules[name] = ruleValue;
506
+ configuredRules[name] = ruleOptions;
464
507
  });
465
508
  } else {
466
509
  problems.push(parseResult.error);
@@ -1322,7 +1365,7 @@ class Linter {
1322
1365
 
1323
1366
  const sourceCode = slots.lastSourceCode;
1324
1367
  const commentDirectives = options.allowInlineConfig
1325
- ? getDirectiveComments(sourceCode, ruleId => getRule(slots, ruleId), options.warnInlineConfig)
1368
+ ? getDirectiveComments(sourceCode, ruleId => getRule(slots, ruleId), options.warnInlineConfig, config)
1326
1369
  : { configuredRules: {}, enabledGlobals: {}, exportedVariables: {}, problems: [], disableDirectives: [] };
1327
1370
 
1328
1371
  // augment global scope with declared global variables
@@ -1332,56 +1375,8 @@ class Linter {
1332
1375
  { exportedVariables: commentDirectives.exportedVariables, enabledGlobals: commentDirectives.enabledGlobals }
1333
1376
  );
1334
1377
 
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
- */
1356
1378
  const configuredRules = Object.assign({}, config.rules, commentDirectives.configuredRules);
1357
1379
 
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
-
1385
1380
  let lintingProblems;
1386
1381
 
1387
1382
  try {
@@ -1713,17 +1708,67 @@ class Linter {
1713
1708
 
1714
1709
  try {
1715
1710
 
1716
- const ruleOptions = Array.isArray(ruleValue) ? ruleValue : [ruleValue];
1711
+ let ruleOptions = Array.isArray(ruleValue) ? ruleValue : [ruleValue];
1717
1712
 
1718
- assertIsRuleOptions(ruleId, ruleValue);
1719
1713
  assertIsRuleSeverity(ruleId, ruleOptions[0]);
1720
1714
 
1721
- ruleValidator.validate({
1722
- plugins: config.plugins,
1723
- rules: {
1724
- [ruleId]: ruleOptions
1715
+ /*
1716
+ * If the rule was already configured, inline rule configuration that
1717
+ * only has severity should retain options from the config and just override the severity.
1718
+ *
1719
+ * Example:
1720
+ *
1721
+ * {
1722
+ * rules: {
1723
+ * curly: ["error", "multi"]
1724
+ * }
1725
+ * }
1726
+ *
1727
+ * /* eslint curly: ["warn"] * /
1728
+ *
1729
+ * Results in:
1730
+ *
1731
+ * curly: ["warn", "multi"]
1732
+ */
1733
+
1734
+ let shouldValidateOptions = true;
1735
+
1736
+ if (
1737
+
1738
+ /*
1739
+ * If inline config for the rule has only severity
1740
+ */
1741
+ ruleOptions.length === 1 &&
1742
+
1743
+ /*
1744
+ * And the rule was already configured
1745
+ */
1746
+ config.rules && Object.hasOwn(config.rules, ruleId)
1747
+ ) {
1748
+
1749
+ /*
1750
+ * Then use severity from the inline config and options from the provided config
1751
+ */
1752
+ ruleOptions = [
1753
+ ruleOptions[0], // severity from the inline config
1754
+ ...config.rules[ruleId].slice(1) // options from the provided config
1755
+ ];
1756
+
1757
+ // if the rule was enabled, the options have already been validated
1758
+ if (config.rules[ruleId][0] > 0) {
1759
+ shouldValidateOptions = false;
1725
1760
  }
1726
- });
1761
+ }
1762
+
1763
+ if (shouldValidateOptions) {
1764
+ ruleValidator.validate({
1765
+ plugins: config.plugins,
1766
+ rules: {
1767
+ [ruleId]: ruleOptions
1768
+ }
1769
+ });
1770
+ }
1771
+
1727
1772
  mergedInlineConfig.rules[ruleId] = ruleOptions;
1728
1773
  } catch (err) {
1729
1774
 
@@ -1763,58 +1808,8 @@ class Linter {
1763
1808
  )
1764
1809
  : { problems: [], disableDirectives: [] };
1765
1810
 
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
- */
1789
1811
  const configuredRules = Object.assign({}, config.rules, mergedInlineConfig.rules);
1790
1812
 
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
-
1818
1813
  let lintingProblems;
1819
1814
 
1820
1815
  sourceCode.finalize();
@@ -11,7 +11,7 @@
11
11
 
12
12
  const assert = require("assert");
13
13
  const ruleFixer = require("./rule-fixer");
14
- const interpolate = require("./interpolate");
14
+ const { interpolate } = require("./interpolate");
15
15
 
16
16
  //------------------------------------------------------------------------------
17
17
  // Typedefs
@@ -17,7 +17,8 @@ const
17
17
  equal = require("fast-deep-equal"),
18
18
  Traverser = require("../shared/traverser"),
19
19
  { getRuleOptionsSchema } = require("../config/flat-config-helpers"),
20
- { Linter, SourceCodeFixer, interpolate } = require("../linter"),
20
+ { Linter, SourceCodeFixer } = require("../linter"),
21
+ { interpolate, getPlaceholderMatcher } = require("../linter/interpolate"),
21
22
  stringify = require("json-stable-stringify-without-jsonify");
22
23
 
23
24
  const { FlatConfigArray } = require("../config/flat-config-array");
@@ -304,6 +305,39 @@ function throwForbiddenMethodError(methodName, prototype) {
304
305
  };
305
306
  }
306
307
 
308
+ /**
309
+ * Extracts names of {{ placeholders }} from the reported message.
310
+ * @param {string} message Reported message
311
+ * @returns {string[]} Array of placeholder names
312
+ */
313
+ function getMessagePlaceholders(message) {
314
+ const matcher = getPlaceholderMatcher();
315
+
316
+ return Array.from(message.matchAll(matcher), ([, name]) => name.trim());
317
+ }
318
+
319
+ /**
320
+ * Returns the placeholders in the reported messages but
321
+ * only includes the placeholders available in the raw message and not in the provided data.
322
+ * @param {string} message The reported message
323
+ * @param {string} raw The raw message specified in the rule meta.messages
324
+ * @param {undefined|Record<unknown, unknown>} data The passed
325
+ * @returns {string[]} Missing placeholder names
326
+ */
327
+ function getUnsubstitutedMessagePlaceholders(message, raw, data = {}) {
328
+ const unsubstituted = getMessagePlaceholders(message);
329
+
330
+ if (unsubstituted.length === 0) {
331
+ return [];
332
+ }
333
+
334
+ // Remove false positives by only counting placeholders in the raw message, which were not provided in the data matcher or added with a data property
335
+ const known = getMessagePlaceholders(raw);
336
+ const provided = Object.keys(data);
337
+
338
+ return unsubstituted.filter(name => known.includes(name) && !provided.includes(name));
339
+ }
340
+
307
341
  const metaSchemaDescription = `
308
342
  \t- If the rule has options, set \`meta.schema\` to an array or non-empty object to enable options validation.
309
343
  \t- If the rule doesn't have options, omit \`meta.schema\` to enforce that no options can be passed to the rule.
@@ -645,7 +679,11 @@ class RuleTester {
645
679
  configs.push(itemConfig);
646
680
  }
647
681
 
648
- if (item.filename) {
682
+ if (hasOwnProperty(item, "only")) {
683
+ assert.ok(typeof item.only === "boolean", "Optional test case property 'only' must be a boolean");
684
+ }
685
+ if (hasOwnProperty(item, "filename")) {
686
+ assert.ok(typeof item.filename === "string", "Optional test case property 'filename' must be a string");
649
687
  filename = item.filename;
650
688
  }
651
689
 
@@ -960,6 +998,7 @@ class RuleTester {
960
998
 
961
999
  // Just an error message.
962
1000
  assertMessageMatches(message.message, error);
1001
+ assert.ok(message.suggestions === void 0, `Error at index ${i} has suggestions. Please convert the test error into an object and specify 'suggestions' property on it to test suggestions.`);
963
1002
  } else if (typeof error === "object" && error !== null) {
964
1003
 
965
1004
  /*
@@ -992,6 +1031,18 @@ class RuleTester {
992
1031
  error.messageId,
993
1032
  `messageId '${message.messageId}' does not match expected messageId '${error.messageId}'.`
994
1033
  );
1034
+
1035
+ const unsubstitutedPlaceholders = getUnsubstitutedMessagePlaceholders(
1036
+ message.message,
1037
+ rule.meta.messages[message.messageId],
1038
+ error.data
1039
+ );
1040
+
1041
+ assert.ok(
1042
+ unsubstitutedPlaceholders.length === 0,
1043
+ `The reported message has ${unsubstitutedPlaceholders.length > 1 ? `unsubstituted placeholders: ${unsubstitutedPlaceholders.map(name => `'${name}'`).join(", ")}` : `an unsubstituted placeholder '${unsubstitutedPlaceholders[0]}'`}. Please provide the missing ${unsubstitutedPlaceholders.length > 1 ? "values" : "value"} via the 'data' property in the context.report() call.`
1044
+ );
1045
+
995
1046
  if (hasOwnProperty(error, "data")) {
996
1047
 
997
1048
  /*
@@ -1008,13 +1059,10 @@ class RuleTester {
1008
1059
  `Hydrated message "${rehydratedMessage}" does not match "${message.message}"`
1009
1060
  );
1010
1061
  }
1062
+ } else {
1063
+ assert.fail("Test error must specify either a 'messageId' or 'message'.");
1011
1064
  }
1012
1065
 
1013
- assert.ok(
1014
- hasOwnProperty(error, "data") ? hasOwnProperty(error, "messageId") : true,
1015
- "Error must specify 'messageId' if 'data' is used."
1016
- );
1017
-
1018
1066
  if (error.type) {
1019
1067
  assert.strictEqual(message.nodeType, error.type, `Error type should be ${error.type}, found ${message.nodeType}`);
1020
1068
  }
@@ -1035,81 +1083,103 @@ class RuleTester {
1035
1083
  assert.strictEqual(message.endColumn, error.endColumn, `Error endColumn should be ${error.endColumn}`);
1036
1084
  }
1037
1085
 
1086
+ assert.ok(!message.suggestions || hasOwnProperty(error, "suggestions"), `Error at index ${i} has suggestions. Please specify 'suggestions' property on the test error object.`);
1038
1087
  if (hasOwnProperty(error, "suggestions")) {
1039
1088
 
1040
1089
  // Support asserting there are no suggestions
1041
- if (!error.suggestions || (Array.isArray(error.suggestions) && error.suggestions.length === 0)) {
1042
- if (Array.isArray(message.suggestions) && message.suggestions.length > 0) {
1043
- assert.fail(`Error should have no suggestions on error with message: "${message.message}"`);
1044
- }
1045
- } else {
1046
- assert.strictEqual(Array.isArray(message.suggestions), true, `Error should have an array of suggestions. Instead received "${message.suggestions}" on error with message: "${message.message}"`);
1047
- assert.strictEqual(message.suggestions.length, error.suggestions.length, `Error should have ${error.suggestions.length} suggestions. Instead found ${message.suggestions.length} suggestions`);
1048
-
1049
- error.suggestions.forEach((expectedSuggestion, index) => {
1050
- assert.ok(
1051
- typeof expectedSuggestion === "object" && expectedSuggestion !== null,
1052
- "Test suggestion in 'suggestions' array must be an object."
1053
- );
1054
- Object.keys(expectedSuggestion).forEach(propertyName => {
1055
- assert.ok(
1056
- suggestionObjectParameters.has(propertyName),
1057
- `Invalid suggestion property name '${propertyName}'. Expected one of ${friendlySuggestionObjectParameterList}.`
1058
- );
1059
- });
1060
-
1061
- const actualSuggestion = message.suggestions[index];
1062
- const suggestionPrefix = `Error Suggestion at index ${index} :`;
1063
-
1064
- if (hasOwnProperty(expectedSuggestion, "desc")) {
1090
+ const expectsSuggestions = Array.isArray(error.suggestions) ? error.suggestions.length > 0 : Boolean(error.suggestions);
1091
+ const hasSuggestions = message.suggestions !== void 0;
1092
+
1093
+ if (!hasSuggestions && expectsSuggestions) {
1094
+ assert.ok(!error.suggestions, `Error should have suggestions on error with message: "${message.message}"`);
1095
+ } else if (hasSuggestions) {
1096
+ assert.ok(expectsSuggestions, `Error should have no suggestions on error with message: "${message.message}"`);
1097
+ if (typeof error.suggestions === "number") {
1098
+ assert.strictEqual(message.suggestions.length, error.suggestions, `Error should have ${error.suggestions} suggestions. Instead found ${message.suggestions.length} suggestions`);
1099
+ } else if (Array.isArray(error.suggestions)) {
1100
+ assert.strictEqual(message.suggestions.length, error.suggestions.length, `Error should have ${error.suggestions.length} suggestions. Instead found ${message.suggestions.length} suggestions`);
1101
+
1102
+ error.suggestions.forEach((expectedSuggestion, index) => {
1065
1103
  assert.ok(
1066
- !hasOwnProperty(expectedSuggestion, "data"),
1067
- `${suggestionPrefix} Test should not specify both 'desc' and 'data'.`
1104
+ typeof expectedSuggestion === "object" && expectedSuggestion !== null,
1105
+ "Test suggestion in 'suggestions' array must be an object."
1068
1106
  );
1069
- assert.strictEqual(
1070
- actualSuggestion.desc,
1071
- expectedSuggestion.desc,
1072
- `${suggestionPrefix} desc should be "${expectedSuggestion.desc}" but got "${actualSuggestion.desc}" instead.`
1073
- );
1074
- }
1107
+ Object.keys(expectedSuggestion).forEach(propertyName => {
1108
+ assert.ok(
1109
+ suggestionObjectParameters.has(propertyName),
1110
+ `Invalid suggestion property name '${propertyName}'. Expected one of ${friendlySuggestionObjectParameterList}.`
1111
+ );
1112
+ });
1075
1113
 
1076
- if (hasOwnProperty(expectedSuggestion, "messageId")) {
1077
- assert.ok(
1078
- ruleHasMetaMessages,
1079
- `${suggestionPrefix} Test can not use 'messageId' if rule under test doesn't define 'meta.messages'.`
1080
- );
1081
- assert.ok(
1082
- hasOwnProperty(rule.meta.messages, expectedSuggestion.messageId),
1083
- `${suggestionPrefix} Test has invalid messageId '${expectedSuggestion.messageId}', the rule under test allows only one of ${friendlyIDList}.`
1084
- );
1085
- assert.strictEqual(
1086
- actualSuggestion.messageId,
1087
- expectedSuggestion.messageId,
1088
- `${suggestionPrefix} messageId should be '${expectedSuggestion.messageId}' but got '${actualSuggestion.messageId}' instead.`
1089
- );
1090
- if (hasOwnProperty(expectedSuggestion, "data")) {
1091
- const unformattedMetaMessage = rule.meta.messages[expectedSuggestion.messageId];
1092
- const rehydratedDesc = interpolate(unformattedMetaMessage, expectedSuggestion.data);
1114
+ const actualSuggestion = message.suggestions[index];
1115
+ const suggestionPrefix = `Error Suggestion at index ${index}:`;
1093
1116
 
1117
+ if (hasOwnProperty(expectedSuggestion, "desc")) {
1118
+ assert.ok(
1119
+ !hasOwnProperty(expectedSuggestion, "data"),
1120
+ `${suggestionPrefix} Test should not specify both 'desc' and 'data'.`
1121
+ );
1122
+ assert.ok(
1123
+ !hasOwnProperty(expectedSuggestion, "messageId"),
1124
+ `${suggestionPrefix} Test should not specify both 'desc' and 'messageId'.`
1125
+ );
1094
1126
  assert.strictEqual(
1095
1127
  actualSuggestion.desc,
1096
- rehydratedDesc,
1097
- `${suggestionPrefix} Hydrated test desc "${rehydratedDesc}" does not match received desc "${actualSuggestion.desc}".`
1128
+ expectedSuggestion.desc,
1129
+ `${suggestionPrefix} desc should be "${expectedSuggestion.desc}" but got "${actualSuggestion.desc}" instead.`
1130
+ );
1131
+ } else if (hasOwnProperty(expectedSuggestion, "messageId")) {
1132
+ assert.ok(
1133
+ ruleHasMetaMessages,
1134
+ `${suggestionPrefix} Test can not use 'messageId' if rule under test doesn't define 'meta.messages'.`
1135
+ );
1136
+ assert.ok(
1137
+ hasOwnProperty(rule.meta.messages, expectedSuggestion.messageId),
1138
+ `${suggestionPrefix} Test has invalid messageId '${expectedSuggestion.messageId}', the rule under test allows only one of ${friendlyIDList}.`
1139
+ );
1140
+ assert.strictEqual(
1141
+ actualSuggestion.messageId,
1142
+ expectedSuggestion.messageId,
1143
+ `${suggestionPrefix} messageId should be '${expectedSuggestion.messageId}' but got '${actualSuggestion.messageId}' instead.`
1144
+ );
1145
+
1146
+ const unsubstitutedPlaceholders = getUnsubstitutedMessagePlaceholders(
1147
+ actualSuggestion.desc,
1148
+ rule.meta.messages[expectedSuggestion.messageId],
1149
+ expectedSuggestion.data
1150
+ );
1151
+
1152
+ assert.ok(
1153
+ unsubstitutedPlaceholders.length === 0,
1154
+ `The message of the suggestion has ${unsubstitutedPlaceholders.length > 1 ? `unsubstituted placeholders: ${unsubstitutedPlaceholders.map(name => `'${name}'`).join(", ")}` : `an unsubstituted placeholder '${unsubstitutedPlaceholders[0]}'`}. Please provide the missing ${unsubstitutedPlaceholders.length > 1 ? "values" : "value"} via the 'data' property for the suggestion in the context.report() call.`
1155
+ );
1156
+
1157
+ if (hasOwnProperty(expectedSuggestion, "data")) {
1158
+ const unformattedMetaMessage = rule.meta.messages[expectedSuggestion.messageId];
1159
+ const rehydratedDesc = interpolate(unformattedMetaMessage, expectedSuggestion.data);
1160
+
1161
+ assert.strictEqual(
1162
+ actualSuggestion.desc,
1163
+ rehydratedDesc,
1164
+ `${suggestionPrefix} Hydrated test desc "${rehydratedDesc}" does not match received desc "${actualSuggestion.desc}".`
1165
+ );
1166
+ }
1167
+ } else if (hasOwnProperty(expectedSuggestion, "data")) {
1168
+ assert.fail(
1169
+ `${suggestionPrefix} Test must specify 'messageId' if 'data' is used.`
1170
+ );
1171
+ } else {
1172
+ assert.fail(
1173
+ `${suggestionPrefix} Test must specify either 'messageId' or 'desc'.`
1098
1174
  );
1099
1175
  }
1100
- } else {
1101
- assert.ok(
1102
- !hasOwnProperty(expectedSuggestion, "data"),
1103
- `${suggestionPrefix} Test must specify 'messageId' if 'data' is used.`
1104
- );
1105
- }
1106
1176
 
1107
- if (hasOwnProperty(expectedSuggestion, "output")) {
1177
+ assert.ok(hasOwnProperty(expectedSuggestion, "output"), `${suggestionPrefix} The "output" property is required.`);
1108
1178
  const codeWithAppliedSuggestion = SourceCodeFixer.applyFixes(item.code, [actualSuggestion]).output;
1109
1179
 
1110
1180
  // Verify if suggestion fix makes a syntax error or not.
1111
1181
  const errorMessageInSuggestion =
1112
- linter.verify(codeWithAppliedSuggestion, result.configs, result.filename).find(m => m.fatal);
1182
+ linter.verify(codeWithAppliedSuggestion, result.configs, result.filename).find(m => m.fatal);
1113
1183
 
1114
1184
  assert(!errorMessageInSuggestion, [
1115
1185
  "A fatal parsing error occurred in suggestion fix.",
@@ -1119,8 +1189,11 @@ class RuleTester {
1119
1189
  ].join("\n"));
1120
1190
 
1121
1191
  assert.strictEqual(codeWithAppliedSuggestion, expectedSuggestion.output, `Expected the applied suggestion fix to match the test suggestion output for suggestion at index: ${index} on error with message: "${message.message}"`);
1122
- }
1123
- });
1192
+ assert.notStrictEqual(expectedSuggestion.output, item.code, `The output of a suggestion should differ from the original source code for suggestion at index: ${index} on error with message: "${message.message}"`);
1193
+ });
1194
+ } else {
1195
+ assert.fail("Test error object property 'suggestions' should be an array or a number");
1196
+ }
1124
1197
  }
1125
1198
  }
1126
1199
  } else {
@@ -1140,6 +1213,7 @@ class RuleTester {
1140
1213
  );
1141
1214
  } else {
1142
1215
  assert.strictEqual(result.output, item.output, "Output is incorrect.");
1216
+ assert.notStrictEqual(item.code, item.output, "Test property 'output' matches 'code'. If no autofix is expected, then omit the 'output' property or set it to null.");
1143
1217
  }
1144
1218
  } else {
1145
1219
  assert.strictEqual(
@@ -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
 
@@ -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.hasOwn(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":
@@ -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
  );
@@ -92,7 +92,7 @@ module.exports = {
92
92
  vars: "all",
93
93
  args: "after-used",
94
94
  ignoreRestSiblings: false,
95
- caughtErrors: "none"
95
+ caughtErrors: "all"
96
96
  };
97
97
 
98
98
  const firstOption = context.options[0];
@@ -101,7 +101,7 @@ module.exports = {
101
101
  properties: {
102
102
  enforceForClassMembers: {
103
103
  type: "boolean",
104
- default: false
104
+ default: true
105
105
  }
106
106
  },
107
107
  additionalProperties: false
@@ -114,7 +114,7 @@ module.exports = {
114
114
  },
115
115
  create(context) {
116
116
  const sourceCode = context.sourceCode;
117
- const enforceForClassMembers = context.options[0] && context.options[0].enforceForClassMembers;
117
+ const enforceForClassMembers = context.options[0]?.enforceForClassMembers ?? true;
118
118
 
119
119
  /**
120
120
  * Reports a given node if it violated this rule.
@@ -21,9 +21,17 @@ const astUtils = require("./utils/ast-utils");
21
21
  * @returns {boolean} `true` if the node is 'NaN' identifier.
22
22
  */
23
23
  function isNaNIdentifier(node) {
24
- return Boolean(node) && (
25
- astUtils.isSpecificId(node, "NaN") ||
26
- astUtils.isSpecificMemberAccess(node, "Number", "NaN")
24
+ if (!node) {
25
+ return false;
26
+ }
27
+
28
+ const nodeToCheck = node.type === "SequenceExpression"
29
+ ? node.expressions.at(-1)
30
+ : node;
31
+
32
+ return (
33
+ astUtils.isSpecificId(nodeToCheck, "NaN") ||
34
+ astUtils.isSpecificMemberAccess(nodeToCheck, "Number", "NaN")
27
35
  );
28
36
  }
29
37
 
@@ -34,6 +42,7 @@ function isNaNIdentifier(node) {
34
42
  /** @type {import('../shared/types').Rule} */
35
43
  module.exports = {
36
44
  meta: {
45
+ hasSuggestions: true,
37
46
  type: "problem",
38
47
 
39
48
  docs: {
@@ -63,7 +72,10 @@ module.exports = {
63
72
  comparisonWithNaN: "Use the isNaN function to compare with NaN.",
64
73
  switchNaN: "'switch(NaN)' can never match a case clause. Use Number.isNaN instead of the switch.",
65
74
  caseNaN: "'case NaN' can never match. Use Number.isNaN before the switch.",
66
- indexOfNaN: "Array prototype method '{{ methodName }}' cannot find NaN."
75
+ indexOfNaN: "Array prototype method '{{ methodName }}' cannot find NaN.",
76
+ replaceWithIsNaN: "Replace with Number.isNaN.",
77
+ replaceWithCastingAndIsNaN: "Replace with Number.isNaN and cast to a Number.",
78
+ replaceWithFindIndex: "Replace with Array.prototype.{{ methodName }}."
67
79
  }
68
80
  },
69
81
 
@@ -71,6 +83,35 @@ module.exports = {
71
83
 
72
84
  const enforceForSwitchCase = !context.options[0] || context.options[0].enforceForSwitchCase;
73
85
  const enforceForIndexOf = context.options[0] && context.options[0].enforceForIndexOf;
86
+ const sourceCode = context.sourceCode;
87
+
88
+ const fixableOperators = new Set(["==", "===", "!=", "!=="]);
89
+ const castableOperators = new Set(["==", "!="]);
90
+
91
+ /**
92
+ * Get a fixer for a binary expression that compares to NaN.
93
+ * @param {ASTNode} node The node to fix.
94
+ * @param {function(string): string} wrapValue A function that wraps the compared value with a fix.
95
+ * @returns {function(Fixer): Fix} The fixer function.
96
+ */
97
+ function getBinaryExpressionFixer(node, wrapValue) {
98
+ return fixer => {
99
+ const comparedValue = isNaNIdentifier(node.left) ? node.right : node.left;
100
+ const shouldWrap = comparedValue.type === "SequenceExpression";
101
+ const shouldNegate = node.operator[0] === "!";
102
+
103
+ const negation = shouldNegate ? "!" : "";
104
+ let comparedValueText = sourceCode.getText(comparedValue);
105
+
106
+ if (shouldWrap) {
107
+ comparedValueText = `(${comparedValueText})`;
108
+ }
109
+
110
+ const fixedValue = wrapValue(comparedValueText);
111
+
112
+ return fixer.replaceText(node, `${negation}${fixedValue}`);
113
+ };
114
+ }
74
115
 
75
116
  /**
76
117
  * Checks the given `BinaryExpression` node for `foo === NaN` and other comparisons.
@@ -82,7 +123,32 @@ module.exports = {
82
123
  /^(?:[<>]|[!=]=)=?$/u.test(node.operator) &&
83
124
  (isNaNIdentifier(node.left) || isNaNIdentifier(node.right))
84
125
  ) {
85
- context.report({ node, messageId: "comparisonWithNaN" });
126
+ const suggestedFixes = [];
127
+ const NaNNode = isNaNIdentifier(node.left) ? node.left : node.right;
128
+
129
+ const isSequenceExpression = NaNNode.type === "SequenceExpression";
130
+ const isSuggestable = fixableOperators.has(node.operator) && !isSequenceExpression;
131
+ const isCastable = castableOperators.has(node.operator);
132
+
133
+ if (isSuggestable) {
134
+ suggestedFixes.push({
135
+ messageId: "replaceWithIsNaN",
136
+ fix: getBinaryExpressionFixer(node, value => `Number.isNaN(${value})`)
137
+ });
138
+
139
+ if (isCastable) {
140
+ suggestedFixes.push({
141
+ messageId: "replaceWithCastingAndIsNaN",
142
+ fix: getBinaryExpressionFixer(node, value => `Number.isNaN(Number(${value}))`)
143
+ });
144
+ }
145
+ }
146
+
147
+ context.report({
148
+ node,
149
+ messageId: "comparisonWithNaN",
150
+ suggest: suggestedFixes
151
+ });
86
152
  }
87
153
  }
88
154
 
@@ -119,7 +185,35 @@ module.exports = {
119
185
  node.arguments.length === 1 &&
120
186
  isNaNIdentifier(node.arguments[0])
121
187
  ) {
122
- context.report({ node, messageId: "indexOfNaN", data: { methodName } });
188
+
189
+ /*
190
+ * To retain side effects, it's essential to address `NaN` beforehand, which
191
+ * is not possible with fixes like `arr.findIndex(Number.isNaN)`.
192
+ */
193
+ const isSuggestable = node.arguments[0].type !== "SequenceExpression";
194
+ const suggestedFixes = [];
195
+
196
+ if (isSuggestable) {
197
+ const shouldWrap = callee.computed;
198
+ const findIndexMethod = methodName === "indexOf" ? "findIndex" : "findLastIndex";
199
+ const propertyName = shouldWrap ? `"${findIndexMethod}"` : findIndexMethod;
200
+
201
+ suggestedFixes.push({
202
+ messageId: "replaceWithFindIndex",
203
+ data: { methodName: findIndexMethod },
204
+ fix: fixer => [
205
+ fixer.replaceText(callee.property, propertyName),
206
+ fixer.replaceText(node.arguments[0], "Number.isNaN")
207
+ ]
208
+ });
209
+ }
210
+
211
+ context.report({
212
+ node,
213
+ messageId: "indexOfNaN",
214
+ data: { methodName },
215
+ suggest: suggestedFixes
216
+ });
123
217
  }
124
218
  }
125
219
  }
@@ -19,6 +19,8 @@ const {
19
19
  lineBreakPattern,
20
20
  shebangPattern
21
21
  } = require("../../shared/ast-utils");
22
+ const globals = require("../../../conf/globals");
23
+ const { LATEST_ECMA_VERSION } = require("../../../conf/ecma-version");
22
24
 
23
25
  //------------------------------------------------------------------------------
24
26
  // Helpers
@@ -46,6 +48,12 @@ const OCTAL_OR_NON_OCTAL_DECIMAL_ESCAPE_PATTERN = /^(?:[^\\]|\\.)*\\(?:[1-9]|0[0
46
48
 
47
49
  const LOGICAL_ASSIGNMENT_OPERATORS = new Set(["&&=", "||=", "??="]);
48
50
 
51
+ /**
52
+ * All builtin global variables defined in the latest ECMAScript specification.
53
+ * @type {Record<string,boolean>} Key is the name of the variable. Value is `true` if the variable is considered writable, `false` otherwise.
54
+ */
55
+ const ECMASCRIPT_GLOBALS = globals[`es${LATEST_ECMA_VERSION}`];
56
+
49
57
  /**
50
58
  * Checks reference if is non initializer and writable.
51
59
  * @param {Reference} reference A reference to check.
@@ -1133,6 +1141,7 @@ module.exports = {
1133
1141
  LINEBREAK_MATCHER: lineBreakPattern,
1134
1142
  SHEBANG_MATCHER: shebangPattern,
1135
1143
  STATEMENT_LIST_PARENTS,
1144
+ ECMASCRIPT_GLOBALS,
1136
1145
 
1137
1146
  /**
1138
1147
  * Determines whether two adjacent tokens are on the same line.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint",
3
- "version": "9.0.0-alpha.2",
3
+ "version": "9.0.0-beta.1",
4
4
  "author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
5
5
  "description": "An AST-based pattern checker for JavaScript.",
6
6
  "bin": {
@@ -65,8 +65,8 @@
65
65
  "dependencies": {
66
66
  "@eslint-community/eslint-utils": "^4.2.0",
67
67
  "@eslint-community/regexpp": "^4.6.1",
68
- "@eslint/eslintrc": "^3.0.0",
69
- "@eslint/js": "9.0.0-alpha.2",
68
+ "@eslint/eslintrc": "^3.0.2",
69
+ "@eslint/js": "9.0.0-beta.1",
70
70
  "@humanwhocodes/config-array": "^0.11.14",
71
71
  "@humanwhocodes/module-importer": "^1.0.1",
72
72
  "@nodelib/fs.walk": "^1.2.8",
@@ -76,15 +76,14 @@
76
76
  "debug": "^4.3.2",
77
77
  "escape-string-regexp": "^4.0.0",
78
78
  "eslint-scope": "^8.0.0",
79
- "eslint-visitor-keys": "^3.4.3",
80
- "espree": "^10.0.0",
79
+ "eslint-visitor-keys": "^4.0.0",
80
+ "espree": "^10.0.1",
81
81
  "esquery": "^1.4.2",
82
82
  "esutils": "^2.0.2",
83
83
  "fast-deep-equal": "^3.1.3",
84
- "file-entry-cache": "^6.0.1",
84
+ "file-entry-cache": "^8.0.0",
85
85
  "find-up": "^5.0.0",
86
86
  "glob-parent": "^6.0.2",
87
- "globals": "^13.19.0",
88
87
  "graphemer": "^1.4.0",
89
88
  "ignore": "^5.2.0",
90
89
  "imurmurhash": "^0.1.4",
@@ -122,12 +121,13 @@
122
121
  "eslint-plugin-jsdoc": "^46.9.0",
123
122
  "eslint-plugin-n": "^16.6.0",
124
123
  "eslint-plugin-unicorn": "^49.0.0",
125
- "eslint-release": "^3.2.0",
124
+ "eslint-release": "^3.2.2",
126
125
  "eslump": "^3.0.0",
127
126
  "esprima": "^4.0.1",
128
127
  "fast-glob": "^3.2.11",
129
128
  "fs-teardown": "^0.1.3",
130
129
  "glob": "^10.0.0",
130
+ "globals": "^14.0.0",
131
131
  "got": "^11.8.3",
132
132
  "gray-matter": "^4.0.3",
133
133
  "js-yaml": "^4.1.0",
@@ -136,7 +136,7 @@
136
136
  "markdown-it": "^12.2.0",
137
137
  "markdown-it-container": "^3.0.0",
138
138
  "markdownlint": "^0.33.0",
139
- "markdownlint-cli": "^0.38.0",
139
+ "markdownlint-cli": "^0.39.0",
140
140
  "marked": "^4.0.8",
141
141
  "memfs": "^3.0.1",
142
142
  "metascraper": "^5.25.7",
@@ -155,7 +155,7 @@
155
155
  "regenerator-runtime": "^0.14.0",
156
156
  "rollup-plugin-node-polyfills": "^0.2.1",
157
157
  "semver": "^7.5.3",
158
- "shelljs": "^0.8.2",
158
+ "shelljs": "^0.8.5",
159
159
  "sinon": "^11.0.0",
160
160
  "vite-plugin-commonjs": "^0.10.0",
161
161
  "webdriverio": "^8.14.6",