eslint 9.11.1 → 9.13.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.
@@ -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
+ ]);
@@ -15,9 +15,7 @@ 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 fswalk = require("@nodelib/fs.walk");
19
18
  const globParent = require("glob-parent");
20
- const isPathInside = require("is-path-inside");
21
19
 
22
20
  //-----------------------------------------------------------------------------
23
21
  // Fixup references
@@ -160,31 +158,28 @@ function isGlobPattern(pattern) {
160
158
  * @param {string} options.pattern A glob pattern to match.
161
159
  * @returns {Promise<boolean>} True if there is a glob match, false if not.
162
160
  */
163
- function globMatch({ basePath, pattern }) {
161
+ async function globMatch({ basePath, pattern }) {
164
162
 
165
163
  let found = false;
164
+ const { hfs } = await import("@humanfs/node");
166
165
  const patternToUse = path.isAbsolute(pattern)
167
166
  ? normalizeToPosix(path.relative(basePath, pattern))
168
167
  : pattern;
169
168
 
170
169
  const matcher = new Minimatch(patternToUse, MINIMATCH_OPTIONS);
171
170
 
172
- const fsWalkSettings = {
171
+ const walkSettings = {
173
172
 
174
- deepFilter(entry) {
175
- const relativePath = normalizeToPosix(path.relative(basePath, entry.path));
176
-
177
- return !found && matcher.match(relativePath, true);
173
+ directoryFilter(entry) {
174
+ return !found && matcher.match(entry.path, true);
178
175
  },
179
176
 
180
177
  entryFilter(entry) {
181
- if (found || entry.dirent.isDirectory()) {
178
+ if (found || entry.isDirectory) {
182
179
  return false;
183
180
  }
184
181
 
185
- const relativePath = normalizeToPosix(path.relative(basePath, entry.path));
186
-
187
- if (matcher.match(relativePath)) {
182
+ if (matcher.match(entry.path)) {
188
183
  found = true;
189
184
  return true;
190
185
  }
@@ -193,25 +188,11 @@ function globMatch({ basePath, pattern }) {
193
188
  }
194
189
  };
195
190
 
196
- return new Promise(resolve => {
197
-
198
- // using a stream so we can exit early because we just need one match
199
- const globStream = fswalk.walkStream(basePath, fsWalkSettings);
200
-
201
- globStream.on("data", () => {
202
- globStream.destroy();
203
- resolve(true);
204
- });
205
-
206
- // swallow errors as they're not important here
207
- globStream.on("error", () => { });
208
-
209
- globStream.on("end", () => {
210
- resolve(false);
211
- });
212
- globStream.read();
213
- });
191
+ if (await hfs.isDirectory(basePath)) {
192
+ return hfs.walk(basePath, walkSettings).next().then(() => found);
193
+ }
214
194
 
195
+ return found;
215
196
  }
216
197
 
217
198
  /**
@@ -225,7 +206,7 @@ function globMatch({ basePath, pattern }) {
225
206
  * to match.
226
207
  * @param {Array<string>} options.rawPatterns An array of glob patterns
227
208
  * as the user inputted them. Used for errors.
228
- * @param {FlatConfigArray} options.configs The config array to use for
209
+ * @param {ConfigLoader|LegacyConfigLoader} options.configLoader The config array to use for
229
210
  * determining what to ignore.
230
211
  * @param {boolean} options.errorOnUnmatchedPattern Determines if an error
231
212
  * should be thrown when a pattern is unmatched.
@@ -238,7 +219,7 @@ async function globSearch({
238
219
  basePath,
239
220
  patterns,
240
221
  rawPatterns,
241
- configs,
222
+ configLoader,
242
223
  errorOnUnmatchedPattern
243
224
  }) {
244
225
 
@@ -277,94 +258,76 @@ async function globSearch({
277
258
  * search.
278
259
  */
279
260
  const unmatchedPatterns = new Set([...relativeToPatterns.keys()]);
261
+ const { hfs } = await import("@humanfs/node");
280
262
 
281
- const filePaths = (await new Promise((resolve, reject) => {
282
-
283
- let promiseRejected = false;
263
+ const walk = hfs.walk(
264
+ basePath,
265
+ {
266
+ async directoryFilter(entry) {
284
267
 
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
- }
268
+ if (!matchers.some(matcher => matcher.match(entry.path, true))) {
269
+ return false;
301
270
  }
302
- return false;
303
- };
304
- }
305
271
 
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
- }
322
-
323
- /*
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.
331
- */
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 has a config. If the file has no
339
- * config, that means there wasn't a match for the
340
- * pattern so it should not be removed.
341
- *
342
- * Performance note: `getConfig()` aggressively caches
343
- * results so there is no performance penalty for calling
344
- * it multiple times with the same argument.
345
- */
346
- if (pathMatches && configs.getConfig(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.getConfig(entry.path) !== void 0;
355
- })
272
+ const absolutePath = path.resolve(basePath, entry.path);
273
+ const configs = await configLoader.loadConfigArrayForDirectory(absolutePath);
274
+
275
+ return !configs.isDirectoryIgnored(absolutePath);
356
276
  },
357
- (error, entries) => {
277
+ async entryFilter(entry) {
278
+ const absolutePath = path.resolve(basePath, entry.path);
358
279
 
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);
280
+ // entries may be directories or files so filter out directories
281
+ if (entry.isDirectory) {
282
+ return false;
364
283
  }
284
+
285
+ const configs = await configLoader.loadConfigArrayForFile(absolutePath);
286
+ const config = configs.getConfig(absolutePath);
287
+
288
+ /*
289
+ * Optimization: We need to track when patterns are left unmatched
290
+ * and so we use `unmatchedPatterns` to do that. There is a bit of
291
+ * complexity here because the same file can be matched by more than
292
+ * one pattern. So, when we start, we actually need to test every
293
+ * pattern against every file. Once we know there are no remaining
294
+ * unmatched patterns, then we can switch to just looking for the
295
+ * first matching pattern for improved speed.
296
+ */
297
+ const matchesPattern = unmatchedPatterns.size > 0
298
+ ? matchers.reduce((previousValue, matcher) => {
299
+ const pathMatches = matcher.match(entry.path);
300
+
301
+ /*
302
+ * We updated the unmatched patterns set only if the path
303
+ * matches and the file has a config. If the file has no
304
+ * config, that means there wasn't a match for the
305
+ * pattern so it should not be removed.
306
+ *
307
+ * Performance note: `getConfig()` aggressively caches
308
+ * results so there is no performance penalty for calling
309
+ * it multiple times with the same argument.
310
+ */
311
+ if (pathMatches && config) {
312
+ unmatchedPatterns.delete(matcher.pattern);
313
+ }
314
+
315
+ return pathMatches || previousValue;
316
+ }, false)
317
+ : matchers.some(matcher => matcher.match(entry.path));
318
+
319
+ return matchesPattern && config !== void 0;
365
320
  }
366
- );
367
- })).map(entry => entry.path);
321
+ }
322
+ );
323
+
324
+ const filePaths = [];
325
+
326
+ if (await hfs.isDirectory(basePath)) {
327
+ for await (const entry of walk) {
328
+ filePaths.push(path.resolve(basePath, entry.path));
329
+ }
330
+ }
368
331
 
369
332
  // now check to see if we have any unmatched patterns
370
333
  if (errorOnUnmatchedPattern && unmatchedPatterns.size > 0) {
@@ -426,14 +389,14 @@ async function throwErrorForUnmatchedPatterns({
426
389
  * @param {Object} options The options for this function.
427
390
  * @param {Map<string,GlobSearch>} options.searches
428
391
  * An array of glob patterns to match.
429
- * @param {FlatConfigArray} options.configs The config array to use for
392
+ * @param {ConfigLoader|LegacyConfigLoader} options.configLoader The config loader to use for
430
393
  * determining what to ignore.
431
394
  * @param {boolean} options.errorOnUnmatchedPattern Determines if an
432
395
  * unmatched glob pattern should throw an error.
433
396
  * @returns {Promise<Array<string>>} An array of matching file paths
434
397
  * or an empty array if there are no matches.
435
398
  */
436
- async function globMultiSearch({ searches, configs, errorOnUnmatchedPattern }) {
399
+ async function globMultiSearch({ searches, configLoader, errorOnUnmatchedPattern }) {
437
400
 
438
401
  /*
439
402
  * For convenience, we normalized the search map into an array of objects.
@@ -452,7 +415,7 @@ async function globMultiSearch({ searches, configs, errorOnUnmatchedPattern }) {
452
415
  basePath,
453
416
  patterns,
454
417
  rawPatterns,
455
- configs,
418
+ configLoader,
456
419
  errorOnUnmatchedPattern
457
420
  })
458
421
  )
@@ -505,7 +468,7 @@ async function globMultiSearch({ searches, configs, errorOnUnmatchedPattern }) {
505
468
  * @param {boolean} args.globInputPaths true to interpret glob patterns,
506
469
  * false to not interpret glob patterns.
507
470
  * @param {string} args.cwd The current working directory to find from.
508
- * @param {FlatConfigArray} args.configs The configs for the current run.
471
+ * @param {ConfigLoader|LegacyConfigLoader} args.configLoader The config loeader for the current run.
509
472
  * @param {boolean} args.errorOnUnmatchedPattern Determines if an unmatched pattern
510
473
  * should throw an error.
511
474
  * @returns {Promise<Array<string>>} The fully resolved file paths.
@@ -516,7 +479,7 @@ async function findFiles({
516
479
  patterns,
517
480
  globInputPaths,
518
481
  cwd,
519
- configs,
482
+ configLoader,
520
483
  errorOnUnmatchedPattern
521
484
  }) {
522
485
 
@@ -526,6 +489,42 @@ async function findFiles({
526
489
  let rawPatterns = [];
527
490
  const searches = new Map([[cwd, { patterns: globbyPatterns, rawPatterns: [] }]]);
528
491
 
492
+ /*
493
+ * This part is a bit involved because we need to account for
494
+ * the different ways that the patterns can match directories.
495
+ * For each different way, we need to decide if we should look
496
+ * for a config file or just use the default config. (Directories
497
+ * without a config file always use the default config.)
498
+ *
499
+ * Here are the cases:
500
+ *
501
+ * 1. A directory is passed directly (e.g., "subdir"). In this case, we
502
+ * can assume that the user intends to lint this directory and we should
503
+ * not look for a config file in the parent directory, because the only
504
+ * reason to do that would be to ignore this directory (which we already
505
+ * know we don't want to do). Instead, we use the default config until we
506
+ * get to the directory that was passed, at which point we start looking
507
+ * for config files again.
508
+ *
509
+ * 2. A dot (".") or star ("*"). In this case, we want to read
510
+ * the config file in the current directory because the user is
511
+ * explicitly asking to lint the current directory. Note that "."
512
+ * will traverse into subdirectories while "*" will not.
513
+ *
514
+ * 3. A directory is passed in the form of "subdir/subsubdir".
515
+ * In this case, we don't want to look for a config file in the
516
+ * parent directory ("subdir"). We can skip looking for a config
517
+ * file until `entry.depth` is greater than 1 because there's no
518
+ * way that the pattern can match `entry.path` yet.
519
+ *
520
+ * 4. A directory glob pattern is passed (e.g., "subd*"). We want
521
+ * this case to act like case 2 because it's unclear whether or not
522
+ * any particular directory is meant to be traversed.
523
+ *
524
+ * 5. A recursive glob pattern is passed (e.g., "**"). We want this
525
+ * case to act like case 2.
526
+ */
527
+
529
528
  // check to see if we have explicit files and directories
530
529
  const filePaths = patterns.map(filePath => path.resolve(cwd, filePath));
531
530
  const stats = await Promise.all(
@@ -549,15 +548,10 @@ async function findFiles({
549
548
  // directories need extensions attached
550
549
  if (stat.isDirectory()) {
551
550
 
552
- // group everything in cwd together and split out others
553
- if (isPathInside(filePath, cwd)) {
554
- ({ patterns: globbyPatterns, rawPatterns } = searches.get(cwd));
555
- } else {
556
- if (!searches.has(filePath)) {
557
- searches.set(filePath, { patterns: [], rawPatterns: [] });
558
- }
559
- ({ patterns: globbyPatterns, rawPatterns } = searches.get(filePath));
551
+ if (!searches.has(filePath)) {
552
+ searches.set(filePath, { patterns: [], rawPatterns: [] });
560
553
  }
554
+ ({ patterns: globbyPatterns, rawPatterns } = searches.get(filePath));
561
555
 
562
556
  globbyPatterns.push(`${normalizeToPosix(filePath)}/**`);
563
557
  rawPatterns.push(pattern);
@@ -569,17 +563,17 @@ async function findFiles({
569
563
  // save patterns for later use based on whether globs are enabled
570
564
  if (globInputPaths && isGlobPattern(pattern)) {
571
565
 
566
+ /*
567
+ * We are grouping patterns by their glob parent. This is done to
568
+ * make it easier to determine when a config file should be loaded.
569
+ */
570
+
572
571
  const basePath = path.resolve(cwd, globParent(pattern));
573
572
 
574
- // group in cwd if possible and split out others
575
- if (isPathInside(basePath, cwd)) {
576
- ({ patterns: globbyPatterns, rawPatterns } = searches.get(cwd));
577
- } else {
578
- if (!searches.has(basePath)) {
579
- searches.set(basePath, { patterns: [], rawPatterns: [] });
580
- }
581
- ({ patterns: globbyPatterns, rawPatterns } = searches.get(basePath));
573
+ if (!searches.has(basePath)) {
574
+ searches.set(basePath, { patterns: [], rawPatterns: [] });
582
575
  }
576
+ ({ patterns: globbyPatterns, rawPatterns } = searches.get(basePath));
583
577
 
584
578
  globbyPatterns.push(filePath);
585
579
  rawPatterns.push(pattern);
@@ -596,7 +590,7 @@ async function findFiles({
596
590
  // now we are safe to do the search
597
591
  const globbyResults = await globMultiSearch({
598
592
  searches,
599
- configs,
593
+ configLoader,
600
594
  errorOnUnmatchedPattern
601
595
  });
602
596