eslint 8.25.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
@@ -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
149
- });
150
- }
151
-
152
146
  } else {
153
147
  overrideConfigFile = config;
154
148
 
@@ -187,7 +181,9 @@ async function translateOptions({
187
181
  reportUnusedDisableDirectives: reportUnusedDisableDirectives ? "error" : void 0
188
182
  };
189
183
 
190
- if (configType !== "flat") {
184
+ if (configType === "flat") {
185
+ options.ignorePatterns = ignorePattern;
186
+ } else {
191
187
  options.resolvePluginsRelativeTo = resolvePluginsRelativeTo;
192
188
  options.rulePaths = rulesdir;
193
189
  options.useEslintrc = eslintrc;
@@ -279,6 +275,31 @@ async function printResults(engine, results, format, outputFile, resultsMeta) {
279
275
  return true;
280
276
  }
281
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
+
282
303
  //------------------------------------------------------------------------------
283
304
  // Public Interface
284
305
  //------------------------------------------------------------------------------
@@ -308,7 +329,7 @@ const cli = {
308
329
  * switch to flat config we can remove this logic.
309
330
  */
310
331
 
311
- const usingFlatConfig = allowFlatConfig && !!(await findFlatConfigFile(process.cwd()));
332
+ const usingFlatConfig = await shouldUseFlatConfig(allowFlatConfig);
312
333
 
313
334
  debug("Using flat config?", usingFlatConfig);
314
335
 
@@ -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
 
@@ -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
@@ -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) {
352
+
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
+ }
248
367
 
249
- // no files were found
250
- if (errorOnUnmatchedPattern) {
251
- throw new NoFilesFoundError(globbyPattern, globInputPaths);
368
+ index++;
252
369
  }
253
370
  }
254
- /* eslint-enable no-unreachable-loop -- Go back to normal. */
255
371
 
256
372
  }
257
373
 
@@ -262,24 +262,16 @@ function findFlatConfigFile(cwd) {
262
262
  /**
263
263
  * Load the config array from the given filename.
264
264
  * @param {string} filePath The filename to load from.
265
- * @param {Object} options Options to help load the config file.
266
- * @param {string} options.basePath The base path for the config array.
267
- * @param {boolean} options.shouldIgnore Whether to honor ignore patterns.
268
- * @returns {Promise<FlatConfigArray>} The config array loaded from the config file.
265
+ * @returns {Promise<any>} The config loaded from the config file.
269
266
  */
270
- async function loadFlatConfigFile(filePath, { basePath, shouldIgnore }) {
267
+ async function loadFlatConfigFile(filePath) {
271
268
  debug(`Loading config from ${filePath}`);
272
269
 
273
270
  const fileURL = pathToFileURL(filePath);
274
271
 
275
272
  debug(`Config file URL is ${fileURL}`);
276
273
 
277
- const module = await import(fileURL);
278
-
279
- return new FlatConfigArray(module.default, {
280
- basePath,
281
- shouldIgnore
282
- });
274
+ return (await import(fileURL)).default;
283
275
  }
284
276
 
285
277
  /**
@@ -290,6 +282,7 @@ async function loadFlatConfigFile(filePath, { basePath, shouldIgnore }) {
290
282
  */
291
283
  async function calculateConfigArray(eslint, {
292
284
  cwd,
285
+ baseConfig,
293
286
  overrideConfig,
294
287
  configFile,
295
288
  ignore: shouldIgnore,
@@ -321,16 +314,18 @@ async function calculateConfigArray(eslint, {
321
314
  basePath = path.resolve(path.dirname(configFilePath));
322
315
  }
323
316
 
324
- // load config array
325
- let configs;
326
317
 
318
+ const configs = new FlatConfigArray(baseConfig || [], { basePath, shouldIgnore });
319
+
320
+ // load config file
327
321
  if (configFilePath) {
328
- configs = await loadFlatConfigFile(configFilePath, {
329
- basePath,
330
- shouldIgnore
331
- });
332
- } else {
333
- 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
+ }
334
329
  }
335
330
 
336
331
  // add in any configured defaults
@@ -362,17 +357,6 @@ async function calculateConfigArray(eslint, {
362
357
  const negated = pattern.startsWith("!");
363
358
  const basePattern = negated ? pattern.slice(1) : pattern;
364
359
 
365
- /*
366
- * Ignore patterns are considered relative to a directory
367
- * when the pattern contains a slash in a position other
368
- * than the last character. If that's the case, we need to
369
- * add the relative ignore path to the current pattern to
370
- * get the correct behavior. Otherwise, no change is needed.
371
- */
372
- if (!basePattern.includes("/") || basePattern.endsWith("/")) {
373
- return pattern;
374
- }
375
-
376
360
  return (negated ? "!" : "") +
377
361
  path.posix.join(relativeIgnorePath, basePattern);
378
362
  });
@@ -665,13 +649,12 @@ class FlatESLint {
665
649
  */
666
650
  getRulesMetaForResults(results) {
667
651
 
668
- const resultRules = new Map();
669
-
670
652
  // short-circuit simple case
671
653
  if (results.length === 0) {
672
- return resultRules;
654
+ return {};
673
655
  }
674
656
 
657
+ const resultRules = new Map();
675
658
  const { configs } = privateMembers.get(this);
676
659
 
677
660
  /*
@@ -700,6 +683,10 @@ class FlatESLint {
700
683
  const allMessages = result.messages.concat(result.suppressedMessages);
701
684
 
702
685
  for (const { ruleId } of allMessages) {
686
+ if (!ruleId) {
687
+ continue;
688
+ }
689
+
703
690
  const rule = getRuleFromConfig(ruleId, config);
704
691
 
705
692
  // ensure the rule exists
@@ -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
 
@@ -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
  }
@@ -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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint",
3
- "version": "8.25.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": {
@@ -56,8 +56,9 @@
56
56
  "bugs": "https://github.com/eslint/eslint/issues/",
57
57
  "dependencies": {
58
58
  "@eslint/eslintrc": "^1.3.3",
59
- "@humanwhocodes/config-array": "^0.10.5",
59
+ "@humanwhocodes/config-array": "^0.11.6",
60
60
  "@humanwhocodes/module-importer": "^1.0.1",
61
+ "@nodelib/fs.walk": "^1.2.8",
61
62
  "ajv": "^6.10.0",
62
63
  "chalk": "^4.0.0",
63
64
  "cross-spawn": "^7.0.2",
@@ -73,14 +74,14 @@
73
74
  "fast-deep-equal": "^3.1.3",
74
75
  "file-entry-cache": "^6.0.1",
75
76
  "find-up": "^5.0.0",
76
- "glob-parent": "^6.0.1",
77
+ "glob-parent": "^6.0.2",
77
78
  "globals": "^13.15.0",
78
- "globby": "^11.1.0",
79
79
  "grapheme-splitter": "^1.0.4",
80
80
  "ignore": "^5.2.0",
81
81
  "import-fresh": "^3.0.0",
82
82
  "imurmurhash": "^0.1.4",
83
83
  "is-glob": "^4.0.0",
84
+ "is-path-inside": "^3.0.3",
84
85
  "js-sdsl": "^4.1.4",
85
86
  "js-yaml": "^4.1.0",
86
87
  "json-stable-stringify-without-jsonify": "^1.0.1",