eslint 9.13.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
@@ -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://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>
@@ -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
  }
@@ -421,20 +421,19 @@ async function globMultiSearch({ searches, configLoader, errorOnUnmatchedPattern
421
421
  )
422
422
  );
423
423
 
424
- const filePaths = [];
425
-
424
+ /*
425
+ * The first loop handles errors from the glob searches. Since we can't
426
+ * use `await` inside `flatMap`, we process errors separately in this loop.
427
+ * This results in two iterations over `results`, but since the length is
428
+ * less than or equal to the number of globs and directories passed on the
429
+ * command line, the performance impact should be minimal.
430
+ */
426
431
  for (let i = 0; i < results.length; i++) {
427
432
 
428
433
  const result = results[i];
429
434
  const currentSearch = normalizedSearches[i];
430
435
 
431
436
  if (result.status === "fulfilled") {
432
-
433
- // if the search was successful just add the results
434
- if (result.value.length > 0) {
435
- filePaths.push(...result.value);
436
- }
437
-
438
437
  continue;
439
438
  }
440
439
 
@@ -457,7 +456,8 @@ async function globMultiSearch({ searches, configLoader, errorOnUnmatchedPattern
457
456
 
458
457
  }
459
458
 
460
- return filePaths;
459
+ // second loop for `fulfulled` results
460
+ return results.flatMap(result => result.value);
461
461
 
462
462
  }
463
463
 
@@ -746,7 +746,7 @@ class ESLint {
746
746
  });
747
747
  const controller = new AbortController();
748
748
  const retryCodes = new Set(["ENFILE", "EMFILE"]);
749
- const retrier = new Retrier(error => retryCodes.has(error.code));
749
+ const retrier = new Retrier(error => retryCodes.has(error.code), { concurrency: 100 });
750
750
 
751
751
  debug(`${filePaths.length} files found in: ${Date.now() - startTime}ms`);
752
752
 
@@ -830,7 +830,7 @@ class ESLint {
830
830
  }
831
831
 
832
832
  return result;
