eslint 9.13.0 → 9.15.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 (100) hide show
  1. package/README.md +2 -2
  2. package/lib/cli-engine/formatters/stylish.js +3 -3
  3. package/lib/cli-engine/lint-result-cache.js +1 -1
  4. package/lib/config/config-loader.js +193 -177
  5. package/lib/config/config.js +40 -24
  6. package/lib/eslint/eslint-helpers.js +18 -22
  7. package/lib/eslint/eslint.js +2 -2
  8. package/lib/languages/js/index.js +76 -0
  9. package/lib/languages/js/source-code/token-store/index.js +1 -1
  10. package/lib/linter/code-path-analysis/code-path-analyzer.js +1 -1
  11. package/lib/linter/code-path-analysis/fork-context.js +1 -1
  12. package/lib/linter/linter.js +36 -35
  13. package/lib/linter/report-translator.js +1 -1
  14. package/lib/rule-tester/rule-tester.js +1 -1
  15. package/lib/rules/accessor-pairs.js +10 -7
  16. package/lib/rules/array-callback-return.js +10 -8
  17. package/lib/rules/arrow-body-style.js +3 -1
  18. package/lib/rules/camelcase.js +27 -14
  19. package/lib/rules/class-methods-use-this.js +9 -5
  20. package/lib/rules/complexity.js +9 -5
  21. package/lib/rules/consistent-return.js +4 -4
  22. package/lib/rules/consistent-this.js +3 -7
  23. package/lib/rules/curly.js +3 -140
  24. package/lib/rules/default-case.js +3 -1
  25. package/lib/rules/dot-notation.js +9 -6
  26. package/lib/rules/func-names.js +3 -3
  27. package/lib/rules/func-style.js +10 -8
  28. package/lib/rules/getter-return.js +5 -5
  29. package/lib/rules/grouped-accessor-pairs.js +3 -1
  30. package/lib/rules/id-denylist.js +14 -1
  31. package/lib/rules/id-length.js +12 -8
  32. package/lib/rules/id-match.js +20 -17
  33. package/lib/rules/new-cap.js +15 -34
  34. package/lib/rules/no-bitwise.js +4 -5
  35. package/lib/rules/no-cond-assign.js +3 -3
  36. package/lib/rules/no-console.js +3 -2
  37. package/lib/rules/no-constant-condition.js +5 -4
  38. package/lib/rules/no-duplicate-imports.js +5 -4
  39. package/lib/rules/no-else-return.js +4 -5
  40. package/lib/rules/no-empty-function.js +4 -4
  41. package/lib/rules/no-empty-pattern.js +4 -4
  42. package/lib/rules/no-empty.js +6 -5
  43. package/lib/rules/no-eval.js +4 -5
  44. package/lib/rules/no-extend-native.js +3 -3
  45. package/lib/rules/no-extra-boolean-cast.js +3 -3
  46. package/lib/rules/no-fallthrough.js +12 -15
  47. package/lib/rules/no-global-assign.js +3 -2
  48. package/lib/rules/no-implicit-coercion.js +13 -24
  49. package/lib/rules/no-implicit-globals.js +4 -4
  50. package/lib/rules/no-inline-comments.js +4 -6
  51. package/lib/rules/no-inner-declarations.js +4 -2
  52. package/lib/rules/no-invalid-regexp.js +5 -4
  53. package/lib/rules/no-invalid-this.js +4 -4
  54. package/lib/rules/no-irregular-whitespace.js +24 -22
  55. package/lib/rules/no-labels.js +8 -7
  56. package/lib/rules/no-lonely-if.js +8 -2
  57. package/lib/rules/no-multi-assign.js +5 -10
  58. package/lib/rules/no-plusplus.js +4 -9
  59. package/lib/rules/no-promise-executor-return.js +4 -6
  60. package/lib/rules/no-redeclare.js +5 -8
  61. package/lib/rules/no-return-assign.js +3 -1
  62. package/lib/rules/no-self-assign.js +4 -3
  63. package/lib/rules/no-sequences.js +7 -7
  64. package/lib/rules/no-shadow.js +18 -14
  65. package/lib/rules/no-undef.js +4 -4
  66. package/lib/rules/no-underscore-dangle.js +31 -28
  67. package/lib/rules/no-unneeded-ternary.js +4 -4
  68. package/lib/rules/no-unreachable-loop.js +4 -2
  69. package/lib/rules/no-unsafe-negation.js +4 -4
  70. package/lib/rules/no-unsafe-optional-chaining.js +4 -4
  71. package/lib/rules/no-unused-expressions.js +17 -13
  72. package/lib/rules/no-use-before-define.js +14 -13
  73. package/lib/rules/no-useless-computed-key.js +9 -3
  74. package/lib/rules/no-useless-rename.js +7 -8
  75. package/lib/rules/no-void.js +4 -4
  76. package/lib/rules/no-warning-comments.js +9 -7
  77. package/lib/rules/operator-assignment.js +4 -2
  78. package/lib/rules/prefer-arrow-callback.js +5 -8
  79. package/lib/rules/prefer-const.js +5 -3
  80. package/lib/rules/prefer-promise-reject-errors.js +5 -3
  81. package/lib/rules/prefer-regex-literals.js +4 -3
  82. package/lib/rules/radix.js +3 -1
  83. package/lib/rules/require-atomic-updates.js +4 -3
  84. package/lib/rules/sort-imports.js +20 -16
  85. package/lib/rules/sort-keys.js +13 -16
  86. package/lib/rules/sort-vars.js +4 -4
  87. package/lib/rules/strict.js +3 -2
  88. package/lib/rules/unicode-bom.js +4 -2
  89. package/lib/rules/use-isnan.js +7 -4
  90. package/lib/rules/utils/ast-utils.js +186 -2
  91. package/lib/rules/valid-typeof.js +3 -2
  92. package/lib/rules/yoda.js +9 -12
  93. package/lib/shared/assert.js +22 -0
  94. package/lib/shared/deep-merge-arrays.js +60 -0
  95. package/lib/shared/text-table.js +67 -0
  96. package/lib/shared/types.js +1 -0
  97. package/lib/types/index.d.ts +3 -0
  98. package/lib/types/rules/ecmascript-6.d.ts +36 -16
  99. package/lib/types/rules/stylistic-issues.d.ts +3 -0
  100. package/package.json +19 -20
