eslint 8.22.0 → 8.33.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 (80) hide show
  1. package/README.md +51 -45
  2. package/bin/eslint.js +2 -4
  3. package/conf/globals.js +6 -1
  4. package/conf/rule-type-list.json +2 -2
  5. package/lib/cli-engine/file-enumerator.js +4 -2
  6. package/lib/cli-engine/formatters/formatters-meta.json +46 -0
  7. package/lib/cli-engine/formatters/html.js +76 -51
  8. package/lib/cli.js +163 -40
  9. package/lib/config/default-config.js +2 -2
  10. package/lib/config/flat-config-array.js +1 -1
  11. package/lib/eslint/eslint-helpers.js +409 -87
  12. package/lib/eslint/eslint.js +5 -2
  13. package/lib/eslint/flat-eslint.js +113 -110
  14. package/lib/linter/code-path-analysis/code-path-segment.js +2 -2
  15. package/lib/linter/code-path-analysis/code-path-state.js +7 -7
  16. package/lib/linter/code-path-analysis/debug-helpers.js +3 -3
  17. package/lib/linter/code-path-analysis/id-generator.js +2 -2
  18. package/lib/linter/config-comment-parser.js +1 -2
  19. package/lib/linter/linter.js +17 -7
  20. package/lib/linter/timing.js +4 -4
  21. package/lib/options.js +293 -239
  22. package/lib/rule-tester/flat-rule-tester.js +13 -11
  23. package/lib/rule-tester/rule-tester.js +15 -11
  24. package/lib/rules/array-callback-return.js +2 -2
  25. package/lib/rules/comma-dangle.js +3 -3
  26. package/lib/rules/for-direction.js +1 -1
  27. package/lib/rules/func-name-matching.js +2 -2
  28. package/lib/rules/getter-return.js +14 -8
  29. package/lib/rules/global-require.js +2 -1
  30. package/lib/rules/id-length.js +43 -2
  31. package/lib/rules/indent-legacy.js +4 -4
  32. package/lib/rules/indent.js +23 -15
  33. package/lib/rules/index.js +3 -0
  34. package/lib/rules/key-spacing.js +50 -38
  35. package/lib/rules/lines-around-comment.js +2 -2
  36. package/lib/rules/logical-assignment-operators.js +474 -0
  37. package/lib/rules/multiline-ternary.js +2 -2
  38. package/lib/rules/new-cap.js +2 -2
  39. package/lib/rules/no-else-return.js +1 -1
  40. package/lib/rules/no-empty-static-block.js +47 -0
  41. package/lib/rules/no-empty.js +19 -2
  42. package/lib/rules/no-extra-boolean-cast.js +1 -1
  43. package/lib/rules/no-extra-parens.js +18 -3
  44. package/lib/rules/no-fallthrough.js +26 -5
  45. package/lib/rules/no-implicit-coercion.js +20 -1
  46. package/lib/rules/no-implicit-globals.js +5 -0
  47. package/lib/rules/no-invalid-regexp.js +40 -18
  48. package/lib/rules/no-labels.js +1 -1
  49. package/lib/rules/no-lone-blocks.js +1 -1
  50. package/lib/rules/no-loss-of-precision.js +2 -2
  51. package/lib/rules/no-magic-numbers.js +18 -1
  52. package/lib/rules/no-misleading-character-class.js +4 -4
  53. package/lib/rules/no-new-native-nonconstructor.js +64 -0
  54. package/lib/rules/no-obj-calls.js +1 -1
  55. package/lib/rules/no-restricted-exports.js +106 -10
  56. package/lib/rules/no-return-await.js +28 -1
  57. package/lib/rules/no-underscore-dangle.js +36 -11
  58. package/lib/rules/no-unneeded-ternary.js +1 -1
  59. package/lib/rules/no-use-before-define.js +1 -1
  60. package/lib/rules/no-useless-computed-key.js +1 -1
  61. package/lib/rules/no-var.js +2 -2
  62. package/lib/rules/no-warning-comments.js +24 -5
  63. package/lib/rules/padded-blocks.js +1 -1
  64. package/lib/rules/prefer-arrow-callback.js +4 -3
  65. package/lib/rules/prefer-const.js +13 -1
  66. package/lib/rules/prefer-named-capture-group.js +71 -6
  67. package/lib/rules/prefer-object-spread.js +1 -1
  68. package/lib/rules/prefer-regex-literals.js +147 -32
  69. package/lib/rules/prefer-rest-params.js +1 -1
  70. package/lib/rules/require-yield.js +0 -1
  71. package/lib/rules/strict.js +1 -1
  72. package/lib/rules/utils/ast-utils.js +10 -4
  73. package/lib/shared/directives.js +15 -0
  74. package/lib/shared/logging.js +1 -1
  75. package/lib/shared/runtime-info.js +1 -1
  76. package/lib/shared/traverser.js +1 -1
  77. package/lib/shared/types.js +15 -2
  78. package/lib/source-code/token-store/cursor.js +1 -1
  79. package/messages/print-config-with-directory-path.js +1 -1
  80. package/package.json +27 -27
