eslint 9.12.0 → 9.14.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.
package/README.md CHANGED
@@ -289,23 +289,22 @@ Percy Ma
289
289
 
290
290
  <!--teamend-->
291
291
 
292
+ <!-- NOTE: This section is autogenerated. Do not manually edit.-->
293
+ <!--sponsorsstart-->
292
294
  ## Sponsors
293
295
 
294
- The following companies, organizations, and individuals support ESLint's ongoing maintenance and development. [Become a Sponsor](https://eslint.org/donate) to get your logo on our README and website.
296
+ The following companies, organizations, and individuals support ESLint's ongoing maintenance and development. [Become a Sponsor](https://eslint.org/donate)
297
+ to get your logo on our READMEs and [website](https://eslint.org/sponsors).
295
298
 
296
- <!-- NOTE: This section is autogenerated. Do not manually edit.-->
297
- <!--sponsorsstart-->
298
299
  <h3>Platinum Sponsors</h3>
299
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>
300
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>
301
- <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>
302
- <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>
303
- <!--sponsorsend-->
304
-
305
- <!--techsponsorsstart-->
306
- <h2>Technology Sponsors</h2>
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://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
+ <h3>Technology Sponsors</h3>
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.
307
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>
308
- <!--techsponsorsend-->
307
+ <!--sponsorsend-->
309
308
 
310
309
  [tidelift]: https://tidelift.com/funding/github/npm/eslint
311
310
  [herodevs]: https://www.herodevs.com/support/eslint-nes?utm_source=ESLintWebsite&utm_medium=ESLintWebsite&utm_campaign=ESLintNES&utm_id=ESLintNES
package/bin/eslint.js CHANGED
@@ -9,6 +9,11 @@
9
9
 
10
10
  "use strict";
11
11
 
12
+ const mod = require("node:module");
13
+
14
+ // to use V8's code cache to speed up instantiation time
15
+ mod.enableCompileCache?.();
16
+
12
17
  // must do this initialization *before* other requires in order to work
13
18
  if (process.argv.includes("--debug")) {
14
19
  require("debug").enable("eslint:*,-eslint:code-path,eslintrc:*");
package/lib/cli.js CHANGED
@@ -146,24 +146,29 @@ async function translateOptions({
146
146
  overrideConfigFile = void 0;
147
147
  }
148
148
 
149
- let globals = {};
149
+ const languageOptions = {};
150
150
 
151
151
  if (global) {
152
- globals = global.reduce((obj, name) => {
152
+ languageOptions.globals = global.reduce((obj, name) => {
153
153
  if (name.endsWith(":true")) {
154
154
  obj[name.slice(0, -5)] = "writable";
155
155
  } else {
156
156
  obj[name] = "readonly";
157
157
  }
158
158
  return obj;
159
- }, globals);
159
+ }, {});
160
+ }
161
+
162
+ if (parserOptions) {
163
+ languageOptions.parserOptions = parserOptions;
164
+ }
165
+
166
+ if (parser) {
167
+ languageOptions.parser = await importer.import(parser);
160
168
  }
161
169
 
162
170
  overrideConfig = [{
163
- languageOptions: {
164
- globals,
165
- parserOptions: parserOptions || {}
166
- },
171
+ ...Object.keys(languageOptions).length > 0 ? { languageOptions } : {},
167
172
  rules: rule ? rule : {}
168
173
  }];
169
174
 
@@ -175,10 +180,6 @@ async function translateOptions({
175
180
  };
176
181
  }
177
182
 
178
- if (parser) {
179
- overrideConfig[0].languageOptions.parser = await importer.import(parser);
180
- }
181
-
182
183
  if (plugin) {
183
184
  overrideConfig[0].plugins = await loadPlugins(importer, plugin);
184
185
  }
@@ -378,7 +379,7 @@ const cli = {
378
379
  debug("Using flat config?", usingFlatConfig);
379
380
 
380
381
  if (allowFlatConfig && !usingFlatConfig) {
381
- process.emitWarning("You are using an eslintrc configuration file, which is deprecated and support will be removed in v10.0.0. Please migrate to an eslint.config.js file. See https://eslint.org/docs/latest/use/configure/migration-guide for details.", "ESLintRCWarning");
382
+ process.emitWarning("You are using an eslintrc configuration file, which is deprecated and support will be removed in v10.0.0. Please migrate to an eslint.config.js file. See https://eslint.org/docs/latest/use/configure/migration-guide for details. An eslintrc configuration file is used because you have the ESLINT_USE_FLAT_CONFIG environment variable set to false. If you want to use an eslint.config.js file, remove the environment variable. If you want to find the location of the eslintrc configuration file, use the --debug flag.", "ESLintRCWarning");
382
383
  }
383
384
 
384
385
  const CLIOptions = createCLIOptions(usingFlatConfig);
@@ -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
@@ -173,21 +173,22 @@ async function loadConfigFile(filePath, allowTS) {
173
173
  */
174
174
  if (allowTS && isTS && !isDeno && !isBun) {
175
175
 
176
- const createJiti = await import("jiti").then(jitiModule => (typeof jitiModule?.createJiti === "function" ? jitiModule.createJiti : jitiModule.default), () => {
176
+ // eslint-disable-next-line no-use-before-define -- `ConfigLoader.loadJiti` can be overwritten for testing
177
+ const { createJiti } = await ConfigLoader.loadJiti().catch(() => {
177
178
  throw new Error("The 'jiti' library is required for loading TypeScript configuration files. Make sure to install it.");
178
179
  });
179
180
 
181
+ // `createJiti` was added in jiti v2.
182
+ if (typeof createJiti !== "function") {
183
+ throw new Error("You are using an outdated version of the 'jiti' library. Please update to the latest version of 'jiti' to ensure compatibility and access to the latest features.");
184
+ }
185
+
180
186
  /*
181
187
  * Disabling `moduleCache` allows us to reload a
182
188
  * config file when the last modified timestamp changes.
183
189
  */
184
190
 
185
191
  const jiti = createJiti(__filename, { moduleCache: false, interopDefault: false });
186
-
187
- if (typeof jiti?.import !== "function") {
188
- throw new Error("You are using an outdated version of the 'jiti' library. Please update to the latest version of 'jiti' to ensure compatibility and access to the latest features.");
189
- }
190
-
191
192
  const config = await jiti.import(fileURL.href);
192
193
 
193
194
  importedConfigFileModificationTime.set(filePath, mtime);
@@ -205,96 +206,6 @@ async function loadConfigFile(filePath, allowTS) {
205
206
  return config;
206
207
  }
207
208
 
208
- /**
209
- * Calculates the config array for this run based on inputs.
210
- * @param {string} configFilePath The absolute path to the config file to use if not overridden.
211
- * @param {string} basePath The base path to use for relative paths in the config file.
212
- * @param {ConfigLoaderOptions} options The options to use when loading configuration files.
213
- * @returns {Promise<FlatConfigArray>} The config array for `eslint`.
214
- */
215
- async function calculateConfigArray(configFilePath, basePath, options) {
216
-
217
- const {
218
- cwd,
219
- baseConfig,
220
- ignoreEnabled,
221
- ignorePatterns,
222
- overrideConfig,
223
- defaultConfigs = [],
224
- allowTS
225
- } = options;
226
-
227
- debug(`Calculating config array from config file ${configFilePath} and base path ${basePath}`);
228
-
229
- const configs = new FlatConfigArray(baseConfig || [], { basePath, shouldIgnore: ignoreEnabled });
230
-
231
- // load config file
232
- if (configFilePath) {
233
-
234
- debug(`Loading config file ${configFilePath}`);
235
- const fileConfig = await loadConfigFile(configFilePath, allowTS);
236
-
237
- if (Array.isArray(fileConfig)) {
238
- configs.push(...fileConfig);
239
- } else {
240
- configs.push(fileConfig);
241
- }
242
- }
243
-
244
- // add in any configured defaults
245
- configs.push(...defaultConfigs);
246
-
247
- // append command line ignore patterns
248
- if (ignorePatterns && ignorePatterns.length > 0) {
249
-
250
- let relativeIgnorePatterns;
251
-
252
- /*
253
- * If the config file basePath is different than the cwd, then
254
- * the ignore patterns won't work correctly. Here, we adjust the
255
- * ignore pattern to include the correct relative path. Patterns
256
- * passed as `ignorePatterns` are relative to the cwd, whereas
257
- * the config file basePath can be an ancestor of the cwd.
258
- */
259
- if (basePath === cwd) {
260
- relativeIgnorePatterns = ignorePatterns;
261
- } else {
262
-
263
- // relative path must only have Unix-style separators
264
- const relativeIgnorePath = path.relative(basePath, cwd).replace(/\\/gu, "/");
265
-
266
- relativeIgnorePatterns = ignorePatterns.map(pattern => {
267
- const negated = pattern.startsWith("!");
268
- const basePattern = negated ? pattern.slice(1) : pattern;
269
-
270
- return (negated ? "!" : "") +
271
- path.posix.join(relativeIgnorePath, basePattern);
272
- });
273
- }
274
-
275
- /*
276
- * Ignore patterns are added to the end of the config array
277
- * so they can override default ignores.
278
- */
279
- configs.push({
280
- ignores: relativeIgnorePatterns
281
- });
282
- }
283
-
284
- if (overrideConfig) {
285
- if (Array.isArray(overrideConfig)) {
286
- configs.push(...overrideConfig);
287
- } else {
288
- configs.push(overrideConfig);
289
- }
290
- }
291
-
292
- await configs.normalize();
293
-
294
- return configs;
295
- }
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
- );
372
-
373
- if (configFilePath) {
374
- basePath = path.dirname(configFilePath);
375
- }
261
+ const resultPromise = ConfigLoader.locateConfigFileToUse({
262
+ useConfigFile: this.#options.configFile,
263
+ cwd: this.#options.cwd,
264
+ fromDirectory,
265
+ allowTS: this.#options.allowTS
266
+ });
376
267
 
377
- }
268
+ // ensure `ConfigLoader.locateConfigFileToUse` is called only once for `fromDirectory`
269
+ this.#configFilePaths.set(fromDirectory, resultPromise);
378
270
 
379
- // cache the result
380
- this.#configFilePaths.set(fromDirectory, { configFilePath, basePath });
271
+ // Unwrap the promise. This is primarily for the sync `getCachedConfigArrayForPath` method.
272
+ const result = await resultPromise;
381
273
 
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,168 @@ 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);
516
411
 
517
- return this.#configArrays.get(configFilePath);
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);
419
+
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;
425
+ }
426
+
427
+ /**
428
+ * Used to import the jiti dependency. This method is exposed internally for testing purposes.
429
+ * @returns {Promise<Record<string, unknown>>} A promise that fulfills with a module object
430
+ * or rejects with an error if jiti is not found.
431
+ */
432
+ static loadJiti() {
433
+ return import("jiti");
434
+ }
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;
518
572
  }
519
573
 
520
574
  }
@@ -533,13 +587,13 @@ class LegacyConfigLoader extends ConfigLoader {
533
587
 
534
588
  /**
535
589
  * The cached config file path for this instance.
536
- * @type {{configFilePath:string,basePath:string}|undefined}
590
+ * @type {Promise<{configFilePath:string,basePath:string}|undefined>}
537
591
  */
538
592
  #configFilePath;
539
593
 
540
594
  /**
541
595
  * The cached config array for this instance.
542
- * @type {FlatConfigArray}
596
+ * @type {FlatConfigArray|Promise<FlatConfigArray>}
543
597
  */
544
598
  #configArray;
545
599
 
@@ -560,50 +614,16 @@ class LegacyConfigLoader extends ConfigLoader {
560
614
  * @returns {Promise<{configFilePath:string|undefined,basePath:string}>} Location information for
561
615
  * the config file.
562
616
  */
563
- async #locateConfigFileToUse() {
564
-
565
- // check cache first
566
- if (this.#configFilePath) {
567
- return this.#configFilePath;
568
- }
569
-
570
- const configFilenames = this.#options.allowTS
571
- ? [...FLAT_CONFIG_FILENAMES, ...TS_FLAT_CONFIG_FILENAMES]
572
- : FLAT_CONFIG_FILENAMES;
573
-
574
- // determine where to load config file from
575
- let configFilePath;
576
- const {
577
- cwd,
578
- configFile: useConfigFile
579
- } = this.#options;
580
- let basePath = cwd;
581
-
582
- if (typeof useConfigFile === "string") {
583
- debug(`[Legacy]: Override config file path is ${useConfigFile}`);
584
- configFilePath = path.resolve(cwd, useConfigFile);
585
- basePath = cwd;
586
- } else if (useConfigFile !== false) {
587
- debug("[Legacy]: Searching for eslint.config.js");
588
- configFilePath = await findUp(
589
- configFilenames,
590
- { cwd }
591
- );
592
-
593
- if (configFilePath) {
594
- basePath = path.dirname(configFilePath);
595
- }
596
-
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
+ });
597
624
  }
598
625
 
599
- // cache the result
600
- this.#configFilePath = { configFilePath, basePath };
601
-
602
- return {
603
- configFilePath,
604
- basePath
605
- };
606
-
626
+ return this.#configFilePath;
607
627
  }