package/README.md CHANGED
@@ -299,8 +299,8 @@ to get your logo on our READMEs and [website](https://eslint.org/sponsors).
299
299
  <h3>Platinum Sponsors</h3>
300
300
  <p><a href="https://automattic.com"><img src="https://images.opencollective.com/automattic/d0ef3e1/logo.png" alt="Automattic" height="128"></a> <a href="https://www.airbnb.com/"><img src="https://images.opencollective.com/airbnb/d327d66/logo.png" alt="Airbnb" height="128"></a></p><h3>Gold Sponsors</h3>
301
301
  <p><a href="https://trunk.io/"><img src="https://images.opencollective.com/trunkio/fb92d60/avatar.png" alt="trunk.io" height="96"></a></p><h3>Silver Sponsors</h3>
302
- <p><a href="https://www.jetbrains.com/"><img src="https://images.opencollective.com/jetbrains/fe76f99/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>
303
- <p><a href="https://www.wordhint.net/"><img src="https://images.opencollective.com/wordhint/be86813/avatar.png" alt="WordHint" 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://www.gitbook.com"><img src="https://avatars.githubusercontent.com/u/7111340?v=4" alt="GitBook" 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>
302
+ <p><a href="https://www.serptriumph.com/"><img src="https://images.opencollective.com/serp-triumph5/fea3074/logo.png" alt="SERP Triumph" height="64"></a> <a href="https://www.jetbrains.com/"><img src="https://images.opencollective.com/jetbrains/fe76f99/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>
303
+ <p><a href="https://cybozu.co.jp/"><img src="https://images.opencollective.com/cybozu/933e46d/logo.png" alt="Cybozu" height="32"></a> <a href="https://syntax.fm"><img src="https://github.com/syntaxfm.png" alt="Syntax" height="32"></a> <a href="https://www.wordhint.net/"><img src="https://images.opencollective.com/wordhint/be86813/avatar.png" alt="WordHint" 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://www.gitbook.com"><img src="https://avatars.githubusercontent.com/u/7111340?v=4" alt="GitBook" 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>
304
304
  <h3>Technology Sponsors</h3>
305
305
  Technology sponsors allow us to use their products and services for free as part of a contribution to the open source ecosystem and our work.
306
306
  <p><a href="https://netlify.com"><img src="https://raw.githubusercontent.com/eslint/eslint.org/main/src/assets/images/techsponsors/netlify-icon.svg" alt="Netlify" height="32"></a> <a href="https://algolia.com"><img src="https://raw.githubusercontent.com/eslint/eslint.org/main/src/assets/images/techsponsors/algolia-icon.svg" alt="Algolia" height="32"></a> <a href="https://1password.com"><img src="https://raw.githubusercontent.com/eslint/eslint.org/main/src/assets/images/techsponsors/1password-icon.svg" alt="1Password" height="32"></a></p>
@@ -6,7 +6,7 @@
6
6
 
7
7
  const chalk = require("chalk"),
8
8
  util = require("node:util"),
9
- table = require("text-table");
9
+ table = require("../../shared/text-table");
10
10
 
11
11
  //------------------------------------------------------------------------------
12
12
  // Helpers
@@ -62,8 +62,8 @@ module.exports = function(results) {
62
62
 
63
63
  return [
64
64
  "",
65
- message.line || 0,
66
- message.column || 0,
65
+ String(message.line || 0),
66
+ String(message.column || 0),
67
67
  messageType,
68
68
  message.message.replace(/([^ ])\.$/u, "$1"),
69
69
  chalk.dim(message.ruleId || "")
@@ -8,11 +8,11 @@
8
8
  // Requirements
9
9
  //-----------------------------------------------------------------------------
10
10
 
11
- const assert = require("node:assert");
12
11
  const fs = require("node:fs");
13
12
  const fileEntryCache = require("file-entry-cache");
14
13
  const stringify = require("json-stable-stringify-without-jsonify");
15
14
  const pkg = require("../../package.json");
15
+ const assert = require("../shared/assert");
16
16
  const hash = require("./hash");
17
17
 
18
18
  const debug = require("debug")("eslint:lint-result-cache");
@@ -14,7 +14,7 @@ const fs = require("node:fs/promises");
14
14
  const findUp = require("find-up");
15
15
  const { pathToFileURL } = require("node:url");
16
16
  const debug = require("debug")("eslint:config-loader");
17
- const { FlatConfigArray } = require("../config/flat-config-array");
17
+ const { FlatConfigArray } = require("./flat-config-array");
18
18
 
19
19
  //-----------------------------------------------------------------------------
20
20
  // Types
@@ -206,95 +206,6 @@ async function loadConfigFile(filePath, allowTS) {
206
206
  return config;
207
207
  }
208
208
 
209
- /**
210
- * Calculates the config array for this run based on inputs.
211
- * @param {string} configFilePath The absolute path to the config file to use if not overridden.
212
- * @param {string} basePath The base path to use for relative paths in the config file.
213
- * @param {ConfigLoaderOptions} options The options to use when loading configuration files.
214
- * @returns {Promise<FlatConfigArray>} The config array for `eslint`.
215
- */
216
- async function calculateConfigArray(configFilePath, basePath, options) {
217
-
218
- const {
219
- cwd,
220
- baseConfig,
221
- ignoreEnabled,
222
- ignorePatterns,
223
- overrideConfig,
224
- defaultConfigs = [],
225
- allowTS
226
- } = options;
227
-
228
- debug(`Calculating config array from config file ${configFilePath} and base path ${basePath}`);
229
-
230
- const configs = new FlatConfigArray(baseConfig || [], { basePath, shouldIgnore: ignoreEnabled });
231
-
232
- // load config file
233
- if (configFilePath) {
234
-
235
- debug(`Loading config file ${configFilePath}`);
236
- const fileConfig = await loadConfigFile(configFilePath, allowTS);
237
-
238
- if (Array.isArray(fileConfig)) {
239
- configs.push(...fileConfig);
240
- } else {
241
- configs.push(fileConfig);
242
- }
243
- }
244
-
245
- // add in any configured defaults
246
- configs.push(...defaultConfigs);
247
-
248
- // append command line ignore patterns
249
- if (ignorePatterns && ignorePatterns.length > 0) {
250
-
251
- let relativeIgnorePatterns;
252
-
253
- /*
254
- * If the config file basePath is different than the cwd, then
255
- * the ignore patterns won't work correctly. Here, we adjust the
256
- * ignore pattern to include the correct relative path. Patterns
257
- * passed as `ignorePatterns` are relative to the cwd, whereas
258
- * the config file basePath can be an ancestor of the cwd.
259
- */
260
- if (basePath === cwd) {
261
- relativeIgnorePatterns = ignorePatterns;
262
- } else {
263
-
264
- // relative path must only have Unix-style separators
265
- const relativeIgnorePath = path.relative(basePath, cwd).replace(/\\/gu, "/");
266
-
267
- relativeIgnorePatterns = ignorePatterns.map(pattern => {
268
- const negated = pattern.startsWith("!");
269
- const basePattern = negated ? pattern.slice(1) : pattern;
270
-
271
- return (negated ? "!" : "") +
272
- path.posix.join(relativeIgnorePath, basePattern);
273
- });
274
- }
275
-
276
- /*
277
- * Ignore patterns are added to the end of the config array
278
- * so they can override default ignores.
279
- */
280
- configs.push({
281
- ignores: relativeIgnorePatterns
282
- });
283
- }
284
-
285
- if (overrideConfig) {
286
- if (Array.isArray(overrideConfig)) {
287
- configs.push(...overrideConfig);
288
- } else {
289
- configs.push(overrideConfig);
290
- }
291
- }
292
-
293
- await configs.normalize();
294
-
295
- return configs;
296
- }
297
-
298
209
  //-----------------------------------------------------------------------------
299
210
  // Exports
300
211
  //-----------------------------------------------------------------------------
@@ -307,13 +218,13 @@ class ConfigLoader {
307
218
 
308
219
  /**
309
220
  * Map of config file paths to the config arrays for those directories.
310
- * @type {Map<string, FlatConfigArray>}
221
+ * @type {Map<string, FlatConfigArray|Promise<FlatConfigArray>>}
311
222
  */
312
223
  #configArrays = new Map();
313
224
 
314
225
  /**
315
226
  * Map of absolute directory names to the config file paths for those directories.
316
- * @type {Map<string, {configFilePath:string,basePath:string}>}
227
+ * @type {Map<string, {configFilePath:string,basePath:string}|Promise<{configFilePath:string,basePath:string}>>}
317
228
  */
318
229
  #configFilePaths = new Map();
319
230
 
@@ -347,43 +258,22 @@ class ConfigLoader {
347
258
  return this.#configFilePaths.get(fromDirectory);
348
259
  }
349
260
 
350
- const configFilenames = this.#options.allowTS
351
- ? [...FLAT_CONFIG_FILENAMES, ...TS_FLAT_CONFIG_FILENAMES]
352
- : FLAT_CONFIG_FILENAMES;
353
-
354
- // determine where to load config file from
355
- let configFilePath;
356
- const {
357
- cwd,
358
- configFile: useConfigFile
359
- } = this.#options;
360
- let basePath = cwd;
361
-
362
- if (typeof useConfigFile === "string") {
363
- debug(`Override config file path is ${useConfigFile}`);
364
- configFilePath = path.resolve(cwd, useConfigFile);
365
- basePath = cwd;
366
- } else if (useConfigFile !== false) {
367
- debug("Searching for eslint.config.js");
368
- configFilePath = await findUp(
369
- configFilenames,
370
- { cwd: fromDirectory }
371
- );
261
+ const resultPromise = ConfigLoader.locateConfigFileToUse({
262
+ useConfigFile: this.#options.configFile,
263
+ cwd: this.#options.cwd,
264
+ fromDirectory,
265
+ allowTS: this.#options.allowTS
266
+ });
372
267
 
373
- if (configFilePath) {
374
- basePath = path.dirname(configFilePath);
375
- }
268
+ // ensure `ConfigLoader.locateConfigFileToUse` is called only once for `fromDirectory`
269
+ this.#configFilePaths.set(fromDirectory, resultPromise);
376
270
 
377
- }
271
+ // Unwrap the promise. This is primarily for the sync `getCachedConfigArrayForPath` method.
272
+ const result = await resultPromise;
378
273
 
379
- // cache the result
380
- this.#configFilePaths.set(fromDirectory, { configFilePath, basePath });
381
-
382
- return {
383
- configFilePath,
384
- basePath
385
- };
274
+ this.#configFilePaths.set(fromDirectory, result);
386
275
 
276
+ return result;
387
277
  }
388
278
 
389
279
  /**
@@ -399,9 +289,14 @@ class ConfigLoader {
399
289
  return this.#configArrays.get(configFilePath);
400
290
  }
401
291
 
402
- const configs = await calculateConfigArray(configFilePath, basePath, this.#options);
292
+ const configsPromise = ConfigLoader.calculateConfigArray(configFilePath, basePath, this.#options);
293
+
294
+ // ensure `ConfigLoader.calculateConfigArray` is called only once for `configFilePath`
295
+ this.#configArrays.set(configFilePath, configsPromise);
296
+
297
+ // Unwrap the promise. This is primarily for the sync `getCachedConfigArrayForPath` method.
298
+ const configs = await configsPromise;
403
299
 
404
- // cache the config array for this instance
405
300
  this.#configArrays.set(configFilePath, configs);
406
301
 
407
302
  return configs;
@@ -512,9 +407,21 @@ class ConfigLoader {
512
407
  throw new Error(`Could not find config file for ${fileOrDirPath}`);
513
408
  }
514
409
 
515
- const { configFilePath } = this.#configFilePaths.get(absoluteDirPath);
410
+ const configFilePathInfo = this.#configFilePaths.get(absoluteDirPath);
411
+
412
+ if (typeof configFilePathInfo.then === "function") {
413
+ throw new Error(`Config file path for ${fileOrDirPath} has not yet been calculated or an error occurred during the calculation`);
414
+ }
415
+
416
+ const { configFilePath } = configFilePathInfo;
417
+
418
+ const configArray = this.#configArrays.get(configFilePath);
516
419
 
517
- return this.#configArrays.get(configFilePath);
420
+ if (!configArray || typeof configArray.then === "function") {
421
+ throw new Error(`Config array for ${fileOrDirPath} has not yet been calculated or an error occurred during the calculation`);
422
+ }
423
+
424
+ return configArray;
518
425
  }
519
426
 
520
427
  /**
@@ -526,6 +433,144 @@ class ConfigLoader {
526
433
  return import("jiti");
527
434
  }
528
435
 
436
+ /**
437
+ * Determines which config file to use. This is determined by seeing if an
438
+ * override config file was specified, and if so, using it; otherwise, as long
439
+ * as override config file is not explicitly set to `false`, it will search
440
+ * upwards from `fromDirectory` for a file named `eslint.config.js`.
441
+ * This method is exposed internally for testing purposes.
442
+ * @param {Object} [options] the options object
443
+ * @param {string|false|undefined} options.useConfigFile The path to the config file to use.
444
+ * @param {string} options.cwd Path to a directory that should be considered as the current working directory.
445
+ * @param {string} [options.fromDirectory] The directory from which to start searching. Defaults to `cwd`.
446
+ * @param {boolean} options.allowTS Indicates if TypeScript configuration files are allowed.
447
+ * @returns {Promise<{configFilePath:string|undefined,basePath:string}>} Location information for
448
+ * the config file.
449
+ */
450
+ static async locateConfigFileToUse({ useConfigFile, cwd, fromDirectory = cwd, allowTS }) {
451
+
452
+ const configFilenames = allowTS
453
+ ? [...FLAT_CONFIG_FILENAMES, ...TS_FLAT_CONFIG_FILENAMES]
454
+ : FLAT_CONFIG_FILENAMES;
455
+
456
+ // determine where to load config file from
457
+ let configFilePath;
458
+ let basePath = cwd;
459
+
460
+ if (typeof useConfigFile === "string") {
461
+ debug(`Override config file path is ${useConfigFile}`);
462
+ configFilePath = path.resolve(cwd, useConfigFile);
463
+ basePath = cwd;
464
+ } else if (useConfigFile !== false) {
465
+ debug("Searching for eslint.config.js");
466
+ configFilePath = await findUp(
467
+ configFilenames,
468
+ { cwd: fromDirectory }
469
+ );
470
+
471
+ if (configFilePath) {
472
+ basePath = path.dirname(configFilePath);
473
+ }
474
+
475
+ }
476
+
477
+ return {
478
+ configFilePath,
479
+ basePath
480
+ };
481
+
482
+ }
483
+
484
+ /**
485
+ * Calculates the config array for this run based on inputs.
486
+ * This method is exposed internally for testing purposes.
487
+ * @param {string} configFilePath The absolute path to the config file to use if not overridden.
488
+ * @param {string} basePath The base path to use for relative paths in the config file.
489
+ * @param {ConfigLoaderOptions} options The options to use when loading configuration files.
490
+ * @returns {Promise<FlatConfigArray>} The config array for `eslint`.
491
+ */
492
+ static async calculateConfigArray(configFilePath, basePath, options) {
493
+
494
+ const {
495
+ cwd,
496
+ baseConfig,
497
+ ignoreEnabled,
498
+ ignorePatterns,
499
+ overrideConfig,
500
+ defaultConfigs = [],
501
+ allowTS
502
+ } = options;
503
+
504
+ debug(`Calculating config array from config file ${configFilePath} and base path ${basePath}`);
505
+
506
+ const configs = new FlatConfigArray(baseConfig || [], { basePath, shouldIgnore: ignoreEnabled });
507
+
508
+ // load config file
509
+ if (configFilePath) {
510
+
511
+ debug(`Loading config file ${configFilePath}`);
512
+ const fileConfig = await loadConfigFile(configFilePath, allowTS);
513
+
514
+ if (Array.isArray(fileConfig)) {
515
+ configs.push(...fileConfig);
516
+ } else {
517
+ configs.push(fileConfig);
518
+ }
519
+ }
520
+
521
+ // add in any configured defaults
522
+ configs.push(...defaultConfigs);
523
+
524
+ // append command line ignore patterns
525
+ if (ignorePatterns && ignorePatterns.length > 0) {
526
+
527
+ let relativeIgnorePatterns;
528
+
529
+ /*
530
+ * If the config file basePath is different than the cwd, then
531
+ * the ignore patterns won't work correctly. Here, we adjust the
532
+ * ignore pattern to include the correct relative path. Patterns
533
+ * passed as `ignorePatterns` are relative to the cwd, whereas
534
+ * the config file basePath can be an ancestor of the cwd.
535
+ */
536
+ if (basePath === cwd) {
537
+ relativeIgnorePatterns = ignorePatterns;
538
+ } else {
539
+
540
+ // relative path must only have Unix-style separators
541
+ const relativeIgnorePath = path.relative(basePath, cwd).replace(/\\/gu, "/");
542
+
543
+ relativeIgnorePatterns = ignorePatterns.map(pattern => {
544
+ const negated = pattern.startsWith("!");
545
+ const basePattern = negated ? pattern.slice(1) : pattern;
546
+
547
+ return (negated ? "!" : "") +
548
+ path.posix.join(relativeIgnorePath, basePattern);
549
+ });
550
+ }
551
+
552
+ /*
553
+ * Ignore patterns are added to the end of the config array
554
+ * so they can override default ignores.
555
+ */
556
+ configs.push({
557
+ ignores: relativeIgnorePatterns
558
+ });
559
+ }
560
+
561
+ if (overrideConfig) {
562
+ if (Array.isArray(overrideConfig)) {
563
+ configs.push(...overrideConfig);
564
+ } else {
565
+ configs.push(overrideConfig);
566
+ }
567
+ }
568
+
569
+ await configs.normalize();
570
+
571
+ return configs;
572
+ }
573
+
529
574
  }
530
575
 
531
576
  /**
@@ -542,13 +587,13 @@ class LegacyConfigLoader extends ConfigLoader {
542
587
 
543
588
  /**
544
589
  * The cached config file path for this instance.
545
- * @type {{configFilePath:string,basePath:string}|undefined}
590
+ * @type {Promise<{configFilePath:string,basePath:string}|undefined>}
546
591
  */
547
592
  #configFilePath;
548
593
 
549
594
  /**
550
595
  * The cached config array for this instance.
551
- * @type {FlatConfigArray}
596
+ * @type {FlatConfigArray|Promise<FlatConfigArray>}
552
597
  */
553
598
  #configArray;
554
599
 
@@ -569,50 +614,16 @@ class LegacyConfigLoader extends ConfigLoader {
569
614
  * @returns {Promise<{configFilePath:string|undefined,basePath:string}>} Location information for
570
615
  * the config file.
571
616
  */
572
- async #locateConfigFileToUse() {
573
-
574
- // check cache first
575
- if (this.#configFilePath) {
576
- return this.#configFilePath;
577
- }
578
-
579
- const configFilenames = this.#options.allowTS
580
- ? [...FLAT_CONFIG_FILENAMES, ...TS_FLAT_CONFIG_FILENAMES]
581
- : FLAT_CONFIG_FILENAMES;
582
-
583
- // determine where to load config file from
584
- let configFilePath;
585
- const {
586
- cwd,
587
- configFile: useConfigFile
588
- } = this.#options;
589
- let basePath = cwd;
590
-
591
- if (typeof useConfigFile === "string") {
592
- debug(`[Legacy]: Override config file path is ${useConfigFile}`);
593
- configFilePath = path.resolve(cwd, useConfigFile);
594
- basePath = cwd;
595
- } else if (useConfigFile !== false) {
596
- debug("[Legacy]: Searching for eslint.config.js");
597
- configFilePath = await findUp(
598
- configFilenames,
599
- { cwd }
600
- );
601
-
602
- if (configFilePath) {
603
- basePath = path.dirname(configFilePath);
604
- }
605
-
617
+ #locateConfigFileToUse() {
618
+ if (!this.#configFilePath) {
619
+ this.#configFilePath = ConfigLoader.locateConfigFileToUse({
620
+ useConfigFile: this.#options.configFile,
621
+ cwd: this.#options.cwd,
622
+ allowTS: this.#options.allowTS
623
+ });
606
624
  }
607
625
 
608
- // cache the result
609
- this.#configFilePath = { configFilePath, basePath };
610
-
611
- return {
612
- configFilePath,
613
- basePath
614
- };
615
-
626
+ return this.#configFilePath;
616
627
  }
617
628
 
618
629
  /**
@@ -628,12 +639,13 @@ class LegacyConfigLoader extends ConfigLoader {
628
639
  return this.#configArray;
629
640
  }
630
641
 
631
- const configs = await calculateConfigArray(configFilePath, basePath, this.#options);
642
+ // ensure `ConfigLoader.calculateConfigArray` is called only once
643
+ this.#configArray = ConfigLoader.calculateConfigArray(configFilePath, basePath, this.#options);
632
644
 
633
- // cache the config array for this instance
634
- this.#configArray = configs;
645
+ // Unwrap the promise. This is primarily for the sync `getCachedConfigArrayForPath` method.
646
+ this.#configArray = await this.#configArray;
635
647
 
636
- return configs;
648
+ return this.#configArray;
637
649
  }
638
650
 
639
651
 
@@ -696,6 +708,10 @@ class LegacyConfigLoader extends ConfigLoader {
696
708
  throw new Error(`Could not find config file for ${dirPath}`);
697
709
  }
698
710
 
711
+ if (typeof this.#configArray.then === "function") {
712
+ throw new Error(`Config array for ${dirPath} has not yet been calculated or an error occurred during the calculation`);
713
+ }
714
+
699
715
  return this.#configArray;
700
716
  }
701
717
  }
@@ -9,8 +9,10 @@
9
9
  // Requirements
10
10
  //-----------------------------------------------------------------------------
11
11
 
12
- const { RuleValidator } = require("./rule-validator");
12
+ const { deepMergeArrays } = require("../shared/deep-merge-arrays");
13
+ const { getRuleFromConfig } = require("./flat-config-helpers");
13
14
  const { flatConfigSchema, hasMethod } = require("./flat-config-schema");
15
+ const { RuleValidator } = require("./rule-validator");
14
16
  const { ObjectSchema } = require("@eslint/config-array");
15
17
 
16
18
  //-----------------------------------------------------------------------------
@@ -119,28 +121,6 @@ function languageOptionsToJSON(languageOptions, objectKey = "languageOptions") {
119
121
  return result;
120
122
  }
121
123
 
122
- /**
123
- * Normalizes the rules configuration. Ensure that each rule config is
124
- * an array and that the severity is a number. This function modifies the
125
- * rulesConfig.
126
- * @param {Record<string, any>} rulesConfig The rules configuration to normalize.
127
- * @returns {void}
128
- */
129
- function normalizeRulesConfig(rulesConfig) {
130
-
131
- for (const [ruleId, ruleConfig] of Object.entries(rulesConfig)) {
132
-
133
- // ensure rule config is an array
134
- if (!Array.isArray(ruleConfig)) {
135
- rulesConfig[ruleId] = [ruleConfig];
136
- }
137
-
138
- // normalize severity
139
- rulesConfig[ruleId][0] = severities.get(rulesConfig[ruleId][0]);
140
- }
141
-
142
- }
143
-
144
124
 
145
125
  //-----------------------------------------------------------------------------
146
126
  // Exports
@@ -215,6 +195,11 @@ class Config {
215
195
  throw new TypeError(`Key "languageOptions": ${error.message}`, { cause: error });
216
196
  }
217
197
 
198
+ // Normalize language options if necessary
199
+ if (this.language.normalizeLanguageOptions) {
200
+ this.languageOptions = this.language.normalizeLanguageOptions(this.languageOptions);
201
+ }
202
+
218
203
  // Check processor value
219
204
  if (processor) {
220
205
  this.processor = processor;
@@ -239,7 +224,7 @@ class Config {
239
224
 
240
225
  // Process the rules
241
226
  if (this.rules) {
242
- normalizeRulesConfig(this.rules);
227
+ this.#normalizeRulesConfig();
243
228
  ruleValidator.validate(this);
244
229
  }
245
230
  }
@@ -276,6 +261,37 @@ class Config {
276
261
  processor: this.#processorName
277
262
  };
278
263
  }
264
+
265
+ /**
266
+ * Normalizes the rules configuration. Ensures that each rule config is
267
+ * an array and that the severity is a number. Applies meta.defaultOptions.
268
+ * This function modifies `this.rules`.
269
+ * @returns {void}
270
+ */
271
+ #normalizeRulesConfig() {
272
+ for (const [ruleId, originalConfig] of Object.entries(this.rules)) {
273
+
274
+ // ensure rule config is an array
275
+ let ruleConfig = Array.isArray(originalConfig)
276
+ ? originalConfig
277
+ : [originalConfig];
278
+
279
+ // normalize severity
280
+ ruleConfig[0] = severities.get(ruleConfig[0]);
281
+
282
+ const rule = getRuleFromConfig(ruleId, this);
283
+
284
+ // apply meta.defaultOptions
285
+ const slicedOptions = ruleConfig.slice(1);
286
+ const mergedOptions = deepMergeArrays(rule?.meta?.defaultOptions, slicedOptions);
287
+
288
+ if (mergedOptions.length) {
289
+ ruleConfig = [ruleConfig[0], ...mergedOptions];
290
+ }
291
+
292
+ this.rules[ruleId] = ruleConfig;
293
+ }
294
+ }
279
295
  }
280
296
 
281
297
  module.exports = { Config };