833
- }))
833
+ }), { signal: controller.signal })
834
834
  .catch(error => {
835
835
  controller.abort(error);
836
836
  throw error;
@@ -238,6 +238,13 @@ module.exports = {
238
238
  return;
239
239
  }
240
240
 
241
+ /*
242
+ * Import attribute keys are always ignored
243
+ */
244
+ if (astUtils.isImportAttributeKey(node)) {
245
+ return;
246
+ }
247
+
241
248
  report(node);
242
249
  }
243
250
 
@@ -272,7 +279,7 @@ module.exports = {
272
279
  for (const reference of scope.through) {
273
280
  const id = reference.identifier;
274
281
 
275
- if (isGoodName(id.name)) {
282
+ if (isGoodName(id.name) || astUtils.isImportAttributeKey(id)) {
276
283
  continue;
277
284
  }
278
285
 
@@ -326,7 +333,7 @@ module.exports = {
326
333
  "MethodDefinition > PrivateIdentifier.key",
327
334
  "PropertyDefinition > PrivateIdentifier.key"
328
335
  ]](node) {
329
- if (properties === "never" || isGoodName(node.name)) {
336
+ if (properties === "never" || astUtils.isImportAttributeKey(node) || isGoodName(node.name)) {
330
337
  return;
331
338
  }
332
339
  report(node);
@@ -6,6 +6,12 @@
6
6
 
7
7
  "use strict";
8
8
 
9
+ //------------------------------------------------------------------------------
10
+ // Requirements
11
+ //------------------------------------------------------------------------------
12
+
13
+ const astUtils = require("./utils/ast-utils");
14
+
9
15
  //------------------------------------------------------------------------------
10
16
  // Helpers
11
17
  //------------------------------------------------------------------------------
@@ -154,6 +160,12 @@ module.exports = {
154
160
  * @returns {boolean} `true` if the node should be checked.
155
161
  */
156
162
  function shouldCheck(node) {
163
+
164
+ // Import attributes are defined by environments, so naming conventions shouldn't apply to them
165
+ if (astUtils.isImportAttributeKey(node)) {
166
+ return false;
167
+ }
168
+
157
169
  const parent = node.parent;
158
170
 
159
171
  /*
@@ -11,7 +11,7 @@
11
11
  //------------------------------------------------------------------------------
12
12
 
13
13
  const { getGraphemeCount } = require("../shared/string-utils");
14
- const { getModuleExportName } = require("./utils/ast-utils");
14
+ const { getModuleExportName, isImportAttributeKey } = require("./utils/ast-utils");
15
15
 
16
16
  //------------------------------------------------------------------------------
17
17
  // Rule Definition
@@ -115,7 +115,7 @@ module.exports = {
115
115
  isKeyAndValueSame && parent.key === node && properties
116
116
  );
117
117
  }
118
- return properties && !parent.computed && parent.key.name === node.name;
118
+ return properties && !isImportAttributeKey(node) && !parent.computed && parent.key.name === node.name;
119
119
  },
120
120
  ImportSpecifier(parent, node) {
121
121
  return (
@@ -5,6 +5,12 @@
5
5
 
6
6
  "use strict";
7
7
 
8
+ //------------------------------------------------------------------------------
9
+ // Requirements
10
+ //------------------------------------------------------------------------------
11
+
12
+ const astUtils = require("./utils/ast-utils");
13
+
8
14
  //------------------------------------------------------------------------------
9
15
  // Rule Definition
10
16
  //------------------------------------------------------------------------------
@@ -180,7 +186,7 @@ module.exports = {
180
186
  parent = node.parent,
181
187
  effectiveParent = (parent.type === "MemberExpression") ? parent.parent : parent;
182
188
 
183
- if (isReferenceToGlobalVariable(node)) {
189
+ if (isReferenceToGlobalVariable(node) || astUtils.isImportAttributeKey(node)) {
184
190
  return;
185
191
  }
186
192
 
@@ -1054,7 +1054,7 @@ let needsPrecedingSemicolon;
1054
1054
  {
1055
1055
  const BREAK_OR_CONTINUE = new Set(["BreakStatement", "ContinueStatement"]);
1056
1056
 
1057
- // Declaration types that must contain a string Literal node at the end.
1057
+ // Declaration types that cannot be continued by a punctuator when ending with a string Literal that is a direct child.
1058
1058
  const DECLARATIONS = new Set(["ExportAllDeclaration", "ExportNamedDeclaration", "ImportDeclaration"]);
1059
1059
 
1060
1060
  const IDENTIFIER_OR_KEYWORD = new Set(["Identifier", "Keyword"]);
@@ -1132,6 +1132,48 @@ let needsPrecedingSemicolon;
1132
1132
  };
1133
1133
  }
1134
1134
 
1135
+ /**
1136
+ * Checks if a node is used as an import attribute key, either in a static or dynamic import.
1137
+ * @param {ASTNode} node The node to check.
1138
+ * @returns {boolean} Whether the node is used as an import attribute key.
1139
+ */
1140
+ function isImportAttributeKey(node) {
1141
+ const { parent } = node;
1142
+
1143
+ // static import/re-export
1144
+ if (parent.type === "ImportAttribute" && parent.key === node) {
1145
+ return true;
1146
+ }
1147
+
1148
+ // dynamic import
1149
+ if (
1150
+ parent.type === "Property" &&
1151
+ !parent.computed &&
1152
+ (parent.key === node || parent.value === node && parent.shorthand && !parent.method) &&
1153
+ parent.parent.type === "ObjectExpression"
1154
+ ) {
1155
+ const objectExpression = parent.parent;
1156
+ const objectExpressionParent = objectExpression.parent;
1157
+
1158
+ if (
1159
+ objectExpressionParent.type === "ImportExpression" &&
1160
+ objectExpressionParent.options === objectExpression
1161
+ ) {
1162
+ return true;
1163
+ }
1164
+
1165
+ // nested key
1166
+ if (
1167
+ objectExpressionParent.type === "Property" &&
1168
+ objectExpressionParent.value === objectExpression
1169
+ ) {
1170
+ return isImportAttributeKey(objectExpressionParent.key);
1171
+ }
1172
+ }
1173
+
1174
+ return false;
1175
+ }
1176
+
1135
1177
  //------------------------------------------------------------------------------
1136
1178
  // Public Interface
1137
1179
  //------------------------------------------------------------------------------
@@ -2288,5 +2330,6 @@ module.exports = {
2288
2330
  isTopLevelExpressionStatement,
2289
2331
  isDirective,
2290
2332
  isStartOfExpressionStatement,
2291
- needsPrecedingSemicolon
2333
+ needsPrecedingSemicolon,
2334
+ isImportAttributeKey
2292
2335
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint",
3
- "version": "9.13.0",
3
+ "version": "9.14.0",
4
4
  "author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
5
5
  "description": "An AST-based pattern checker for JavaScript.",
6
6
  "type": "commonjs",
@@ -98,15 +98,15 @@
98
98
  "bugs": "https://github.com/eslint/eslint/issues/",
99
99
  "dependencies": {
100
100
  "@eslint-community/eslint-utils": "^4.2.0",
101
- "@eslint-community/regexpp": "^4.11.0",
101
+ "@eslint-community/regexpp": "^4.12.1",
102
102
  "@eslint/config-array": "^0.18.0",
103
103
  "@eslint/core": "^0.7.0",
104
104
  "@eslint/eslintrc": "^3.1.0",
105
- "@eslint/js": "9.13.0",
105
+ "@eslint/js": "9.14.0",
106
106
  "@eslint/plugin-kit": "^0.2.0",
107
- "@humanfs/node": "^0.16.5",
107
+ "@humanfs/node": "^0.16.6",
108
108
  "@humanwhocodes/module-importer": "^1.0.1",
109
- "@humanwhocodes/retry": "^0.3.1",
109
+ "@humanwhocodes/retry": "^0.4.0",
110
110
  "@types/estree": "^1.0.6",
111
111
  "@types/json-schema": "^7.0.15",
112
112
  "ajv": "^6.12.4",
@@ -114,9 +114,9 @@
114
114
  "cross-spawn": "^7.0.2",
115
115
  "debug": "^4.3.2",
116
116
  "escape-string-regexp": "^4.0.0",
117
- "eslint-scope": "^8.1.0",
118
- "eslint-visitor-keys": "^4.1.0",
119
- "espree": "^10.2.0",
117
+ "eslint-scope": "^8.2.0",
118
+ "eslint-visitor-keys": "^4.2.0",
119
+ "espree": "^10.3.0",
120
120
  "esquery": "^1.5.0",
121
121
  "esutils": "^2.0.2",
122
122
  "fast-deep-equal": "^3.1.3",
@@ -141,10 +141,10 @@
141
141
  "@trunkio/launcher": "^1.3.0",
142
142
  "@types/node": "^20.11.5",
143
143
  "@typescript-eslint/parser": "^8.4.0",
144
- "@wdio/browser-runner": "^9.0.5",
145
- "@wdio/cli": "^9.0.5",
146
- "@wdio/concise-reporter": "^9.0.4",
147
- "@wdio/mocha-framework": "^9.0.5",
144
+ "@wdio/browser-runner": "^9.2.4",
145
+ "@wdio/cli": "^9.2.4",
146
+ "@wdio/concise-reporter": "^9.2.2",
147
+ "@wdio/mocha-framework": "^9.2.2",
148
148
  "babel-loader": "^8.0.5",
149
149
  "c8": "^7.12.0",
150
150
  "chai": "^4.0.1",