@@ -13,8 +13,31 @@ const path = require("path");
13
13
  const fs = require("fs");
14
14
  const fsp = fs.promises;
15
15
  const isGlob = require("is-glob");
16
- const globby = require("globby");
17
16
  const hash = require("../cli-engine/hash");
17
+ const minimatch = require("minimatch");
18
+ const util = require("util");
19
+ const fswalk = require("@nodelib/fs.walk");
20
+ const globParent = require("glob-parent");
21
+ const isPathInside = require("is-path-inside");
22
+
23
+ //-----------------------------------------------------------------------------
24
+ // Fixup references
25
+ //-----------------------------------------------------------------------------
26
+
27
+ const doFsWalk = util.promisify(fswalk.walk);
28
+ const Minimatch = minimatch.Minimatch;
29
+ const MINIMATCH_OPTIONS = { dot: true };
30
+
31
+ //-----------------------------------------------------------------------------
32
+ // Types
33
+ //-----------------------------------------------------------------------------
34
+
35
+ /**
36
+ * @typedef {Object} GlobSearch
37
+ * @property {Array<string>} patterns The normalized patterns to use for a search.
38
+ * @property {Array<string>} rawPatterns The patterns as entered by the user
39
+ * before doing any normalization.
40
+ */
18
41
 
19
42
  //-----------------------------------------------------------------------------
20
43
  // Errors
@@ -36,6 +59,30 @@ class NoFilesFoundError extends Error {
36
59
  }
37
60
  }
38
61
 
62
+ /**
63
+ * The error type when a search fails to match multiple patterns.
64
+ */
65
+ class UnmatchedSearchPatternsError extends Error {
66
+
67
+ /**
68
+ * @param {Object} options The options for the error.
69
+ * @param {string} options.basePath The directory that was searched.
70
+ * @param {Array<string>} options.unmatchedPatterns The glob patterns
71
+ * which were not found.
72
+ * @param {Array<string>} options.patterns The glob patterns that were
73
+ * searched.
74
+ * @param {Array<string>} options.rawPatterns The raw glob patterns that
75
+ * were searched.
76
+ */
77
+ constructor({ basePath, unmatchedPatterns, patterns, rawPatterns }) {
78
+ super(`No files matching '${rawPatterns}' in '${basePath}' were found.`);
79
+ this.basePath = basePath;
80
+ this.unmatchedPatterns = unmatchedPatterns;
81
+ this.patterns = patterns;
82
+ this.rawPatterns = rawPatterns;
83
+ }
84
+ }
85
+
39
86
  /**
40
87
  * The error type when there are files matched by a glob, but all of them have been ignored.
41
88
  */
@@ -66,9 +113,9 @@ function isNonEmptyString(x) {
66
113
  }
67
114
 
68
115
  /**
69
- * Check if a given value is an array of non-empty stringss or not.
116
+ * Check if a given value is an array of non-empty strings or not.
70
117
  * @param {any} x The value to check.
71
- * @returns {boolean} `true` if `x` is an array of non-empty stringss.
118
+ * @returns {boolean} `true` if `x` is an array of non-empty strings.
72
119
  */
