eslint 9.0.0-rc.0 → 9.1.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
@@ -59,15 +59,11 @@ After that, you can run ESLint on any file or directory like this:
59
59
 
60
60
  ## Configuration
61
61
 
62
- After running `npm init @eslint/config`, you'll have an `.eslintrc` file in your directory. In it, you'll see some rules configured like this:
63
-
64
- ```json
65
- {
66
- "rules": {
67
- "semi": ["error", "always"],
68
- "quotes": ["error", "double"]
69
- }
70
- }
62
+ After running `npm init @eslint/config`, you'll have an `eslint.config.js` (or `eslint.config.mjs`) file in your directory. In it, you'll see some rules configured like this:
63
+
64
+ ```js
65
+ import pluginJs from "@eslint/js";
66
+ export default [ pluginJs.configs.recommended, ];
71
67
  ```
72
68
 
73
69
  The names `"semi"` and `"quotes"` are the names of [rules](https://eslint.org/docs/rules) in ESLint. The first value is the error level of the rule and can be one of these values:
@@ -304,9 +300,9 @@ The following companies, organizations, and individuals support ESLint's ongoing
304
300
  <!--sponsorsstart-->
305
301
  <h3>Platinum Sponsors</h3>
306
302
  <p><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>
307
- <p><a href="https://bitwarden.com"><img src="https://avatars.githubusercontent.com/u/15990069?v=4" alt="Bitwarden" height="96"></a> <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>
303
+ <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>
308
304
  <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>
309
- <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>
305
+ <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://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>
310
306
  <!--sponsorsend-->
311
307
 
312
308
  ## Technology Sponsors
package/conf/globals.js CHANGED
@@ -70,6 +70,7 @@ const es2015 = {
70
70
  Int16Array: false,
71
71
  Int32Array: false,
72
72
  Int8Array: false,
73
+ Intl: false,
73
74
  Map: false,
74
75
  Promise: false,
75
76
  Proxy: false,
package/lib/cli.js CHANGED
@@ -19,7 +19,7 @@ const fs = require("fs"),
19
19
  path = require("path"),
20
20
  { promisify } = require("util"),
21
21
  { LegacyESLint } = require("./eslint"),
22
- { ESLint, shouldUseFlatConfig } = require("./eslint/eslint"),
22
+ { ESLint, shouldUseFlatConfig, locateConfigFileToUse } = require("./eslint/eslint"),
23
23
  createCLIOptions = require("./options"),
24
24
  log = require("./shared/logging"),
25
25
  RuntimeInfo = require("./shared/runtime-info"),
@@ -131,6 +131,7 @@ async function translateOptions({
131
131
  resolvePluginsRelativeTo,
132
132
  rule,
133
133
  rulesdir,
134
+ stats,
134
135
  warnIgnored,
135
136
  passOnNoPatterns,
136
137
  maxWarnings
@@ -222,6 +223,7 @@ async function translateOptions({
222
223
 
223
224
  if (configType === "flat") {
224
225
  options.ignorePatterns = ignorePattern;
226
+ options.stats = stats;
225
227
  options.warnIgnored = warnIgnored;
226
228
 
227
229
  /*
@@ -334,6 +336,27 @@ async function printResults(engine, results, format, outputFile, resultsMeta) {
334
336
  */
335
337
  const cli = {
336
338
 
339
+ /**
340
+ * Calculates the command string for the --inspect-config operation.
341
+ * @param {string} configFile The path to the config file to inspect.
342
+ * @returns {Promise<string>} The command string to execute.
343
+ */
344
+ async calculateInspectConfigFlags(configFile) {
345
+
346
+ // find the config file
347
+ const {
348
+ configFilePath,
349
+ basePath,
350
+ error
351
+ } = await locateConfigFileToUse({ cwd: process.cwd(), configFile });
352
+
353
+ if (error) {
354
+ throw error;
355
+ }
356
+
357
+ return ["--config", configFilePath, "--basePath", basePath];
358
+ },
359
+
337
360
  /**
338
361
  * Executes the CLI based on an array of arguments that is passed in.
339
362
  * @param {string|Array|Object} args The arguments to process.
@@ -423,6 +446,24 @@ const cli = {
423
446
  return 0;
424
447
  }
425
448
 
449
+ if (options.inspectConfig) {
450
+
451
+ log.info("You can also run this command directly using 'npx @eslint/config-inspector' in the same directory as your configuration file.");
452
+
453
+ try {
454
+ const flatOptions = await translateOptions(options, "flat");
455
+ const spawn = require("cross-spawn");
456
+ const flags = await cli.calculateInspectConfigFlags(flatOptions.overrideConfigFile);
457
+
458
+ spawn.sync("npx", ["@eslint/config-inspector", ...flags], { encoding: "utf8", stdio: "inherit" });
459
+ } catch (error) {
460
+ log.error(error);
461
+ return 2;
462
+ }
463
+
464
+ return 0;
465
+ }
466
+
426
467
  debug(`Running on ${useStdin ? "text" : "files"}`);
427
468
 
428
469
  if (options.fix && options.fixDryRun) {
@@ -18,6 +18,11 @@ const { defaultConfig } = require("./default-config");
18
18
  // Helpers
19
19
  //-----------------------------------------------------------------------------
20
20
 
21
+ /**
22
+ * Fields that are considered metadata and not part of the config object.
23
+ */
24
+ const META_FIELDS = new Set(["name"]);
25
+
21
26
  const ruleValidator = new RuleValidator();
22
27
 
23
28
  /**
@@ -74,7 +79,53 @@ function getObjectId(object) {
74
79
  return name;
75
80
  }
76
81
 
82
+ /**
83
+ * Wraps a config error with details about where the error occurred.
84
+ * @param {Error} error The original error.
85
+ * @param {number} originalLength The original length of the config array.
86
+ * @param {number} baseLength The length of the base config.
87
+ * @returns {TypeError} The new error with details.
88
+ */
89
+ function wrapConfigErrorWithDetails(error, originalLength, baseLength) {
90
+
91
+ let location = "user-defined";
92
+ let configIndex = error.index;
93
+
94
+ /*
95
+ * A config array is set up in this order:
96
+ * 1. Base config
97
+ * 2. Original configs
98
+ * 3. User-defined configs
99
+ * 4. CLI-defined configs
100
+ *
101
+ * So we need to adjust the index to account for the base config.
102
+ *
103
+ * - If the index is less than the base length, it's in the base config
104
+ * (as specified by `baseConfig` argument to `FlatConfigArray` constructor).
105
+ * - If the index is greater than the base length but less than the original
106
+ * length + base length, it's in the original config. The original config
107
+ * is passed to the `FlatConfigArray` constructor as the first argument.
108
+ * - Otherwise, it's in the user-defined config, which is loaded from the
109
+ * config file and merged with any command-line options.
110
+ */
111
+ if (error.index < baseLength) {
112
+ location = "base";
113
+ } else if (error.index < originalLength + baseLength) {
114
+ location = "original";
115
+ configIndex = error.index - baseLength;
116
+ } else {
117
+ configIndex = error.index - originalLength - baseLength;
118
+ }
119
+
120
+ return new TypeError(
121
+ `${error.message.slice(0, -1)} at ${location} index ${configIndex}.`,
122
+ { cause: error }
123
+ );
124
+ }
125
+
77
126
  const originalBaseConfig = Symbol("originalBaseConfig");
127
+ const originalLength = Symbol("originalLength");
128
+ const baseLength = Symbol("baseLength");
78
129
 
79
130
  //-----------------------------------------------------------------------------
80
131
  // Exports
@@ -101,12 +152,24 @@ class FlatConfigArray extends ConfigArray {
101
152
  schema: flatConfigSchema
102
153
  });
103
154
 
155
+ /**
156
+ * The original length of the array before any modifications.
157
+ * @type {number}
158
+ */
159
+ this[originalLength] = this.length;
160
+
104
161
  if (baseConfig[Symbol.iterator]) {
105
162
  this.unshift(...baseConfig);
106
163
  } else {
107
164
  this.unshift(baseConfig);
108
165
  }
109
166
 
167
+ /**
168
+ * The length of the array after applying the base config.
169
+ * @type {number}
170
+ */
171
+ this[baseLength] = this.length - this[originalLength];
172
+
110
173
  /**
111
174
  * The base config used to build the config array.
112
175
  * @type {Array<FlatConfig>}
@@ -124,6 +187,49 @@ class FlatConfigArray extends ConfigArray {
124
187
  Object.defineProperty(this, "shouldIgnore", { writable: false });
125
188
  }
126
189
 
190
+ /**
191
+ * Normalizes the array by calling the superclass method and catching/rethrowing
192
+ * any ConfigError exceptions with additional details.
193
+ * @param {any} [context] The context to use to normalize the array.
194
+ * @returns {Promise<FlatConfigArray>} A promise that resolves when the array is normalized.
195
+ */
196
+ normalize(context) {
197
+ return super.normalize(context)
198
+ .catch(error => {
199
+ if (error.name === "ConfigError") {
200
+ throw wrapConfigErrorWithDetails(error, this[originalLength], this[baseLength]);
201
+ }
202
+
203
+ throw error;
204
+
205
+ });
206
+ }
207
+
208
+ /**
209
+ * Normalizes the array by calling the superclass method and catching/rethrowing
210
+ * any ConfigError exceptions with additional details.
211
+ * @param {any} [context] The context to use to normalize the array.
212
+ * @returns {FlatConfigArray} The current instance.
213
+ * @throws {TypeError} If the config is invalid.
214
+ */
215
+ normalizeSync(context) {
216
+
217
+ try {
218
+
219
+ return super.normalizeSync(context);
220
+
221
+ } catch (error) {
222
+
223
+ if (error.name === "ConfigError") {
224
+ throw wrapConfigErrorWithDetails(error, this[originalLength], this[baseLength]);
225
+ }
226
+
227
+ throw error;
228
+
229
+ }
230
+
231
+ }
232
+
127
233
  /* eslint-disable class-methods-use-this -- Desired as instance method */
128
234
  /**
129
235
  * Replaces a config with another config to allow us to put strings
@@ -135,15 +241,15 @@ class FlatConfigArray extends ConfigArray {
135
241
  [ConfigArraySymbol.preprocessConfig](config) {
136
242
 
137
243
  /*
138
- * If `shouldIgnore` is false, we remove any ignore patterns specified
139
- * in the config so long as it's not a default config and it doesn't
140
- * have a `files` entry.
244
+ * If a config object has `ignores` and no other non-meta fields, then it's an object
245
+ * for global ignores. If `shouldIgnore` is false, that object shouldn't apply,
246
+ * so we'll remove its `ignores`.
141
247
  */
142
248
  if (
143
249
  !this.shouldIgnore &&
144
250
  !this[originalBaseConfig].includes(config) &&
145
251
  config.ignores &&
146
- !config.files
252
+ Object.keys(config).filter(key => !META_FIELDS.has(key)).length === 1
147
253
  ) {
148
254
  /* eslint-disable-next-line no-unused-vars -- need to strip off other keys */
149
255
  const { ignores, ...otherKeys } = config;
@@ -15,7 +15,6 @@ const fsp = fs.promises;
15
15
  const isGlob = require("is-glob");
16
16
  const hash = require("../cli-engine/hash");
17
17
  const minimatch = require("minimatch");
18
- const util = require("util");
19
18
  const fswalk = require("@nodelib/fs.walk");
20
19
  const globParent = require("glob-parent");
21
20
  const isPathInside = require("is-path-inside");
@@ -24,7 +23,6 @@ const isPathInside = require("is-path-inside");
24
23
  // Fixup references
25
24
  //-----------------------------------------------------------------------------
26
25
 
27
- const doFsWalk = util.promisify(fswalk.walk);
28
26
  const Minimatch = minimatch.Minimatch;
29
27
  const MINIMATCH_OPTIONS = { dot: true };
30
28
 
@@ -280,56 +278,92 @@ async function globSearch({
280
278
  */
281
279
  const unmatchedPatterns = new Set([...relativeToPatterns.keys()]);
282
280
 
283
- const filePaths = (await doFsWalk(basePath, {
281
+ const filePaths = (await new Promise((resolve, reject) => {
284
282
 
285
- deepFilter(entry) {
286
- const relativePath = normalizeToPosix(path.relative(basePath, entry.path));
287
- const matchesPattern = matchers.some(matcher => matcher.match(relativePath, true));
288
-
289
- return matchesPattern && !configs.isDirectoryIgnored(entry.path);
290
- },
291
- entryFilter(entry) {
292
- const relativePath = normalizeToPosix(path.relative(basePath, entry.path));
283
+ let promiseRejected = false;
293
284
 
294
- // entries may be directories or files so filter out directories
295
- if (entry.dirent.isDirectory()) {
285
+ /**
286
+ * Wraps a boolean-returning filter function. The wrapped function will reject the promise if an error occurs.
287
+ * @param {Function} filter A filter function to wrap.
288
+ * @returns {Function} A function similar to the wrapped filter that rejects the promise if an error occurs.
289
+ */
290
+ function wrapFilter(filter) {
291
+ return (...args) => {
292
+
293
+ // No need to run the filter if an error has been thrown.
294
+ if (!promiseRejected) {
295
+ try {
296
+ return filter(...args);
297
+ } catch (error) {
298
+ promiseRejected = true;
299
+ reject(error);
300
+ }
301
+ }
296
302
  return false;
297
- }
303
+ };
304
+ }
298
305
 
299
- /*
300
- * Optimization: We need to track when patterns are left unmatched
301
- * and so we use `unmatchedPatterns` to do that. There is a bit of
302
- * complexity here because the same file can be matched by more than
303
- * one pattern. So, when we start, we actually need to test every
304
- * pattern against every file. Once we know there are no remaining
305
- * unmatched patterns, then we can switch to just looking for the
306
- * first matching pattern for improved speed.
307
- */
308
- const matchesPattern = unmatchedPatterns.size > 0
309
- ? matchers.reduce((previousValue, matcher) => {
310
- const pathMatches = matcher.match(relativePath);
306
+ fswalk.walk(
307
+ basePath,
308
+ {
309
+ deepFilter: wrapFilter(entry => {
310
+ const relativePath = normalizeToPosix(path.relative(basePath, entry.path));
311
+ const matchesPattern = matchers.some(matcher => matcher.match(relativePath, true));
312
+
313
+ return matchesPattern && !configs.isDirectoryIgnored(entry.path);
314
+ }),
315
+ entryFilter: wrapFilter(entry => {
316
+ const relativePath = normalizeToPosix(path.relative(basePath, entry.path));
317
+
318
+ // entries may be directories or files so filter out directories
319
+ if (entry.dirent.isDirectory()) {
320
+ return false;
321
+ }
311
322
 
312
323
  /*
313
- * We updated the unmatched patterns set only if the path
314
- * matches and the file isn't ignored. If the file is
315
- * ignored, that means there wasn't a match for the
316
- * pattern so it should not be removed.
317
- *
318
- * Performance note: isFileIgnored() aggressively caches
319
- * results so there is no performance penalty for calling
320
- * it twice with the same argument.
324
+ * Optimization: We need to track when patterns are left unmatched
325
+ * and so we use `unmatchedPatterns` to do that. There is a bit of
326
+ * complexity here because the same file can be matched by more than
327
+ * one pattern. So, when we start, we actually need to test every
328
+ * pattern against every file. Once we know there are no remaining
329
+ * unmatched patterns, then we can switch to just looking for the
330
+ * first matching pattern for improved speed.
321
331
  */
322
- if (pathMatches && !configs.isFileIgnored(entry.path)) {
323
- unmatchedPatterns.delete(matcher.pattern);
324
- }
325
-
326
- return pathMatches || previousValue;
327
- }, false)
328
- : matchers.some(matcher => matcher.match(relativePath));
329
-
330
- return matchesPattern && !configs.isFileIgnored(entry.path);
331
- }
332
-
332
+ const matchesPattern = unmatchedPatterns.size > 0
333
+ ? matchers.reduce((previousValue, matcher) => {
334
+ const pathMatches = matcher.match(relativePath);
335
+
336
+ /*
337
+ * We updated the unmatched patterns set only if the path
338
+ * matches and the file isn't ignored. If the file is
339
+ * ignored, that means there wasn't a match for the
340
+ * pattern so it should not be removed.
341
+ *
342
+ * Performance note: isFileIgnored() aggressively caches
343
+ * results so there is no performance penalty for calling
344
+ * it twice with the same argument.
345
+ */
346
+ if (pathMatches && !configs.isFileIgnored(entry.path)) {
347
+ unmatchedPatterns.delete(matcher.pattern);
348
+ }
349
+
350
+ return pathMatches || previousValue;
351
+ }, false)
352
+ : matchers.some(matcher => matcher.match(relativePath));
353
+
354
+ return matchesPattern && !configs.isFileIgnored(entry.path);
355
+ })
356
+ },
357
+ (error, entries) => {
358
+
359
+ // If the promise is already rejected, calling `resolve` or `reject` will do nothing.
360
+ if (error) {
361
+ reject(error);
362
+ } else {
363
+ resolve(entries);
364
+ }
365
+ }
366
+ );
333
367
  })).map(entry => entry.path);
334
368
 
335
369
  // now check to see if we have any unmatched patterns
@@ -685,6 +719,7 @@ function processOptions({
685
719
  overrideConfig = null,
686
720
  overrideConfigFile = null,
687
721
  plugins = {},
722
+ stats = false,
688
723
  warnIgnored = true,
689
724
  passOnNoPatterns = false,
690
725
  ruleFilter = () => true,
@@ -791,6 +826,9 @@ function processOptions({
791
826
  if (Array.isArray(plugins)) {
792
827
  errors.push("'plugins' doesn't add plugins to configuration to load. Please use the 'overrideConfig.plugins' option instead.");
793
828
  }
829
+ if (typeof stats !== "boolean") {
830
+ errors.push("'stats' must be a boolean.");
831
+ }
794
832
  if (typeof warnIgnored !== "boolean") {
795
833
  errors.push("'warnIgnored' must be a boolean.");
796
834
  }
@@ -818,6 +856,7 @@ function processOptions({
818
856
  globInputPaths,
819
857
  ignore,
820
858
  ignorePatterns,
859
+ stats,
821
860
  passOnNoPatterns,
822
861
  warnIgnored,
823
862
  ruleFilter
@@ -42,6 +42,7 @@ const {
42
42
  const { pathToFileURL } = require("url");
43
43
  const { FlatConfigArray } = require("../config/flat-config-array");
44
44
  const LintResultCache = require("../cli-engine/lint-result-cache");
45
+ const { Retrier } = require("@humanwhocodes/retry");
45
46
 
46
47
  /*
47
48
  * This is necessary to allow overwriting writeFile for testing purposes.
@@ -84,6 +85,7 @@ const LintResultCache = require("../cli-engine/lint-result-cache");
84
85
  * doesn't do any config file lookup when `true`; considered to be a config filename
85
86
  * when a string.
86
87
  * @property {Record<string,Plugin>} [plugins] An array of plugin implementations.
88
+ * @property {boolean} [stats] True enables added statistics on lint results.
87
89
  * @property {boolean} warnIgnored Show warnings when the file list includes ignored files
88
90
  * @property {boolean} [passOnNoPatterns=false] When set to true, missing patterns cause
89
91
  * the linting operation to short circuit and not report any failures.
@@ -465,6 +467,7 @@ async function calculateConfigArray(eslint, {
465
467
  * @param {boolean} config.fix If `true` then it does fix.
466
468
  * @param {boolean} config.allowInlineConfig If `true` then it uses directive comments.
467
469
  * @param {Function} config.ruleFilter A predicate function to filter which rules should be run.
470
+ * @param {boolean} config.stats If `true`, then if reports extra statistics with the lint results.
468
471
  * @param {Linter} config.linter The linter instance to verify.
469
472
  * @returns {LintResult} The result of linting.
470
473
  * @private
@@ -477,6 +480,7 @@ function verifyText({
477
480
  fix,
478
481
  allowInlineConfig,
479
482
  ruleFilter,
483
+ stats,
480
484
  linter
481
485
  }) {
482
486
  const filePath = providedFilePath || "<text>";
@@ -497,6 +501,7 @@ function verifyText({
497
501
  filename: filePathToVerify,
498
502
  fix,
499
503
  ruleFilter,
504
+ stats,
500
505
 
501
506
  /**
502
507
  * Check if the linter should adopt a given code block or not.
@@ -528,6 +533,13 @@ function verifyText({
528
533
  result.source = text;
529
534
  }
530
535
 
536
+ if (stats) {
537
+ result.stats = {
538
+ times: linter.getTimes(),
539
+ fixPasses: linter.getFixPassCount()
540
+ };
541
+ }
542
+
531
543
  return result;
532
544
  }
533
545
 
@@ -808,6 +820,7 @@ class ESLint {
808
820
  fix,
809
821
  fixTypes,
810
822
  ruleFilter,
823
+ stats,
811
824
  globInputPaths,
812
825
  errorOnUnmatchedPattern,
813
826
  warnIgnored
@@ -839,6 +852,8 @@ class ESLint {
839
852
  errorOnUnmatchedPattern
840
853
  });
841
854
  const controller = new AbortController();
855
+ const retryCodes = new Set(["ENFILE", "EMFILE"]);
856
+ const retrier = new Retrier(error => retryCodes.has(error.code));
842
857
 
843
858
  debug(`${filePaths.length} files found in: ${Date.now() - startTime}ms`);
844
859
 
@@ -907,7 +922,7 @@ class ESLint {
907
922
  fixer = message => shouldMessageBeFixed(message, config, fixTypesSet) && originalFix(message);
908
923
  }
909
924
 
910
- return fs.readFile(filePath, { encoding: "utf8", signal: controller.signal })
925
+ return retrier.retry(() => fs.readFile(filePath, { encoding: "utf8", signal: controller.signal })
911
926
  .then(text => {
912
927
 
913
928
  // fail immediately if an error occurred in another file
@@ -922,6 +937,7 @@ class ESLint {
922
937
  fix: fixer,
923
938
  allowInlineConfig,
924
939
  ruleFilter,
940
+ stats,
925
941
  linter
926
942
  });
927
943
 
@@ -936,11 +952,11 @@ class ESLint {
936
952
  }
937
953
 
938
954
  return result;
939
- }).catch(error => {
955
+ }))
956
+ .catch(error => {
940
957
  controller.abort(error);
941
958
  throw error;
942
959
  });
943
-
944
960
  })
945
961
  );
946
962
 
@@ -1010,7 +1026,8 @@ class ESLint {
1010
1026
  cwd,
1011
1027
  fix,
1012
1028
  warnIgnored: constructorWarnIgnored,
1013
- ruleFilter
1029
+ ruleFilter,
1030
+ stats
1014
1031
  } = eslintOptions;
1015
1032
  const results = [];
1016
1033
  const startTime = Date.now();
@@ -1034,6 +1051,7 @@ class ESLint {
1034
1051
  fix,
1035
1052
  allowInlineConfig,
1036
1053
  ruleFilter,
1054
+ stats,
1037
1055
  linter
1038
1056
  }));
1039
1057
  }
@@ -1199,5 +1217,6 @@ async function shouldUseFlatConfig() {
1199
1217
 
1200
1218
  module.exports = {
1201
1219
  ESLint,
1202
- shouldUseFlatConfig
1220
+ shouldUseFlatConfig,
1221
+ locateConfigFileToUse
1203
1222
  };