eslint 8.24.0 → 8.26.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/lib/cli.js CHANGED
@@ -25,7 +25,6 @@ const fs = require("fs"),
25
25
  RuntimeInfo = require("./shared/runtime-info");
26
26
  const { Legacy: { naming } } = require("@eslint/eslintrc");
27
27
  const { findFlatConfigFile } = require("./eslint/flat-eslint");
28
- const { gitignoreToMinimatch } = require("@humanwhocodes/gitignore-to-minimatch");
29
28
  const { ModuleImporter } = require("@humanwhocodes/module-importer");
30
29
 
31
30
  const debug = require("debug")("eslint:cli");
@@ -38,6 +37,7 @@ const debug = require("debug")("eslint:cli");
38
37
  /** @typedef {import("./eslint/eslint").LintMessage} LintMessage */
39
38
  /** @typedef {import("./eslint/eslint").LintResult} LintResult */
40
39
  /** @typedef {import("./options").ParsedCLIOptions} ParsedCLIOptions */
40
+ /** @typedef {import("./shared/types").ResultsMeta} ResultsMeta */
41
41
 
42
42
  //------------------------------------------------------------------------------
43
43
  // Helpers
@@ -143,12 +143,6 @@ async function translateOptions({
143
143
  overrideConfig[0].plugins = plugins;
144
144
  }
145
145
 
146
- if (ignorePattern) {
147
- overrideConfig.push({
148
- ignores: ignorePattern.map(gitignoreToMinimatch)
149
- });
150
- }
151
-
152
146
  } else {
153
147
  overrideConfigFile = config;
154
148
 
@@ -182,17 +176,19 @@ async function translateOptions({
182
176
  fix: (fix || fixDryRun) && (quiet ? quietFixPredicate : true),
183
177
  fixTypes: fixType,
184
178
  ignore,
185
- ignorePath,
186
179
  overrideConfig,
187
180
  overrideConfigFile,
188
181
  reportUnusedDisableDirectives: reportUnusedDisableDirectives ? "error" : void 0
189
182
  };
190
183
 
191
- if (configType !== "flat") {
184
+ if (configType === "flat") {
185
+ options.ignorePatterns = ignorePattern;
186
+ } else {
192
187
  options.resolvePluginsRelativeTo = resolvePluginsRelativeTo;
193
188
  options.rulePaths = rulesdir;
194
189
  options.useEslintrc = eslintrc;
195
190
  options.extensions = ext;
191
+ options.ignorePath = ignorePath;
196
192
  }
197
193
 
198
194
  return options;
@@ -201,7 +197,7 @@ async function translateOptions({
201
197
  /**
202
198
  * Count error messages.
203
199
  * @param {LintResult[]} results The lint results.
204
- * @returns {{errorCount:number;warningCount:number}} The number of error messages.
200
+ * @returns {{errorCount:number;fatalErrorCount:number,warningCount:number}} The number of error messages.
205
201
  */
206
202
  function countErrors(results) {
207
203
  let errorCount = 0;
@@ -239,10 +235,11 @@ async function isDirectory(filePath) {
239
235
  * @param {LintResult[]} results The results to print.
240
236
  * @param {string} format The name of the formatter to use or the path to the formatter.
241
237
  * @param {string} outputFile The path for the output file.
238
+ * @param {ResultsMeta} resultsMeta Warning count and max threshold.
242
239
  * @returns {Promise<boolean>} True if the printing succeeds, false if not.
243
240
  * @private
244
241
  */
245
- async function printResults(engine, results, format, outputFile) {
242
+ async function printResults(engine, results, format, outputFile, resultsMeta) {
246
243
  let formatter;
247
244
 
248
245
  try {
@@ -252,7 +249,7 @@ async function printResults(engine, results, format, outputFile) {
252
249
  return false;
253
250
  }
254
251
 
255
- const output = await formatter.format(results);
252
+ const output = await formatter.format(results, resultsMeta);
256
253
 
257
254
  if (output) {
258
255
  if (outputFile) {
@@ -278,6 +275,31 @@ async function printResults(engine, results, format, outputFile) {
278
275
  return true;
279
276
  }
280
277
 
278
+ /**
279
+ * Returns whether flat config should be used.
280
+ * @param {boolean} [allowFlatConfig] Whether or not to allow flat config.
281
+ * @returns {Promise<boolean>} Where flat config should be used.
282
+ */
283
+ async function shouldUseFlatConfig(allowFlatConfig) {
284
+ if (!allowFlatConfig) {
285
+ return false;
286
+ }
287
+
288
+ switch (process.env.ESLINT_USE_FLAT_CONFIG) {
289
+ case "true":
290
+ return true;
291
+ case "false":
292
+ return false;
293
+ default:
294
+
295
+ /*
296
+ * If neither explicitly enabled nor disabled, then use the presence
297
+ * of a flat config file to determine enablement.
298
+ */
299
+ return !!(await findFlatConfigFile(process.cwd()));
300
+ }
301
+ }
302
+
281
303
  //------------------------------------------------------------------------------
282
304
  // Public Interface
283
305
  //------------------------------------------------------------------------------
@@ -307,7 +329,7 @@ const cli = {
307
329
  * switch to flat config we can remove this logic.
308
330
  */
309
331
 
310
- const usingFlatConfig = allowFlatConfig && !!(await findFlatConfigFile(process.cwd()));
332
+ const usingFlatConfig = await shouldUseFlatConfig(allowFlatConfig);
311
333
 
312
334
  debug("Using flat config?", usingFlatConfig);
313
335
 
@@ -407,17 +429,24 @@ const cli = {
407
429
  resultsToPrint = ActiveESLint.getErrorResults(resultsToPrint);
408
430
  }
409
431
 
410
- if (await printResults(engine, resultsToPrint, options.format, options.outputFile)) {
432
+ const resultCounts = countErrors(results);
433
+ const tooManyWarnings = options.maxWarnings >= 0 && resultCounts.warningCount > options.maxWarnings;
434
+ const resultsMeta = tooManyWarnings
435
+ ? {
436
+ maxWarningsExceeded: {
437
+ maxWarnings: options.maxWarnings,
438
+ foundWarnings: resultCounts.warningCount
439
+ }
440
+ }
441
+ : {};
411
442
 
412
- // Errors and warnings from the original unfiltered results should determine the exit code
413
- const { errorCount, fatalErrorCount, warningCount } = countErrors(results);
443
+ if (await printResults(engine, resultsToPrint, options.format, options.outputFile, resultsMeta)) {
414
444
 
415
- const tooManyWarnings =
416
- options.maxWarnings >= 0 && warningCount > options.maxWarnings;
445
+ // Errors and warnings from the original unfiltered results should determine the exit code
417
446
  const shouldExitForFatalErrors =
418
- options.exitOnFatalError && fatalErrorCount > 0;
447
+ options.exitOnFatalError && resultCounts.fatalErrorCount > 0;
419
448
 
420
- if (!errorCount && tooManyWarnings) {
449
+ if (!resultCounts.errorCount && tooManyWarnings) {
421
450
  log.error(
422
451
  "ESLint found too many warnings (maximum: %s).",
423
452
  options.maxWarnings
@@ -428,7 +457,7 @@ const cli = {
428
457
  return 2;
429
458
  }
430
459
 
431
- return (errorCount || tooManyWarnings) ? 1 : 0;
460
+ return (resultCounts.errorCount || tooManyWarnings) ? 1 : 0;
432
461
  }
433
462
 
434
463
  return 2;
@@ -52,7 +52,7 @@ exports.defaultConfig = [
52
52
  {
53
53
  ignores: [
54
54
  "**/node_modules/**",
55
- ".git/**"
55
+ ".git/"
56
56
  ]
57
57
  },
58
58
 
@@ -70,7 +70,7 @@ class FlatConfigArray extends ConfigArray {
70
70
  }
71
71
 
72
72
  /**
73
- * The baes config used to build the config array.
73
+ * The base config used to build the config array.
74
74
  * @type {Array<FlatConfig>}
75
75
  */
76
76
  this[originalBaseConfig] = baseConfig;
@@ -13,9 +13,19 @@ 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");
18
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;
19
29
 
20
30
  //-----------------------------------------------------------------------------
21
31
  // Errors
@@ -67,9 +77,9 @@ function isNonEmptyString(x) {
67
77
  }
68
78
 
69
79
  /**
70
- * Check if a given value is an array of non-empty stringss or not.
80
+ * Check if a given value is an array of non-empty strings or not.
71
81
  * @param {any} x The value to check.
72
- * @returns {boolean} `true` if `x` is an array of non-empty stringss.
82
+ * @returns {boolean} `true` if `x` is an array of non-empty strings.
73
83
  */
74
84
  function isArrayOfNonEmptyString(x) {
75
85
  return Array.isArray(x) && x.every(isNonEmptyString);
@@ -97,6 +107,141 @@ function isGlobPattern(pattern) {
97
107
  return isGlob(path.sep === "\\" ? normalizeToPosix(pattern) : pattern);
98
108
  }
99
109
 
110
+ /**
111
+ * Searches a directory looking for matching glob patterns. This uses
112
+ * the config array's logic to determine if a directory or file should
113
+ * be ignored, so it is consistent with how ignoring works throughout
114
+ * ESLint.
115
+ * @param {Object} options The options for this function.
116
+ * @param {string} options.basePath The directory to search.
117
+ * @param {Array<string>} options.patterns An array of glob patterns
118
+ * to match.
119
+ * @param {FlatConfigArray} options.configs The config array to use for
120
+ * determining what to ignore.
121
+ * @returns {Promise<Array<string>>} An array of matching file paths
122
+ * or an empty array if there are no matches.
123
+ */
124
+ async function globSearch({ basePath, patterns, configs }) {
125
+
126
+ if (patterns.length === 0) {
127
+ return [];
128
+ }
129
+
130
+ const matchers = patterns.map(pattern => {
131
+ const patternToUse = path.isAbsolute(pattern)
132
+ ? normalizeToPosix(path.relative(basePath, pattern))
133
+ : pattern;
134
+
135
+ return new minimatch.Minimatch(patternToUse);
136
+ });
137
+
138
+ return (await doFsWalk(basePath, {
139
+
140
+ deepFilter(entry) {
141
+ const relativePath = normalizeToPosix(path.relative(basePath, entry.path));
142
+ const matchesPattern = matchers.some(matcher => matcher.match(relativePath, true));
143
+
144
+ return matchesPattern && !configs.isDirectoryIgnored(entry.path);
145
+ },
146
+ entryFilter(entry) {
147
+ const relativePath = normalizeToPosix(path.relative(basePath, entry.path));
148
+
149
+ // entries may be directories or files so filter out directories
150
+ if (entry.dirent.isDirectory()) {
151
+ return false;
152
+ }
153
+
154
+ const matchesPattern = matchers.some(matcher => matcher.match(relativePath));
155
+
156
+ return matchesPattern && !configs.isFileIgnored(entry.path);
157
+ }
158
+ })).map(entry => entry.path);
159
+
160
+ }
161
+
162
+ /**
163
+ * Performs multiple glob searches in parallel.
164
+ * @param {Object} options The options for this function.
165
+ * @param {Array<{patterns:Array<string>,rawPatterns:Array<string>}>} options.searches
166
+ * An array of glob patterns to match.
167
+ * @param {FlatConfigArray} options.configs The config array to use for
168
+ * determining what to ignore.
169
+ * @returns {Promise<Array<string>>} An array of matching file paths
170
+ * or an empty array if there are no matches.
171
+ */
172
+ async function globMultiSearch({ searches, configs }) {
173
+
174
+ const results = await Promise.all(
175
+ [...searches].map(
176
+ ([basePath, { patterns }]) => globSearch({ basePath, patterns, configs })
177
+ )
178
+ );
179
+
180
+ return [...new Set(results.flat())];
181
+ }
182
+
183
+ /**
184
+ * Determines if a given glob pattern will return any results.
185
+ * Used primarily to help with useful error messages.
186
+ * @param {Object} options The options for the function.
187
+ * @param {string} options.basePath The directory to search.
188
+ * @param {string} options.pattern A glob pattern to match.
189
+ * @returns {Promise<boolean>} True if there is a glob match, false if not.
190
+ */
191
+ function globMatch({ basePath, pattern }) {
192
+
193
+ let found = false;
194
+ const patternToUse = path.isAbsolute(pattern)
195
+ ? normalizeToPosix(path.relative(basePath, pattern))
196
+ : pattern;
197
+
198
+ const matcher = new Minimatch(patternToUse);
199
+
200
+ const fsWalkSettings = {
201
+
202
+ deepFilter(entry) {
203
+ const relativePath = normalizeToPosix(path.relative(basePath, entry.path));
204
+
205
+ return !found && matcher.match(relativePath, true);
206
+ },
207
+
208
+ entryFilter(entry) {
209
+ if (found || entry.dirent.isDirectory()) {
210
+ return false;
211
+ }
212
+
213
+ const relativePath = normalizeToPosix(path.relative(basePath, entry.path));
214
+
215
+ if (matcher.match(relativePath)) {
216
+ found = true;
217
+ return true;
218
+ }
219
+
220
+ return false;
221
+ }
222
+ };
223
+
224
+ return new Promise(resolve => {
225
+
226
+ // using a stream so we can exit early because we just need one match
227
+ const globStream = fswalk.walkStream(basePath, fsWalkSettings);
228
+
229
+ globStream.on("data", () => {
230
+ globStream.destroy();
231
+ resolve(true);
232
+ });
233
+
234
+ // swallow errors as they're not important here
235
+ globStream.on("error", () => {});
236
+
237
+ globStream.on("end", () => {
238
+ resolve(false);
239
+ });
240
+ globStream.read();
241
+ });
242
+
243
+ }
244
+
100
245
  /**
101
246
  * Finds all files matching the options specified.
102
247
  * @param {Object} args The arguments objects.
@@ -120,8 +265,10 @@ async function findFiles({
120
265
  }) {
121
266
 
122
267
  const results = [];
123
- const globbyPatterns = [];
124
268
  const missingPatterns = [];
269
+ let globbyPatterns = [];
270
+ let rawPatterns = [];
271
+ const searches = new Map([[cwd, { patterns: globbyPatterns, rawPatterns: [] }]]);
125
272
 
126
273
  // check to see if we have explicit files and directories
127
274
  const filePaths = patterns.map(filePath => path.resolve(cwd, filePath));
@@ -142,76 +289,25 @@ async function findFiles({
142
289
  if (stat.isFile()) {
143
290
  results.push({
144
291
  filePath,
145
- ignored: configs.isIgnored(filePath)
292
+ ignored: configs.isFileIgnored(filePath)
146
293
  });
147
294
  }
148
295
 
149
296
  // directories need extensions attached
150
297
  if (stat.isDirectory()) {
151
298
 
152
- // filePatterns are all relative to cwd
153
- const filePatterns = configs.files
154
- .filter(filePattern => {
155
-
156
- // can only do this for strings, not functions
157
- if (typeof filePattern !== "string") {
158
- return false;
159
- }
160
-
161
- // patterns starting with ** always apply
162
- if (filePattern.startsWith("**")) {
163
- return true;
164
- }
165
-
166
- // patterns ending with * are not used for file search
167
- if (filePattern.endsWith("*")) {
168
- return false;
169
- }
170
-
171
- // not sure how to handle negated patterns yet
172
- if (filePattern.startsWith("!")) {
173
- return false;
174
- }
175
-
176
- // check if the pattern would be inside the config base path or not
177
- const fullFilePattern = path.join(cwd, filePattern);
178
- const patternRelativeToConfigBasePath = path.relative(configs.basePath, fullFilePattern);
179
-
180
- if (patternRelativeToConfigBasePath.startsWith("..")) {
181
- return false;
182
- }
183
-
184
- // check if the pattern matches
185
- if (minimatch(filePath, path.dirname(fullFilePattern), { partial: true })) {
186
- return true;
187
- }
188
-
189
- // check if the pattern is inside the directory or not
190
- const patternRelativeToFilePath = path.relative(filePath, fullFilePattern);
191
-
192
- if (patternRelativeToFilePath.startsWith("..")) {
193
- return false;
194
- }
195
-
196
- return true;
197
- })
198
- .map(filePattern => {
199
- if (filePattern.startsWith("**")) {
200
- return path.join(pattern, filePattern);
201
- }
202
-
203
- // adjust the path to be relative to the cwd
204
- return path.relative(
205
- cwd,
206
- path.join(configs.basePath, filePattern)
207
- );
208
- })
209
- .map(normalizeToPosix);
210
-
211
- if (filePatterns.length) {
212
- globbyPatterns.push(...filePatterns);
299
+ // group everything in cwd together and split out others
300
+ if (isPathInside(filePath, cwd)) {
301
+ ({ patterns: globbyPatterns, rawPatterns } = searches.get(cwd));
302
+ } else {
303
+ if (!searches.has(filePath)) {
304
+ searches.set(filePath, { patterns: [], rawPatterns: [] });
305
+ }
306
+ ({ patterns: globbyPatterns, rawPatterns } = searches.get(filePath));
213
307
  }
214
308
 
309
+ globbyPatterns.push(`${normalizeToPosix(filePath)}/**`);
310
+ rawPatterns.push(pattern);
215
311
  }
216
312
 
217
313
  return;
@@ -219,39 +315,59 @@ async function findFiles({
219
315
 
220
316
  // save patterns for later use based on whether globs are enabled
221
317
  if (globInputPaths && isGlobPattern(filePath)) {
222
- globbyPatterns.push(pattern);
318
+
319
+ const basePath = globParent(filePath);
320
+
321
+ // group in cwd if possible and split out others
322
+ if (isPathInside(basePath, cwd)) {
323
+ ({ patterns: globbyPatterns, rawPatterns } = searches.get(cwd));
324
+ } else {
325
+ if (!searches.has(basePath)) {
326
+ searches.set(basePath, { patterns: [], rawPatterns: [] });
327
+ }
328
+ ({ patterns: globbyPatterns, rawPatterns } = searches.get(basePath));
329
+ }
330
+
331
+ globbyPatterns.push(filePath);
332
+ rawPatterns.push(pattern);
223
333
  } else {
224
334
  missingPatterns.push(pattern);
225
335
  }
226
336
  });
227
337
 
228
- // note: globbyPatterns can be an empty array
229
- const globbyResults = (await globby(globbyPatterns, {
230
- cwd,
231
- absolute: true,
232
- ignore: configs.ignores.filter(matcher => typeof matcher === "string")
233
- }));
338
+ const globbyResults = await globMultiSearch({
339
+ searches,
340
+ configs
341
+ });
234
342
 
235
343
  // if there are no results, tell the user why
236
344
  if (!results.length && !globbyResults.length) {
237
345
 
238
- // try globby without ignoring anything
239
- /* eslint-disable no-unreachable-loop -- We want to exit early. */
240
- for (const globbyPattern of globbyPatterns) {
346
+ for (const [basePath, { patterns: patternsToCheck, rawPatterns: patternsToReport }] of searches) {
241
347
 
242
- /* eslint-disable-next-line no-unused-vars -- Want to exit early. */
243
- for await (const filePath of globby.stream(globbyPattern, { cwd, absolute: true })) {
348
+ let index = 0;
244
349
 
245
- // files were found but ignored
246
- throw new AllFilesIgnoredError(globbyPattern);
247
- }
350
+ // try globby without ignoring anything
351
+ for (const patternToCheck of patternsToCheck) {
248
352
 
249
- // no files were found
250
- if (errorOnUnmatchedPattern) {
251
- throw new NoFilesFoundError(globbyPattern, globInputPaths);
353
+ // check if there are any matches at all
354
+ const patternHasMatch = await globMatch({
355
+ basePath,
356
+ pattern: patternToCheck
357
+ });
358
+
359
+ if (patternHasMatch) {
360
+ throw new AllFilesIgnoredError(patternsToReport[index]);
361
+ }
362
+
363
+ // otherwise no files were found
364
+ if (errorOnUnmatchedPattern) {
365
+ throw new NoFilesFoundError(patternsToReport[index], globInputPaths);
366
+ }
367
+
368
+ index++;
252
369
  }
253
370
  }
254
- /* eslint-enable no-unreachable-loop -- Go back to normal. */
255
371
 
256
372
  }
257
373
 
@@ -410,7 +526,6 @@ function processOptions({
410
526
  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.
411
527
  globInputPaths = true,
412
528
  ignore = true,
413
- ignorePath = null, // ← should be null by default because if it's a string then it may throw ENOENT.
414
529
  ignorePatterns = null,
415
530
  overrideConfig = null,
416
531
  overrideConfigFile = null,
@@ -441,6 +556,9 @@ function processOptions({
441
556
  if (unknownOptionKeys.includes("globals")) {
442
557
  errors.push("'globals' has been removed. Please use the 'overrideConfig.languageOptions.globals' option instead.");
443
558
  }
559
+ if (unknownOptionKeys.includes("ignorePath")) {
560
+ errors.push("'ignorePath' has been removed.");
561
+ }
444
562
  if (unknownOptionKeys.includes("ignorePattern")) {
445
563
  errors.push("'ignorePattern' has been removed. Please use the 'overrideConfig.ignorePatterns' option instead.");
446
564
  }
@@ -493,9 +611,6 @@ function processOptions({
493
611
  if (typeof ignore !== "boolean") {
494
612
  errors.push("'ignore' must be a boolean.");
495
613
  }
496
- if (!isNonEmptyString(ignorePath) && ignorePath !== null) {
497
- errors.push("'ignorePath' must be a non-empty string or null.");
498
- }
499
614
  if (typeof overrideConfig !== "object") {
500
615
  errors.push("'overrideConfig' must be an object or null.");
501
616
  }
@@ -538,7 +653,6 @@ function processOptions({
538
653
  fixTypes,
539
654
  globInputPaths,
540
655
  ignore,
541
- ignorePath,
542
656
  ignorePatterns,
543
657
  reportUnusedDisableDirectives
544
658
  };
@@ -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
  },
@@ -16,7 +16,6 @@ const findUp = require("find-up");
16
16
  const { version } = require("../../package.json");
17
17
  const { Linter } = require("../linter");
18
18
  const { getRuleFromConfig } = require("../config/flat-config-helpers");
19
- const { gitignoreToMinimatch } = require("@humanwhocodes/gitignore-to-minimatch");
20
19
  const {
21
20
  Legacy: {
22
21
  ConfigOps: {
@@ -28,7 +27,6 @@ const {
28
27
  } = require("@eslint/eslintrc");
29
28
 
30
29
  const {
31
- fileExists,
32
30
  findFiles,
33
31
  getCacheFile,
34
32
 
@@ -59,6 +57,7 @@ const LintResultCache = require("../cli-engine/lint-result-cache");
59
57
  /** @typedef {import("../shared/types").LintMessage} LintMessage */
60
58
  /** @typedef {import("../shared/types").ParserOptions} ParserOptions */
61
59
  /** @typedef {import("../shared/types").Plugin} Plugin */
60
+ /** @typedef {import("../shared/types").ResultsMeta} ResultsMeta */
62
61
  /** @typedef {import("../shared/types").RuleConf} RuleConf */
63
62
  /** @typedef {import("../shared/types").Rule} Rule */
64
63
  /** @typedef {ReturnType<ConfigArray.extractConfig>} ExtractedConfig */
@@ -76,9 +75,8 @@ const LintResultCache = require("../cli-engine/lint-result-cache");
76
75
  * @property {boolean|Function} [fix] Execute in autofix mode. If a function, should return a boolean.
77
76
  * @property {string[]} [fixTypes] Array of rule types to apply fixes for.
78
77
  * @property {boolean} [globInputPaths] Set to false to skip glob resolution of input file paths to lint (default: true). If false, each input file paths is assumed to be a non-glob path to an existing file.
79
- * @property {boolean} [ignore] False disables use of .eslintignore.
80
- * @property {string} [ignorePath] The ignore file to use instead of .eslintignore.
81
- * @property {string[]} [ignorePatterns] Ignore file patterns to use in addition to .eslintignore.
78
+ * @property {boolean} [ignore] False disables all ignore patterns except for the default ones.
79
+ * @property {string[]} [ignorePatterns] Ignore file patterns to use in addition to config ignores.
82
80
  * @property {ConfigData} [overrideConfig] Override config object, overrides all configs used with this instance
83
81
  * @property {boolean|string} [overrideConfigFile] Searches for default config file when falsy;
84
82
  * doesn't do any config file lookup when `true`; considered to be a config filename
@@ -151,30 +149,6 @@ function calculateStatsPerRun(results) {
151
149
  });
152
150
  }
153
151
 
154
- /**
155
- * Loads global ignore patterns from an ignore file (usually .eslintignore).
156
- * @param {string} filePath The filename to load.
157
- * @returns {ignore} A function encapsulating the ignore patterns.
158
- * @throws {Error} If the file cannot be read.
159
- * @private
160
- */
161
- async function loadIgnoreFilePatterns(filePath) {
162
- debug(`Loading ignore file: ${filePath}`);
163
-
164
- try {
165
- const ignoreFileText = await fs.readFile(filePath, { encoding: "utf8" });
166
-
167
- return ignoreFileText
168
- .split(/\r?\n/gu)
169
- .filter(line => line.trim() !== "" && !line.startsWith("#"));
170
-
171
- } catch (e) {
172
- debug(`Error reading ignore file: ${filePath}`);
173
- e.message = `Cannot read ignore file: ${filePath}\nError: ${e.message}`;
174
- throw e;
175
- }
176
- }
177
-
178
152
  /**
179
153
  * Create rulesMeta object.
180
154
  * @param {Map<string,Rule>} rules a map of rules from which to generate the object.
@@ -288,24 +262,16 @@ function findFlatConfigFile(cwd) {
288
262
  /**
289
263
  * Load the config array from the given filename.
290
264
  * @param {string} filePath The filename to load from.
291
- * @param {Object} options Options to help load the config file.
292
- * @param {string} options.basePath The base path for the config array.
293
- * @param {boolean} options.shouldIgnore Whether to honor ignore patterns.
294
- * @returns {Promise<FlatConfigArray>} The config array loaded from the config file.
265
+ * @returns {Promise<any>} The config loaded from the config file.
295
266
  */
296
- async function loadFlatConfigFile(filePath, { basePath, shouldIgnore }) {
267
+ async function loadFlatConfigFile(filePath) {
297
268
  debug(`Loading config from ${filePath}`);
298
269
 
299
270
  const fileURL = pathToFileURL(filePath);
300
271
 
301
272
  debug(`Config file URL is ${fileURL}`);
302
273
 
303
- const module = await import(fileURL);
304
-
305
- return new FlatConfigArray(module.default, {
306
- basePath,
307
- shouldIgnore
308
- });
274
+ return (await import(fileURL)).default;
309
275
  }
310
276
 
311
277
  /**
@@ -316,10 +282,10 @@ async function loadFlatConfigFile(filePath, { basePath, shouldIgnore }) {
316
282
  */
317
283
  async function calculateConfigArray(eslint, {
318
284
  cwd,
285
+ baseConfig,
319
286
  overrideConfig,
320
287
  configFile,
321
288
  ignore: shouldIgnore,
322
- ignorePath,
323
289
  ignorePatterns
324
290
  }) {
325
291
 
@@ -348,38 +314,24 @@ async function calculateConfigArray(eslint, {
348
314
  basePath = path.resolve(path.dirname(configFilePath));
349
315
  }
350
316
 
351
- // load config array
352
- let configs;
353
317
 
318
+ const configs = new FlatConfigArray(baseConfig || [], { basePath, shouldIgnore });
319
+
320
+ // load config file
354
321
  if (configFilePath) {
355
- configs = await loadFlatConfigFile(configFilePath, {
356
- basePath,
357
- shouldIgnore
358
- });
359
- } else {
360
- configs = new FlatConfigArray([], { basePath, shouldIgnore });
322
+ const fileConfig = await loadFlatConfigFile(configFilePath);
323
+
324
+ if (Array.isArray(fileConfig)) {
325
+ configs.push(...fileConfig);
326
+ } else {
327
+ configs.push(fileConfig);
328
+ }
361
329
  }
362
330
 
363
331
  // add in any configured defaults
364
332
  configs.push(...slots.defaultConfigs);
365
333
 
366
334
  let allIgnorePatterns = [];
367
- let ignoreFilePath;
368
-
369
- // load ignore file if necessary
370
- if (shouldIgnore) {
371
- if (ignorePath) {
372
- ignoreFilePath = path.resolve(cwd, ignorePath);
373
- allIgnorePatterns = await loadIgnoreFilePatterns(ignoreFilePath);
374
- } else {
375
- ignoreFilePath = path.resolve(cwd, ".eslintignore");
376
-
377
- // no error if .eslintignore doesn't exist`
378
- if (fileExists(ignoreFilePath)) {
379
- allIgnorePatterns = await loadIgnoreFilePatterns(ignoreFilePath);
380
- }
381
- }
382
- }
383
335
 
384
336
  // append command line ignore patterns
385
337
  if (ignorePatterns) {
@@ -405,17 +357,6 @@ async function calculateConfigArray(eslint, {
405
357
  const negated = pattern.startsWith("!");
406
358
  const basePattern = negated ? pattern.slice(1) : pattern;
407
359
 
408
- /*
409
- * Ignore patterns are considered relative to a directory
410
- * when the pattern contains a slash in a position other
411
- * than the last character. If that's the case, we need to
412
- * add the relative ignore path to the current pattern to
413
- * get the correct behavior. Otherwise, no change is needed.
414
- */
415
- if (!basePattern.includes("/") || basePattern.endsWith("/")) {
416
- return pattern;
417
- }
418
-
419
360
  return (negated ? "!" : "") +
420
361
  path.posix.join(relativeIgnorePath, basePattern);
421
362
  });
@@ -428,7 +369,7 @@ async function calculateConfigArray(eslint, {
428
369
  * so they can override default ignores.
429
370
  */
430
371
  configs.push({
431
- ignores: allIgnorePatterns.map(gitignoreToMinimatch)
372
+ ignores: allIgnorePatterns
432
373
  });
433
374
  }
434
375
 
@@ -708,13 +649,12 @@ class FlatESLint {
708
649
  */
709
650
  getRulesMetaForResults(results) {
710
651
 
711
- const resultRules = new Map();
712
-
713
652
  // short-circuit simple case
714
653
  if (results.length === 0) {
715
- return resultRules;
654
+ return {};
716
655
  }
717
656
 
657
+ const resultRules = new Map();
718
658
  const { configs } = privateMembers.get(this);
719
659
 
720
660
  /*
@@ -743,6 +683,10 @@ class FlatESLint {
743
683
  const allMessages = result.messages.concat(result.suppressedMessages);
744
684
 
745
685
  for (const { ruleId } of allMessages) {
686
+ if (!ruleId) {
687
+ continue;
688
+ }
689
+
746
690
  const rule = getRuleFromConfig(ruleId, config);
747
691
 
748
692
  // ensure the rule exists
@@ -872,7 +816,7 @@ class FlatESLint {
872
816
  }
873
817
 
874
818
 
875
- // set up fixer for fixtypes if necessary
819
+ // set up fixer for fixTypes if necessary
876
820
  let fixer = fix;
877
821
 
878
822
  if (fix && fixTypesSet) {
@@ -1048,7 +992,7 @@ class FlatESLint {
1048
992
  * The following values are allowed:
1049
993
  * - `undefined` ... Load `stylish` builtin formatter.
1050
994
  * - A builtin formatter name ... Load the builtin formatter.
1051
- * - A thirdparty formatter name:
995
+ * - A third-party formatter name:
1052
996
  * - `foo` → `eslint-formatter-foo`
1053
997
  * - `@foo` → `@foo/eslint-formatter`
1054
998
  * - `@foo/bar` → `@foo/eslint-formatter-bar`
@@ -1114,14 +1058,16 @@ class FlatESLint {
1114
1058
  /**
1115
1059
  * The main formatter method.
1116
1060
  * @param {LintResults[]} results The lint results to format.
1061
+ * @param {ResultsMeta} resultsMeta Warning count and max threshold.
1117
1062
  * @returns {string} The formatted lint results.
1118
1063
  */
1119
- format(results) {
1064
+ format(results, resultsMeta) {
1120
1065
  let rulesMeta = null;
1121
1066
 
1122
1067
  results.sort(compareResultsByFilePath);
1123
1068
 
1124
1069
  return formatter(results, {
1070
+ ...resultsMeta,
1125
1071
  cwd,
1126
1072
  get rulesMeta() {
1127
1073
  if (!rulesMeta) {
@@ -213,6 +213,7 @@ function addDeclaredGlobals(globalScope, configGlobals, { exportedVariables, ena
213
213
 
214
214
  if (variable) {
215
215
  variable.eslintUsed = true;
216
+ variable.eslintExported = true;
216
217
  }
217
218
  });
218
219
 
package/lib/options.js CHANGED
@@ -67,7 +67,7 @@ const optionator = require("optionator");
67
67
  /**
68
68
  * Creates the CLI options for ESLint.
69
69
  * @param {boolean} usingFlatConfig Indicates if flat config is being used.
70
- * @returns {Object} The opinionator instance.
70
+ * @returns {Object} The optionator instance.
71
71
  */
72
72
  module.exports = function(usingFlatConfig) {
73
73
 
@@ -129,6 +129,16 @@ module.exports = function(usingFlatConfig) {
129
129
  };
130
130
  }
131
131
 
132
+ let ignorePathFlag;
133
+
134
+ if (!usingFlatConfig) {
135
+ ignorePathFlag = {
136
+ option: "ignore-path",
137
+ type: "path::String",
138
+ description: "Specify path of ignore file"
139
+ };
140
+ }
141
+
132
142
  return optionator({
133
143
  prepend: "eslint [options] file.js [file.js] [dir]",
134
144
  defaults: {
@@ -203,11 +213,7 @@ module.exports = function(usingFlatConfig) {
203
213
  {
204
214
  heading: "Ignoring files"
205
215
  },
206
- {
207
- option: "ignore-path",
208
- type: "path::String",
209
- description: "Specify path of ignore file"
210
- },
216
+ ignorePathFlag,
211
217
  {
212
218
  option: "ignore",
213
219
  type: "Boolean",
@@ -112,18 +112,24 @@ module.exports = {
112
112
  }
113
113
  if (parent.type === "Property" && astUtils.getStaticPropertyName(parent) === "get" && parent.parent.type === "ObjectExpression") {
114
114
 
115
- // Object.defineProperty()
116
- if (parent.parent.parent.type === "CallExpression" &&
117
- astUtils.getStaticPropertyName(parent.parent.parent.callee) === "defineProperty") {
118
- return true;
115
+ // Object.defineProperty() or Reflect.defineProperty()
116
+ if (parent.parent.parent.type === "CallExpression") {
117
+ const callNode = parent.parent.parent.callee;
118
+
119
+ if (astUtils.isSpecificMemberAccess(callNode, "Object", "defineProperty") ||
120
+ astUtils.isSpecificMemberAccess(callNode, "Reflect", "defineProperty")) {
121
+ return true;
122
+ }
119
123
  }
120
124
 
121
- // Object.defineProperties()
125
+ // Object.defineProperties() or Object.create()
122
126
  if (parent.parent.parent.type === "Property" &&
123
127
  parent.parent.parent.parent.type === "ObjectExpression" &&
124
- parent.parent.parent.parent.parent.type === "CallExpression" &&
125
- astUtils.getStaticPropertyName(parent.parent.parent.parent.parent.callee) === "defineProperties") {
126
- return true;
128
+ parent.parent.parent.parent.parent.type === "CallExpression") {
129
+ const callNode = parent.parent.parent.parent.parent.callee;
130
+
131
+ return astUtils.isSpecificMemberAccess(callNode, "Object", "defineProperties") ||
132
+ astUtils.isSpecificMemberAccess(callNode, "Object", "create");
127
133
  }
128
134
  }
129
135
  }
@@ -6,6 +6,45 @@
6
6
 
7
7
  "use strict";
8
8
 
9
+ //------------------------------------------------------------------------------
10
+ // Requirements
11
+ //------------------------------------------------------------------------------
12
+ const GraphemeSplitter = require("grapheme-splitter");
13
+
14
+ //------------------------------------------------------------------------------
15
+ // Helpers
16
+ //------------------------------------------------------------------------------
17
+
18
+ /**
19
+ * Checks if the string given as argument is ASCII or not.
20
+ * @param {string} value A string that you want to know if it is ASCII or not.
21
+ * @returns {boolean} `true` if `value` is ASCII string.
22
+ */
23
+ function isASCII(value) {
24
+ if (typeof value !== "string") {
25
+ return false;
26
+ }
27
+ return /^[\u0020-\u007f]*$/u.test(value);
28
+ }
29
+
30
+ /** @type {GraphemeSplitter | undefined} */
31
+ let splitter;
32
+
33
+ /**
34
+ * Gets the length of the string. If the string is not in ASCII, counts graphemes.
35
+ * @param {string} value A string that you want to get the length.
36
+ * @returns {number} The length of `value`.
37
+ */
38
+ function getStringLength(value) {
39
+ if (isASCII(value)) {
40
+ return value.length;
41
+ }
42
+ if (!splitter) {
43
+ splitter = new GraphemeSplitter();
44
+ }
45
+ return splitter.countGraphemes(value);
46
+ }
47
+
9
48
  //------------------------------------------------------------------------------
10
49
  // Rule Definition
11
50
  //------------------------------------------------------------------------------
@@ -130,8 +169,10 @@ module.exports = {
130
169
  const name = node.name;
131
170
  const parent = node.parent;
132
171
 
133
- const isShort = name.length < minLength;
134
- const isLong = name.length > maxLength;
172
+ const nameLength = getStringLength(name);
173
+
174
+ const isShort = nameLength < minLength;
175
+ const isLong = nameLength > maxLength;
135
176
 
136
177
  if (!(isShort || isLong) || exceptions.has(name) || matchesExceptionPattern(name)) {
137
178
  return; // Nothing to report
@@ -15,7 +15,7 @@ const astUtils = require("./utils/ast-utils");
15
15
  //------------------------------------------------------------------------------
16
16
 
17
17
  /**
18
- * Return an array with with any line numbers that are empty.
18
+ * Return an array with any line numbers that are empty.
19
19
  * @param {Array} lines An array of each line of the file.
20
20
  * @returns {Array} An array of line numbers.
21
21
  */
@@ -29,7 +29,7 @@ function getEmptyLineNums(lines) {
29
29
  }
30
30
 
31
31
  /**
32
- * Return an array with with any line numbers that contain comments.
32
+ * Return an array with any line numbers that contain comments.
33
33
  * @param {Array} comments An array of comment tokens.
34
34
  * @returns {Array} An array of line numbers.
35
35
  */
@@ -77,6 +77,11 @@ module.exports = {
77
77
  return;
78
78
  }
79
79
 
80
+ // Variables exported by "exported" block comments
81
+ if (variable.eslintExported) {
82
+ return;
83
+ }
84
+
80
85
  variable.defs.forEach(def => {
81
86
  const defNode = def.node;
82
87
 
@@ -105,7 +105,7 @@ module.exports = {
105
105
  }
106
106
 
107
107
  /**
108
- * Converts an integer to to an object containing the integer's coefficient and order of magnitude
108
+ * Converts an integer to an object containing the integer's coefficient and order of magnitude
109
109
  * @param {string} stringInteger the string representation of the integer being converted
110
110
  * @returns {Object} the object containing the integer's coefficient and order of magnitude
111
111
  */
@@ -120,7 +120,7 @@ module.exports = {
120
120
 
121
121
  /**
122
122
  *
123
- * Converts a float to to an object containing the floats's coefficient and order of magnitude
123
+ * Converts a float to an object containing the floats's coefficient and order of magnitude
124
124
  * @param {string} stringFloat the string representation of the float being converted
125
125
  * @returns {Object} the object containing the integer's coefficient and order of magnitude
126
126
  */
@@ -68,7 +68,7 @@ function isInClassStaticInitializerRange(node, location) {
68
68
  }
69
69
 
70
70
  /**
71
- * Checks whether a given scope is the scope of a a class static initializer.
71
+ * Checks whether a given scope is the scope of a class static initializer.
72
72
  * Static initializers are static blocks and initializers of static fields.
73
73
  * @param {eslint-scope.Scope} scope A scope to check.
74
74
  * @returns {boolean} `true` if the scope is a class static initializer scope.
@@ -74,7 +74,7 @@ class Traverser {
74
74
  }
75
75
 
76
76
  /**
77
- * Gives a a copy of the ancestor nodes.
77
+ * Gives a copy of the ancestor nodes.
78
78
  * @returns {ASTNode[]} The ancestor nodes.
79
79
  */
80
80
  parents() {
@@ -190,10 +190,23 @@ module.exports = {};
190
190
  * @property {DeprecatedRuleInfo[]} usedDeprecatedRules The list of used deprecated rules.
191
191
  */
192
192
 
193
+ /**
194
+ * Information provided when the maximum warning threshold is exceeded.
195
+ * @typedef {Object} MaxWarningsExceeded
196
+ * @property {number} maxWarnings Number of warnings to trigger nonzero exit code.
197
+ * @property {number} foundWarnings Number of warnings found while linting.
198
+ */
199
+
200
+ /**
201
+ * Metadata about results for formatters.
202
+ * @typedef {Object} ResultsMeta
203
+ * @property {MaxWarningsExceeded} [maxWarningsExceeded] Present if the maxWarnings threshold was exceeded.
204
+ */
205
+
193
206
  /**
194
207
  * A formatter function.
195
208
  * @callback FormatterFunction
196
209
  * @param {LintResult[]} results The list of linting results.
197
- * @param {{cwd: string, rulesMeta: Record<string, RuleMeta>}} [context] A context object.
210
+ * @param {{cwd: string, maxWarningsExceeded?: MaxWarningsExceeded, rulesMeta: Record<string, RuleMeta>}} [context] A context object.
198
211
  * @returns {string | Promise<string>} Formatted text.
199
212
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint",
3
- "version": "8.24.0",
3
+ "version": "8.26.0",
4
4
  "author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
5
5
  "description": "An AST-based pattern checker for JavaScript.",
6
6
  "bin": {
@@ -55,10 +55,10 @@
55
55
  "homepage": "https://eslint.org",
56
56
  "bugs": "https://github.com/eslint/eslint/issues/",
57
57
  "dependencies": {
58
- "@eslint/eslintrc": "^1.3.2",
59
- "@humanwhocodes/config-array": "^0.10.5",
60
- "@humanwhocodes/gitignore-to-minimatch": "^1.0.2",
58
+ "@eslint/eslintrc": "^1.3.3",
59
+ "@humanwhocodes/config-array": "^0.11.6",
61
60
  "@humanwhocodes/module-importer": "^1.0.1",
61
+ "@nodelib/fs.walk": "^1.2.8",
62
62
  "ajv": "^6.10.0",
63
63
  "chalk": "^4.0.0",
64
64
  "cross-spawn": "^7.0.2",
@@ -74,14 +74,14 @@
74
74
  "fast-deep-equal": "^3.1.3",
75
75
  "file-entry-cache": "^6.0.1",
76
76
  "find-up": "^5.0.0",
77
- "glob-parent": "^6.0.1",
77
+ "glob-parent": "^6.0.2",
78
78
  "globals": "^13.15.0",
79
- "globby": "^11.1.0",
80
79
  "grapheme-splitter": "^1.0.4",
81
80
  "ignore": "^5.2.0",
82
81
  "import-fresh": "^3.0.0",
83
82
  "imurmurhash": "^0.1.4",
84
83
  "is-glob": "^4.0.0",
84
+ "is-path-inside": "^3.0.3",
85
85
  "js-sdsl": "^4.1.4",
86
86
  "js-yaml": "^4.1.0",
87
87
  "json-stable-stringify-without-jsonify": "^1.0.1",
@@ -121,7 +121,6 @@
121
121
  "glob": "^7.1.6",
122
122
  "got": "^11.8.3",
123
123
  "gray-matter": "^4.0.3",
124
- "jsdoc": "^3.5.5",
125
124
  "karma": "^6.1.1",
126
125
  "karma-chrome-launcher": "^3.1.0",
127
126
  "karma-mocha": "^2.0.1",