73
120
  function isArrayOfNonEmptyString(x) {
74
121
  return Array.isArray(x) && x.every(isNonEmptyString);
@@ -96,6 +143,317 @@ function isGlobPattern(pattern) {
96
143
  return isGlob(path.sep === "\\" ? normalizeToPosix(pattern) : pattern);
97
144
  }
98
145
 
146
+
147
+ /**
148
+ * Determines if a given glob pattern will return any results.
149
+ * Used primarily to help with useful error messages.
150
+ * @param {Object} options The options for the function.
151
+ * @param {string} options.basePath The directory to search.
152
+ * @param {string} options.pattern A glob pattern to match.
153
+ * @returns {Promise<boolean>} True if there is a glob match, false if not.
154
+ */
155
+ function globMatch({ basePath, pattern }) {
156
+
157
+ let found = false;
158
+ const patternToUse = path.isAbsolute(pattern)
159
+ ? normalizeToPosix(path.relative(basePath, pattern))
160
+ : pattern;
161
+
162
+ const matcher = new Minimatch(patternToUse, MINIMATCH_OPTIONS);
163
+
164
+ const fsWalkSettings = {
165
+
166
+ deepFilter(entry) {
167
+ const relativePath = normalizeToPosix(path.relative(basePath, entry.path));
168
+
169
+ return !found && matcher.match(relativePath, true);
170
+ },
171
+
172
+ entryFilter(entry) {
173
+ if (found || entry.dirent.isDirectory()) {
174
+ return false;
175
+ }
176
+
177
+ const relativePath = normalizeToPosix(path.relative(basePath, entry.path));
178
+
179
+ if (matcher.match(relativePath)) {
180
+ found = true;
181
+ return true;
182
+ }
183
+
184
+ return false;
185
+ }
186
+ };
187
+
188
+ return new Promise(resolve => {
189
+
190
+ // using a stream so we can exit early because we just need one match
191
+ const globStream = fswalk.walkStream(basePath, fsWalkSettings);
192
+
193
+ globStream.on("data", () => {
194
+ globStream.destroy();
195
+ resolve(true);
196
+ });
197
+
198
+ // swallow errors as they're not important here
199
+ globStream.on("error", () => { });
200
+
201
+ globStream.on("end", () => {
202
+ resolve(false);
203
+ });
204
+ globStream.read();
205
+ });
206
+
207
+ }
208
+
209
+ /**
210
+ * Searches a directory looking for matching glob patterns. This uses
211
+ * the config array's logic to determine if a directory or file should
212
+ * be ignored, so it is consistent with how ignoring works throughout
213
+ * ESLint.
214
+ * @param {Object} options The options for this function.
215
+ * @param {string} options.basePath The directory to search.
216
+ * @param {Array<string>} options.patterns An array of glob patterns
217
+ * to match.
218
+ * @param {Array<string>} options.rawPatterns An array of glob patterns
219
+ * as the user inputted them. Used for errors.
220
+ * @param {FlatConfigArray} options.configs The config array to use for
221
+ * determining what to ignore.
222
+ * @param {boolean} options.errorOnUnmatchedPattern Determines if an error
223
+ * should be thrown when a pattern is unmatched.
224
+ * @returns {Promise<Array<string>>} An array of matching file paths
225
+ * or an empty array if there are no matches.
226
+ * @throws {UnmatchedSearchPatternsErrror} If there is a pattern that doesn't
227
+ * match any files.
228
+ */
229
+ async function globSearch({
230
+ basePath,
231
+ patterns,
232
+ rawPatterns,
233
+ configs,
234
+ errorOnUnmatchedPattern
235
+ }) {
236
+
237
+ if (patterns.length === 0) {
238
+ return [];
239
+ }
240
+
241
+ /*
242
+ * In this section we are converting the patterns into Minimatch
243
+ * instances for performance reasons. Because we are doing the same
244
+ * matches repeatedly, it's best to compile those patterns once and
245
+ * reuse them multiple times.
246
+ *
247
+ * To do that, we convert any patterns with an absolute path into a
248
+ * relative path and normalize it to Posix-style slashes. We also keep
249
+ * track of the relative patterns to map them back to the original
250
+ * patterns, which we need in order to throw an error if there are any
251
+ * unmatched patterns.
252
+ */
253
+ const relativeToPatterns = new Map();
254
+ const matchers = patterns.map((pattern, i) => {
255
+ const patternToUse = path.isAbsolute(pattern)
256
+ ? normalizeToPosix(path.relative(basePath, pattern))
257
+ : pattern;
258
+
259
+ relativeToPatterns.set(patternToUse, patterns[i]);
260
+
261
+ return new Minimatch(patternToUse, MINIMATCH_OPTIONS);
262
+ });
263
+
264
+ /*
265
+ * We track unmatched patterns because we may want to throw an error when
266
+ * they occur. To start, this set is initialized with all of the patterns.
267
+ * Every time a match occurs, the pattern is removed from the set, making
268
+ * it easy to tell if we have any unmatched patterns left at the end of
269
+ * search.
270
+ */
271
+ const unmatchedPatterns = new Set([...relativeToPatterns.keys()]);
272
+
273
+ const filePaths = (await doFsWalk(basePath, {
274
+
275
+ deepFilter(entry) {
276
+ const relativePath = normalizeToPosix(path.relative(basePath, entry.path));
277
+ const matchesPattern = matchers.some(matcher => matcher.match(relativePath, true));
278
+
279
+ return matchesPattern && !configs.isDirectoryIgnored(entry.path);
280
+ },
281
+ entryFilter(entry) {
282
+ const relativePath = normalizeToPosix(path.relative(basePath, entry.path));
283
+
284
+ // entries may be directories or files so filter out directories
285
+ if (entry.dirent.isDirectory()) {
286
+ return false;
287
+ }
288
+
289
+ /*
290
+ * Optimization: We need to track when patterns are left unmatched
291
+ * and so we use `unmatchedPatterns` to do that. There is a bit of
292
+ * complexity here because the same file can be matched by more than
293
+ * one pattern. So, when we start, we actually need to test every
294
+ * pattern against every file. Once we know there are no remaining
295
+ * unmatched patterns, then we can switch to just looking for the
296
+ * first matching pattern for improved speed.
297
+ */
298
+ const matchesPattern = unmatchedPatterns.size > 0
299
+ ? matchers.reduce((previousValue, matcher) => {
300
+ const pathMatches = matcher.match(relativePath);
301
+
302
+ /*
303
+ * We updated the unmatched patterns set only if the path
304
+ * matches and the file isn't ignored. If the file is
305
+ * ignored, that means there wasn't a match for the
306
+ * pattern so it should not be removed.
307
+ *
308
+ * Performance note: isFileIgnored() aggressively caches
309
+ * results so there is no performance penalty for calling
310
+ * it twice with the same argument.
311
+ */
312
+ if (pathMatches && !configs.isFileIgnored(entry.path)) {
313
+ unmatchedPatterns.delete(matcher.pattern);
314
+ }
315
+
316
+ return pathMatches || previousValue;
317
+ }, false)
318
+ : matchers.some(matcher => matcher.match(relativePath));
319
+
320
+ return matchesPattern && !configs.isFileIgnored(entry.path);
321
+ }
322
+
323
+ })).map(entry => entry.path);
324
+
325
+ // now check to see if we have any unmatched patterns
326
+ if (errorOnUnmatchedPattern && unmatchedPatterns.size > 0) {
327
+ throw new UnmatchedSearchPatternsError({
328
+ basePath,
329
+ unmatchedPatterns: [...unmatchedPatterns].map(
330
+ pattern => relativeToPatterns.get(pattern)
331
+ ),
332
+ patterns,
333
+ rawPatterns
334
+ });
335
+ }
336
+
337
+ return filePaths;
338
+ }
339
+
340
+ /**
341
+ * Throws an error for unmatched patterns. The error will only contain information about the first one.
342
+ * Checks to see if there are any ignored results for a given search.
343
+ * @param {Object} options The options for this function.
344
+ * @param {string} options.basePath The directory to search.
345
+ * @param {Array<string>} options.patterns An array of glob patterns
346
+ * that were used in the original search.
347
+ * @param {Array<string>} options.rawPatterns An array of glob patterns
348
+ * as the user inputted them. Used for errors.
349
+ * @param {Array<string>} options.unmatchedPatterns A non-empty array of glob patterns
350
+ * that were unmatched in the original search.
351
+ * @returns {void} Always throws an error.
352
+ * @throws {NoFilesFoundError} If the first unmatched pattern
353
+ * doesn't match any files even when there are no ignores.
354
+ * @throws {AllFilesIgnoredError} If the first unmatched pattern
355
+ * matches some files when there are no ignores.
356
+ */
357
+ async function throwErrorForUnmatchedPatterns({
358
+ basePath,
359
+ patterns,
360
+ rawPatterns,
361
+ unmatchedPatterns
362
+ }) {
363
+
364
+ const pattern = unmatchedPatterns[0];
365
+ const rawPattern = rawPatterns[patterns.indexOf(pattern)];
366
+
367
+ const patternHasMatch = await globMatch({
368
+ basePath,
369
+ pattern
370
+ });
371
+
372
+ if (patternHasMatch) {
373
+ throw new AllFilesIgnoredError(rawPattern);
374
+ }
375
+
376
+ // if we get here there are truly no matches
377
+ throw new NoFilesFoundError(rawPattern, true);
378
+ }
379
+
380
+ /**
381
+ * Performs multiple glob searches in parallel.
382
+ * @param {Object} options The options for this function.
383
+ * @param {Map<string,GlobSearch>} options.searches
384
+ * An array of glob patterns to match.
385
+ * @param {FlatConfigArray} options.configs The config array to use for
386
+ * determining what to ignore.
387
+ * @param {boolean} options.errorOnUnmatchedPattern Determines if an
388
+ * unmatched glob pattern should throw an error.
389
+ * @returns {Promise<Array<string>>} An array of matching file paths
390
+ * or an empty array if there are no matches.
391
+ */
392
+ async function globMultiSearch({ searches, configs, errorOnUnmatchedPattern }) {
393
+
394
+ /*
395
+ * For convenience, we normalized the search map into an array of objects.
396
+ * Next, we filter out all searches that have no patterns. This happens
397
+ * primarily for the cwd, which is prepopulated in the searches map as an
398
+ * optimization. However, if it has no patterns, it means all patterns
399
+ * occur outside of the cwd and we can safely filter out that search.
400
+ */
401
+ const normalizedSearches = [...searches].map(
402
+ ([basePath, { patterns, rawPatterns }]) => ({ basePath, patterns, rawPatterns })
403
+ ).filter(({ patterns }) => patterns.length > 0);
404
+
405
+ const results = await Promise.allSettled(
406
+ normalizedSearches.map(
407
+ ({ basePath, patterns, rawPatterns }) => globSearch({
408
+ basePath,
409
+ patterns,
410
+ rawPatterns,
411
+ configs,
412
+ errorOnUnmatchedPattern
413
+ })
414
+ )
415
+ );
416
+
417
+ const filePaths = [];
418
+
419
+ for (let i = 0; i < results.length; i++) {
420
+
421
+ const result = results[i];
422
+ const currentSearch = normalizedSearches[i];
423
+
424
+ if (result.status === "fulfilled") {
425
+
426
+ // if the search was successful just add the results
427
+ if (result.value.length > 0) {
428
+ filePaths.push(...result.value);
429
+ }
430
+
431
+ continue;
432
+ }
433
+
434
+ // if we make it here then there was an error
435
+ const error = result.reason;
436
+
437
+ // unexpected errors should be re-thrown
438
+ if (!error.basePath) {
439
+ throw error;
440
+ }
441
+
442
+ if (errorOnUnmatchedPattern) {
443
+
444
+ await throwErrorForUnmatchedPatterns({
445
+ ...currentSearch,
446
+ unmatchedPatterns: error.unmatchedPatterns
447
+ });
448
+
449
+ }
450
+
451
+ }
452
+
453
+ return [...new Set(filePaths)];
454
+
455
+ }
456
+
99
457
  /**
100
458
  * Finds all files matching the options specified.
101
459
  * @param {Object} args The arguments objects.
@@ -104,6 +462,8 @@ function isGlobPattern(pattern) {
104
462
  * false to not interpret glob patterns.
105
463
  * @param {string} args.cwd The current working directory to find from.
106
464
  * @param {FlatConfigArray} args.configs The configs for the current run.
465
+ * @param {boolean} args.errorOnUnmatchedPattern Determines if an unmatched pattern
466
+ * should throw an error.
107
467
  * @returns {Promise<Array<string>>} The fully resolved file paths.
108
468
  * @throws {AllFilesIgnoredError} If there are no results due to an ignore pattern.
109
469
  * @throws {NoFilesFoundError} If no files matched the given patterns.
@@ -112,25 +472,28 @@ async function findFiles({
112
472
  patterns,
113
473
  globInputPaths,
114
474
  cwd,
115
- configs
475
+ configs,
476
+ errorOnUnmatchedPattern
116
477
  }) {
117
478
 
118
479
  const results = [];
119
- const globbyPatterns = [];
120
480
  const missingPatterns = [];
481
+ let globbyPatterns = [];
482
+ let rawPatterns = [];
483
+ const searches = new Map([[cwd, { patterns: globbyPatterns, rawPatterns: [] }]]);
121
484
 
122
485
  // check to see if we have explicit files and directories
123
486
  const filePaths = patterns.map(filePath => path.resolve(cwd, filePath));
124
487
  const stats = await Promise.all(
125
488
  filePaths.map(
126
- filePath => fsp.stat(filePath).catch(() => {})
489
+ filePath => fsp.stat(filePath).catch(() => { })
127
490
  )
128
491
  );
129
492
 
130
493
  stats.forEach((stat, index) => {
131
494
 
132
495
  const filePath = filePaths[index];
133
- const pattern = patterns[index];
496
+ const pattern = normalizeToPosix(patterns[index]);
134
497
 
135
498
  if (stat) {
136
499
 
@@ -138,55 +501,25 @@ async function findFiles({
138
501
  if (stat.isFile()) {
139
502
  results.push({
140
503
  filePath,
141
- ignored: configs.isIgnored(filePath)
504
+ ignored: configs.isFileIgnored(filePath)
142
505
  });
143
506
  }
144
507
 
145
508
  // directories need extensions attached
146
509
  if (stat.isDirectory()) {
147
510
 
148
- // filePatterns are all relative to cwd
149
- const filePatterns = configs.files
150
- .filter(filePattern => {
151
-
152
- // can only do this for strings, not functions
153
- if (typeof filePattern !== "string") {
154
- return false;
155
- }
156
-
157
- // patterns ending with * are not used for file search
158
- if (filePattern.endsWith("*")) {
159
- return false;
160
- }
161
-
162
- // not sure how to handle negated patterns yet
163
- if (filePattern.startsWith("!")) {
164
- return false;
165
- }
166
-
167
- // check if the pattern would be inside the cwd or not
168
- const fullFilePattern = path.join(cwd, filePattern);
169
- const relativeFilePattern = path.relative(configs.basePath, fullFilePattern);
170
-
171
- return !relativeFilePattern.startsWith("..");
172
- })
173
- .map(filePattern => {
174
- if (filePattern.startsWith("**")) {
175
- return path.join(pattern, filePattern);
176
- }
177
-
178
- // adjust the path to be relative to the cwd
179
- return path.relative(
180
- cwd,
181
- path.join(configs.basePath, filePattern)
182
- );
183
- })
184
- .map(normalizeToPosix);
185
-
186
- if (filePatterns.length) {
187
- globbyPatterns.push(...filePatterns);
511
+ // group everything in cwd together and split out others
512
+ if (isPathInside(filePath, cwd)) {
513
+ ({ patterns: globbyPatterns, rawPatterns } = searches.get(cwd));
514
+ } else {
515
+ if (!searches.has(filePath)) {
516
+ searches.set(filePath, { patterns: [], rawPatterns: [] });
517
+ }
518
+ ({ patterns: globbyPatterns, rawPatterns } = searches.get(filePath));
188
519
  }
189
520
 
521
+ globbyPatterns.push(`${normalizeToPosix(filePath)}/**`);
522
+ rawPatterns.push(pattern);
190
523
  }
191
524
 
192
525
  return;
@@ -194,45 +527,37 @@ async function findFiles({
194
527
 
195
528
  // save patterns for later use based on whether globs are enabled
196
529
  if (globInputPaths && isGlobPattern(filePath)) {
197
- globbyPatterns.push(pattern);
198
- } else {
199
- missingPatterns.push(pattern);
200
- }
201
- });
202
-
203
- // note: globbyPatterns can be an empty array
204
- const globbyResults = (await globby(globbyPatterns, {
205
- cwd,
206
- absolute: true,
207
- ignore: configs.ignores.filter(matcher => typeof matcher === "string")
208
- }));
209
-
210
- // if there are no results, tell the user why
211
- if (!results.length && !globbyResults.length) {
212
530
 
213
- // try globby without ignoring anything
214
- /* eslint-disable no-unreachable-loop -- We want to exit early. */
215
- for (const globbyPattern of globbyPatterns) {
531
+ const basePath = globParent(filePath);
216
532
 
217
- /* eslint-disable-next-line no-unused-vars -- Want to exit early. */
218
- for await (const filePath of globby.stream(globbyPattern, { cwd, absolute: true })) {
219
-
220
- // files were found but ignored
221
- throw new AllFilesIgnoredError(globbyPattern);
533
+ // group in cwd if possible and split out others
534
+ if (isPathInside(basePath, cwd)) {
535
+ ({ patterns: globbyPatterns, rawPatterns } = searches.get(cwd));
536
+ } else {
537
+ if (!searches.has(basePath)) {
538
+ searches.set(basePath, { patterns: [], rawPatterns: [] });
539
+ }
540
+ ({ patterns: globbyPatterns, rawPatterns } = searches.get(basePath));
222
541
  }
223
542
 
224
- // no files were found
225
- throw new NoFilesFoundError(globbyPattern, globInputPaths);
543
+ globbyPatterns.push(filePath);
544
+ rawPatterns.push(pattern);
545
+ } else {
546
+ missingPatterns.push(pattern);
226
547
  }
227
- /* eslint-enable no-unreachable-loop -- Go back to normal. */
228
-
229
- }
548
+ });
230
549
 