608
628
 
609
629
  /**
@@ -619,12 +639,13 @@ class LegacyConfigLoader extends ConfigLoader {
619
639
  return this.#configArray;
620
640
  }
621
641
 
622
- 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);
623
644
 
624
- // cache the config array for this instance
625
- this.#configArray = configs;
645
+ // Unwrap the promise. This is primarily for the sync `getCachedConfigArrayForPath` method.
646
+ this.#configArray = await this.#configArray;
626
647
 
627
- return configs;
648
+ return this.#configArray;
628
649
  }
629
650
 
630
651
 
@@ -687,6 +708,10 @@ class LegacyConfigLoader extends ConfigLoader {
687
708
  throw new Error(`Could not find config file for ${dirPath}`);
688
709
  }
689
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
+
690
715
  return this.#configArray;
691
716
  }
692
717
  }
@@ -188,10 +188,6 @@ class Config {
188
188
  this.plugins = plugins;
189
189
  this.language = language;
190
190
 
191
- if (languageOptions) {
192
- this.languageOptions = languageOptions;
193
- }
194
-
195
191
  // Check language value
196
192
  const { pluginName: languagePluginName, objectName: localLanguageName } = splitPluginIdentifier(language);
197
193
 
@@ -203,13 +199,20 @@ class Config {
203
199
 
204
200
  this.language = plugins[languagePluginName].languages[localLanguageName];
205
201
 
202
+ if (this.language.defaultLanguageOptions ?? languageOptions) {
203
+ this.languageOptions = flatConfigSchema.languageOptions.merge(
204
+ this.language.defaultLanguageOptions,
205
+ languageOptions
206
+ );
207
+ } else {
208
+ this.languageOptions = {};
209
+ }
210
+
206
211
  // Validate language options
207
- if (this.languageOptions) {
208
- try {
209
- this.language.validateLanguageOptions(this.languageOptions);
210
- } catch (error) {
211
- throw new TypeError(`Key "languageOptions": ${error.message}`, { cause: error });
212
- }
212
+ try {
213
+ this.language.validateLanguageOptions(this.languageOptions);
214
+ } catch (error) {
215
+ throw new TypeError(`Key "languageOptions": ${error.message}`, { cause: error });
213
216
  }
214
217
 
215
218
  // Check processor value
@@ -15,7 +15,7 @@ const Rules = require("../rules");
15
15
  // Helpers
16
16
  //-----------------------------------------------------------------------------
17
17
 
18
- exports.defaultConfig = [
18
+ exports.defaultConfig = Object.freeze([
19
19
  {
20
20
  plugins: {
21
21
  "@": {
@@ -42,12 +42,6 @@ exports.defaultConfig = [
42
42
  }
43
43
  },
44
44
  language: "@/js",
45
- languageOptions: {
46
- sourceType: "module",
47
- ecmaVersion: "latest",
48
- parser: require("espree"),
49
- parserOptions: {}
50
- },
51
45
  linterOptions: {
52
46
  reportUnusedDisableDirectives: 1
53
47
  }
@@ -72,4 +66,4 @@ exports.defaultConfig = [
72
66
  ecmaVersion: "latest"
73
67
  }
74
68
  }
75
- ];
69
+ ]);