231
550
  // there were patterns that didn't match anything, tell the user
232
- if (missingPatterns.length) {
551
+ if (errorOnUnmatchedPattern && missingPatterns.length) {
233
552
  throw new NoFilesFoundError(missingPatterns[0], globInputPaths);
234
553
  }
235
554
 
555
+ // now we are safe to do the search
556
+ const globbyResults = await globMultiSearch({
557
+ searches,
558
+ configs,
559
+ errorOnUnmatchedPattern
560
+ });
236
561
 
237
562
  return [
238
563
  ...results,
@@ -322,6 +647,7 @@ function createIgnoreResult(filePath, baseDir) {
322
647
  message
323
648
  }
324
649
  ],
650
+ suppressedMessages: [],
325
651
  errorCount: 0,
326
652
  warningCount: 1,
327
653
  fatalErrorCount: 0,
@@ -378,12 +704,10 @@ function processOptions({
378
704
  cacheStrategy = "metadata",
379
705
  cwd = process.cwd(),
380
706
  errorOnUnmatchedPattern = true,
381
- extensions = null, // ← should be null by default because if it's an array then it suppresses RFC20 feature.
382
707
  fix = false,
383
708
  fixTypes = null, // ← should be null by default because if it's an array then it suppresses rules that don't have the `meta.type` property.
384
709
  globInputPaths = true,
385
710
  ignore = true,
386
- ignorePath = null, // ← should be null by default because if it's a string then it may throw ENOENT.
387
711
  ignorePatterns = null,
388
712
  overrideConfig = null,
389
713
  overrideConfigFile = null,
@@ -405,12 +729,18 @@ function processOptions({
405
729
  if (unknownOptionKeys.includes("envs")) {
406
730
  errors.push("'envs' has been removed.");
407
731
  }
732
+ if (unknownOptionKeys.includes("extensions")) {
733
+ errors.push("'extensions' has been removed.");
734
+ }
408
735
  if (unknownOptionKeys.includes("resolvePluginsRelativeTo")) {
409
736
  errors.push("'resolvePluginsRelativeTo' has been removed.");
410
737
  }
411
738
  if (unknownOptionKeys.includes("globals")) {
412
739
  errors.push("'globals' has been removed. Please use the 'overrideConfig.languageOptions.globals' option instead.");
413
740
  }
741
+ if (unknownOptionKeys.includes("ignorePath")) {
742
+ errors.push("'ignorePath' has been removed.");
743
+ }
414
744
  if (unknownOptionKeys.includes("ignorePattern")) {
415
745
  errors.push("'ignorePattern' has been removed. Please use the 'overrideConfig.ignorePatterns' option instead.");
416
746
  }
@@ -451,9 +781,6 @@ function processOptions({
451
781
  if (typeof errorOnUnmatchedPattern !== "boolean") {
452
782
  errors.push("'errorOnUnmatchedPattern' must be a boolean.");
453
783
  }
454
- if (!isArrayOfNonEmptyString(extensions) && extensions !== null) {
455
- errors.push("'extensions' must be an array of non-empty strings or null.");
456
- }
457
784
  if (typeof fix !== "boolean" && typeof fix !== "function") {
458
785
  errors.push("'fix' must be a boolean or a function.");
459
786
  }
@@ -466,9 +793,6 @@ function processOptions({
466
793
  if (typeof ignore !== "boolean") {
467
794
  errors.push("'ignore' must be a boolean.");
468
795
  }
469
- if (!isNonEmptyString(ignorePath) && ignorePath !== null) {
470
- errors.push("'ignorePath' must be a non-empty string or null.");
471
- }
472
796
  if (typeof overrideConfig !== "object") {
473
797
  errors.push("'overrideConfig' must be an object or null.");
474
798
  }
@@ -507,12 +831,10 @@ function processOptions({
507
831
  overrideConfig,
508
832
  cwd,
509
833
  errorOnUnmatchedPattern,
510
- extensions,
511
834
  fix,
512
835
  fixTypes,
513
836
  globInputPaths,
514
837
  ignore,
515
- ignorePath,
516
838
  ignorePatterns,
517
839
  reportUnusedDisableDirectives
518
840
  };
@@ -36,11 +36,12 @@ const { version } = require("../../package.json");
36
36
  /** @typedef {import("../shared/types").Plugin} Plugin */
37
37
  /** @typedef {import("../shared/types").Rule} Rule */
38
38
  /** @typedef {import("../shared/types").LintResult} LintResult */
39
+ /** @typedef {import("../shared/types").ResultsMeta} ResultsMeta */
39
40
 
40
41
  /**
41
42
  * The main formatter object.
42
43
  * @typedef LoadedFormatter
43
- * @property {function(LintResult[]): string | Promise<string>} format format function.
44
+ * @property {(results: LintResult[], resultsMeta: ResultsMeta) => string | Promise<string>} format format function.
44
45
  */
45
46
 
46
47
  /**
@@ -625,14 +626,16 @@ class ESLint {
625
626
  /**
626
627
  * The main formatter method.
627
628
  * @param {LintResult[]} results The lint results to format.
629
+ * @param {ResultsMeta} resultsMeta Warning count and max threshold.
628
630
  * @returns {string | Promise<string>} The formatted lint results.
629
631
  */
630
- format(results) {
632
+ format(results, resultsMeta) {
631
633
  let rulesMeta = null;
632
634
 
633
635
  results.sort(compareResultsByFilePath);
634
636
 
635
637
  return formatter(results, {
638
+ ...resultsMeta,
636
639
  get cwd() {
637
640
  return options.cwd;
638
641
  },