eslint 8.26.0 → 8.27.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.
@@ -43,85 +43,110 @@ function pageTemplate(it) {
43
43
  <link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PScwIDAgMjk0LjgyNSAyNTguOTgyJyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnPg0KPHBhdGggZmlsbD0nIzgwODBGMicgZD0nTTk3LjAyMSw5OS4wMTZsNDguNDMyLTI3Ljk2MmMxLjIxMi0wLjcsMi43MDYtMC43LDMuOTE4LDBsNDguNDMzLDI3Ljk2MiBjMS4yMTEsMC43LDEuOTU5LDEuOTkzLDEuOTU5LDMuMzkzdjU1LjkyNGMwLDEuMzk5LTAuNzQ4LDIuNjkzLTEuOTU5LDMuMzk0bC00OC40MzMsMjcuOTYyYy0xLjIxMiwwLjctMi43MDYsMC43LTMuOTE4LDAgbC00OC40MzItMjcuOTYyYy0xLjIxMi0wLjctMS45NTktMS45OTQtMS45NTktMy4zOTR2LTU1LjkyNEM5NS4wNjMsMTAxLjAwOSw5NS44MSw5OS43MTYsOTcuMDIxLDk5LjAxNicvPg0KPHBhdGggZmlsbD0nIzRCMzJDMycgZD0nTTI3My4zMzYsMTI0LjQ4OEwyMTUuNDY5LDIzLjgxNmMtMi4xMDItMy42NC01Ljk4NS02LjMyNS0xMC4xODgtNi4zMjVIODkuNTQ1IGMtNC4yMDQsMC04LjA4OCwyLjY4NS0xMC4xOSw2LjMyNWwtNTcuODY3LDEwMC40NWMtMi4xMDIsMy42NDEtMi4xMDIsOC4yMzYsMCwxMS44NzdsNTcuODY3LDk5Ljg0NyBjMi4xMDIsMy42NCw1Ljk4Niw1LjUwMSwxMC4xOSw1LjUwMWgxMTUuNzM1YzQuMjAzLDAsOC4wODctMS44MDUsMTAuMTg4LTUuNDQ2bDU3Ljg2Ny0xMDAuMDEgQzI3NS40MzksMTMyLjM5NiwyNzUuNDM5LDEyOC4xMjgsMjczLjMzNiwxMjQuNDg4IE0yMjUuNDE5LDE3Mi44OThjMCwxLjQ4LTAuODkxLDIuODQ5LTIuMTc0LDMuNTlsLTczLjcxLDQyLjUyNyBjLTEuMjgyLDAuNzQtMi44ODgsMC43NC00LjE3LDBsLTczLjc2Ny00Mi41MjdjLTEuMjgyLTAuNzQxLTIuMTc5LTIuMTA5LTIuMTc5LTMuNTlWODcuODQzYzAtMS40ODEsMC44ODQtMi44NDksMi4xNjctMy41OSBsNzMuNzA3LTQyLjUyN2MxLjI4Mi0wLjc0MSwyLjg4Ni0wLjc0MSw0LjE2OCwwbDczLjc3Miw0Mi41MjdjMS4yODMsMC43NDEsMi4xODYsMi4xMDksMi4xODYsMy41OVYxNzIuODk4eicvPg0KPC9zdmc+">
44
44
  <style>
45
45
  body {
46
- font-family:Arial, "Helvetica Neue", Helvetica, sans-serif;
47
- font-size:16px;
48
- font-weight:normal;
49
- margin:0;
50
- padding:0;
51
- color:#333
46
+ font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
47
+ font-size: 16px;
48
+ font-weight: normal;
49
+ margin: 0;
50
+ padding: 0;
51
+ color: #333;
52
52
  }
53
+
53
54
  #overview {
54
- padding:20px 30px
55
+ padding: 20px 30px;
55
56
  }
56
- td, th {
57
- padding:5px 10px
57
+
58
+ td,
59
+ th {
60
+ padding: 5px 10px;
58
61
  }
62
+
59
63
  h1 {
60
- margin:0
64
+ margin: 0;
61
65
  }
66
+
62
67
  table {
63
- margin:30px;
64
- width:calc(100% - 60px);
65
- max-width:1000px;
66
- border-radius:5px;
67
- border:1px solid #ddd;
68
- border-spacing:0px;
68
+ margin: 30px;
69
+ width: calc(100% - 60px);
70
+ max-width: 1000px;
71
+ border-radius: 5px;
72
+ border: 1px solid #ddd;
73
+ border-spacing: 0;
69
74
  }
75
+
70
76
  th {
71
- font-weight:400;
72
- font-size:medium;
73
- text-align:left;
74
- cursor:pointer
77
+ font-weight: 400;
78
+ font-size: medium;
79
+ text-align: left;
80
+ cursor: pointer;
75
81
  }
76
- td.clr-1, td.clr-2, th span {
77
- font-weight:700
82
+
83
+ td.clr-1,
84
+ td.clr-2,
85
+ th span {
86
+ font-weight: 700;
78
87
  }
88
+
79
89
  th span {
80
- float:right;
81
- margin-left:20px
90
+ float: right;
91
+ margin-left: 20px;
82
92
  }
83
- th span:after {
84
- content:"";
85
- clear:both;
86
- display:block
93
+
94
+ th span::after {
95
+ content: "";
96
+ clear: both;
97
+ display: block;
87
98
  }
99
+
88
100
  tr:last-child td {
89
- border-bottom:none
101
+ border-bottom: none;
90
102
  }
91
- tr td:first-child, tr td:last-child {
92
- color:#9da0a4
103
+
104
+ tr td:first-child,
105
+ tr td:last-child {
106
+ color: #9da0a4;
93
107
  }
94
- #overview.bg-0, tr.bg-0 th {
95
- color:#468847;
96
- background:#dff0d8;
97
- border-bottom:1px solid #d6e9c6
108
+
109
+ #overview.bg-0,
110
+ tr.bg-0 th {
111
+ color: #468847;
112
+ background: #dff0d8;
113
+ border-bottom: 1px solid #d6e9c6;
98
114
  }
99
- #overview.bg-1, tr.bg-1 th {
100
- color:#f0ad4e;
101
- background:#fcf8e3;
102
- border-bottom:1px solid #fbeed5
115
+
116
+ #overview.bg-1,
117
+ tr.bg-1 th {
118
+ color: #f0ad4e;
119
+ background: #fcf8e3;
120
+ border-bottom: 1px solid #fbeed5;
103
121
  }
104
- #overview.bg-2, tr.bg-2 th {
105
- color:#b94a48;
106
- background:#f2dede;
107
- border-bottom:1px solid #eed3d7
122
+
123
+ #overview.bg-2,
124
+ tr.bg-2 th {
125
+ color: #b94a48;
126
+ background: #f2dede;
127
+ border-bottom: 1px solid #eed3d7;
108
128
  }
129
+
109
130
  td {
110
- border-bottom:1px solid #ddd
131
+ border-bottom: 1px solid #ddd;
111
132
  }
133
+
112
134
  td.clr-1 {
113
- color:#f0ad4e
135
+ color: #f0ad4e;
114
136
  }
137
+
115
138
  td.clr-2 {
116
- color:#b94a48
139
+ color: #b94a48;
117
140
  }
141
+
118
142
  td a {
119
- color:#3a33d1;
120
- text-decoration:none
143
+ color: #3a33d1;
144
+ text-decoration: none;
121
145
  }
146
+
122
147
  td a:hover {
123
- color:#272296;
124
- text-decoration:underline
148
+ color: #272296;
149
+ text-decoration: underline;
125
150
  }
126
151
  </style>
127
152
  </head>
@@ -214,7 +239,7 @@ function messageTemplate(it) {
214
239
  } = it;
215
240
 
216
241
  return `
217
- <tr style="display:none" class="f-${parentIndex}">
242
+ <tr style="display: none;" class="f-${parentIndex}">
218
243
  <td>${lineNumber}:${columnNumber}</td>
219
244
  <td class="clr-${severityNumber}">${severityName}</td>
220
245
  <td>${encodeHTML(message)}</td>
@@ -27,6 +27,17 @@ const isPathInside = require("is-path-inside");
27
27
  const doFsWalk = util.promisify(fswalk.walk);
28
28
  const Minimatch = minimatch.Minimatch;
29
29
 
30
+ //-----------------------------------------------------------------------------
31
+ // Types
32
+ //-----------------------------------------------------------------------------
33
+
34
+ /**
35
+ * @typedef {Object} GlobSearch
36
+ * @property {Array<string>} patterns The normalized patterns to use for a search.
37
+ * @property {Array<string>} rawPatterns The patterns as entered by the user
38
+ * before doing any normalization.
39
+ */
40
+
30
41
  //-----------------------------------------------------------------------------
31
42
  // Errors
32
43
  //-----------------------------------------------------------------------------
@@ -47,6 +58,30 @@ class NoFilesFoundError extends Error {
47
58
  }
48
59
  }
49
60
 
61
+ /**
62
+ * The error type when a search fails to match multiple patterns.
63
+ */
64
+ class UnmatchedSearchPatternsError extends Error {
65
+
66
+ /**
67
+ * @param {Object} options The options for the error.
68
+ * @param {string} options.basePath The directory that was searched.
69
+ * @param {Array<string>} options.unmatchedPatterns The glob patterns
70
+ * which were not found.
71
+ * @param {Array<string>} options.patterns The glob patterns that were
72
+ * searched.
73
+ * @param {Array<string>} options.rawPatterns The raw glob patterns that
74
+ * were searched.
75
+ */
76
+ constructor({ basePath, unmatchedPatterns, patterns, rawPatterns }) {
77
+ super(`No files matching '${rawPatterns}' in '${basePath}' were found.`);
78
+ this.basePath = basePath;
79
+ this.patternsToCheck = unmatchedPatterns;
80
+ this.patterns = patterns;
81
+ this.rawPatterns = rawPatterns;
82
+ }
83
+ }
84
+
50
85
  /**
51
86
  * The error type when there are files matched by a glob, but all of them have been ignored.
52
87
  */
@@ -107,6 +142,69 @@ function isGlobPattern(pattern) {
107
142
  return isGlob(path.sep === "\\" ? normalizeToPosix(pattern) : pattern);
108
143
  }
109
144
 
145
+
146
+ /**
147
+ * Determines if a given glob pattern will return any results.
148
+ * Used primarily to help with useful error messages.
149
+ * @param {Object} options The options for the function.
150
+ * @param {string} options.basePath The directory to search.
151
+ * @param {string} options.pattern A glob pattern to match.
152
+ * @returns {Promise<boolean>} True if there is a glob match, false if not.
153
+ */
154
+ function globMatch({ basePath, pattern }) {
155
+
156
+ let found = false;
157
+ const patternToUse = path.isAbsolute(pattern)
158
+ ? normalizeToPosix(path.relative(basePath, pattern))
159
+ : pattern;
160
+
161
+ const matcher = new Minimatch(patternToUse);
162
+
163
+ const fsWalkSettings = {
164
+
165
+ deepFilter(entry) {
166
+ const relativePath = normalizeToPosix(path.relative(basePath, entry.path));
167
+
168
+ return !found && matcher.match(relativePath, true);
169
+ },
170
+
171
+ entryFilter(entry) {
172
+ if (found || entry.dirent.isDirectory()) {
173
+ return false;
174
+ }
175
+
176
+ const relativePath = normalizeToPosix(path.relative(basePath, entry.path));
177
+
178
+ if (matcher.match(relativePath)) {
179
+ found = true;
180
+ return true;
181
+ }
182
+
183
+ return false;
184
+ }
185
+ };
186
+
187
+ return new Promise(resolve => {
188
+
189
+ // using a stream so we can exit early because we just need one match
190
+ const globStream = fswalk.walkStream(basePath, fsWalkSettings);
191
+
192
+ globStream.on("data", () => {
193
+ globStream.destroy();
194
+ resolve(true);
195
+ });
196
+
197
+ // swallow errors as they're not important here
198
+ globStream.on("error", () => { });
199
+
200
+ globStream.on("end", () => {
201
+ resolve(false);
202
+ });
203
+ globStream.read();
204
+ });
205
+
206
+ }
207
+
110
208
  /**
111
209
  * Searches a directory looking for matching glob patterns. This uses
112
210
  * the config array's logic to determine if a directory or file should
@@ -116,26 +214,62 @@ function isGlobPattern(pattern) {
116
214
  * @param {string} options.basePath The directory to search.
117
215
  * @param {Array<string>} options.patterns An array of glob patterns
118
216
  * to match.
217
+ * @param {Array<string>} options.rawPatterns An array of glob patterns
218
+ * as the user inputted them. Used for errors.
119
219
  * @param {FlatConfigArray} options.configs The config array to use for
120
220
  * determining what to ignore.
221
+ * @param {boolean} options.errorOnUnmatchedPattern Determines if an error
222
+ * should be thrown when a pattern is unmatched.
121
223
  * @returns {Promise<Array<string>>} An array of matching file paths
122
224
  * or an empty array if there are no matches.
225
+ * @throws {UnmatchedSearchPatternsErrror} If there is a pattern that doesn't
226
+ * match any files.
123
227
  */
124
- async function globSearch({ basePath, patterns, configs }) {
228
+ async function globSearch({
229
+ basePath,
230
+ patterns,
231
+ rawPatterns,
232
+ configs,
233
+ errorOnUnmatchedPattern
234
+ }) {
125
235
 
126
236
  if (patterns.length === 0) {
127
237
  return [];
128
238
  }
129
239
 
130
- const matchers = patterns.map(pattern => {
240
+ /*
241
+ * In this section we are converting the patterns into Minimatch
242
+ * instances for performance reasons. Because we are doing the same
243
+ * matches repeatedly, it's best to compile those patterns once and
244
+ * reuse them multiple times.
245
+ *
246
+ * To do that, we convert any patterns with an absolute path into a
247
+ * relative path and normalize it to Posix-style slashes. We also keep
248
+ * track of the relative patterns to map them back to the original
249
+ * patterns, which we need in order to throw an error if there are any
250
+ * unmatched patterns.
251
+ */
252
+ const relativeToPatterns = new Map();
253
+ const matchers = patterns.map((pattern, i) => {
131
254
  const patternToUse = path.isAbsolute(pattern)
132
255
  ? normalizeToPosix(path.relative(basePath, pattern))
133
256
  : pattern;
134
257
 
258
+ relativeToPatterns.set(patternToUse, patterns[i]);
259
+
135
260
  return new minimatch.Minimatch(patternToUse);
136
261
  });
137
262
 
138
- return (await doFsWalk(basePath, {
263
+ /*
264
+ * We track unmatched patterns because we may want to throw an error when
265
+ * they occur. To start, this set is initialized with all of the patterns.
266
+ * Every time a match occurs, the pattern is removed from the set, making
267
+ * it easy to tell if we have any unmatched patterns left at the end of
268
+ * search.
269
+ */
270
+ const unmatchedPatterns = new Set([...relativeToPatterns.keys()]);
271
+
272
+ const filePaths = (await doFsWalk(basePath, {
139
273
 
140
274
  deepFilter(entry) {
141
275
  const relativePath = normalizeToPosix(path.relative(basePath, entry.path));
@@ -151,94 +285,177 @@ async function globSearch({ basePath, patterns, configs }) {
151
285
  return false;
152
286
  }
153
287
 
154
- const matchesPattern = matchers.some(matcher => matcher.match(relativePath));
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(relativePath);
300
+
301
+ /*
302
+ * We updated the unmatched patterns set only if the path
303
+ * matches and the file isn't ignored. If the file is
304
+ * ignored, that means there wasn't a match for the
305
+ * pattern so it should not be removed.
306
+ *
307
+ * Performance note: isFileIgnored() aggressively caches
308
+ * results so there is no performance penalty for calling
309
+ * it twice with the same argument.
310
+ */
311
+ if (pathMatches && !configs.isFileIgnored(entry.path)) {
312
+ unmatchedPatterns.delete(matcher.pattern);
313
+ }
314
+
315
+ return pathMatches || previousValue;
316
+ }, false)
317
+ : matchers.some(matcher => matcher.match(relativePath));
155
318
 
156
319
  return matchesPattern && !configs.isFileIgnored(entry.path);
157
320
  }
321
+
158
322
  })).map(entry => entry.path);
159
323
 
324
+ // now check to see if we have any unmatched patterns
325
+ if (errorOnUnmatchedPattern && unmatchedPatterns.size > 0) {
326
+ throw new UnmatchedSearchPatternsError({
327
+ basePath,
328
+ unmatchedPatterns: [...unmatchedPatterns].map(
329
+ pattern => relativeToPatterns.get(pattern)
330
+ ),
331
+ patterns,
332
+ rawPatterns
333
+ });
334
+ }
335
+
336
+ return filePaths;
337
+ }
338
+
339
+ /**
340
+ * Checks to see if there are any ignored results for a given search. This
341
+ * happens either when there are unmatched patterns during a search or if
342
+ * a search returns no results.
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.patternsToCheck An array of glob patterns
350
+ * to use for this check.
351
+ * @returns {void}
352
+ * @throws {NoFilesFoundError} If there is a pattern that doesn't match
353
+ * any files and `errorOnUnmatchedPattern` is true.
354
+ * @throws {AllFilesIgnoredError} If there is a pattern that matches files
355
+ * when there are no ignores.
356
+ */
357
+ async function checkForIgnoredResults({
358
+ basePath,
359
+ patterns,
360
+ rawPatterns,
361
+ patternsToCheck = patterns
362
+ }) {
363
+
364
+ for (const pattern of patternsToCheck) {
365
+
366
+ const patternHasMatch = await globMatch({
367
+ basePath,
368
+ pattern
369
+ });
370
+
371
+ if (patternHasMatch) {
372
+ throw new AllFilesIgnoredError(
373
+ rawPatterns[patterns.indexOf(pattern)]
374
+ );
375
+ }
376
+ }
377
+
378
+ // if we get here there are truly no matches
379
+ throw new NoFilesFoundError(
380
+ rawPatterns[patterns.indexOf(patternsToCheck[0])],
381
+ true
382
+ );
160
383
  }
161
384
 
162
385
  /**
163
386
  * Performs multiple glob searches in parallel.
164
387
  * @param {Object} options The options for this function.
165
- * @param {Array<{patterns:Array<string>,rawPatterns:Array<string>}>} options.searches
388
+ * @param {Map<string,GlobSearch>} options.searches
166
389
  * An array of glob patterns to match.
167
390
  * @param {FlatConfigArray} options.configs The config array to use for
168
391
  * determining what to ignore.
392
+ * @param {boolean} options.errorOnUnmatchedPattern Determines if an
393
+ * unmatched glob pattern should throw an error.
169
394
  * @returns {Promise<Array<string>>} An array of matching file paths
170
395
  * or an empty array if there are no matches.
171
396
  */
172
- async function globMultiSearch({ searches, configs }) {
397
+ async function globMultiSearch({ searches, configs, errorOnUnmatchedPattern }) {
173
398
 
174
- const results = await Promise.all(
175
- [...searches].map(
176
- ([basePath, { patterns }]) => globSearch({ basePath, patterns, configs })
399
+ /*
400
+ * For convenience, we normalized the search map into an array of objects.
401
+ * Next, we filter out all searches that have no patterns. This happens
402
+ * primarily for the cwd, which is prepopulated in the searches map as an
403
+ * optimization. However, if it has no patterns, it means all patterns
404
+ * occur outside of the cwd and we can safely filter out that search.
405
+ */
406
+ const normalizedSearches = [...searches].map(
407
+ ([basePath, { patterns, rawPatterns }]) => ({ basePath, patterns, rawPatterns })
408
+ ).filter(({ patterns }) => patterns.length > 0);
409
+
410
+ const results = await Promise.allSettled(
411
+ normalizedSearches.map(
412
+ ({ basePath, patterns, rawPatterns }) => globSearch({
413
+ basePath,
414
+ patterns,
415
+ rawPatterns,
416
+ configs,
417
+ errorOnUnmatchedPattern
418
+ })
177
419
  )
178
420
  );
179
421
 
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;
422
+ const filePaths = [];
197
423
 
198
- const matcher = new Minimatch(patternToUse);
424
+ for (let i = 0; i < results.length; i++) {
199
425
 
200
- const fsWalkSettings = {
426
+ const result = results[i];
427
+ const currentSearch = normalizedSearches[i];
201
428
 
202
- deepFilter(entry) {
203
- const relativePath = normalizeToPosix(path.relative(basePath, entry.path));
429
+ if (result.status === "fulfilled") {
204
430
 
205
- return !found && matcher.match(relativePath, true);
206
- },
207
-
208
- entryFilter(entry) {
209
- if (found || entry.dirent.isDirectory()) {
210
- return false;
431
+ // if the search was successful just add the results
432
+ if (result.value.length > 0) {
433
+ filePaths.push(...result.value);
211
434
  }
212
435
 
213
- const relativePath = normalizeToPosix(path.relative(basePath, entry.path));
436
+ continue;
437
+ }
214
438
 
215
- if (matcher.match(relativePath)) {
216
- found = true;
217
- return true;
218
- }
439
+ // if we make it here then there was an error
440
+ const error = result.reason;
219
441
 
220
- return false;
442
+ // unexpected errors should be re-thrown
443
+ if (!error.basePath) {
444
+ throw error;
221
445
  }
222
- };
223
446
 
224
- return new Promise(resolve => {
447
+ if (errorOnUnmatchedPattern) {
225
448
 
226
- // using a stream so we can exit early because we just need one match
227
- const globStream = fswalk.walkStream(basePath, fsWalkSettings);
449
+ await checkForIgnoredResults({
450
+ ...currentSearch,
451
+ patternsToCheck: error.patternsToCheck
452
+ });
228
453
 
229
- globStream.on("data", () => {
230
- globStream.destroy();
231
- resolve(true);
232
- });
454
+ }
233
455
 
234
- // swallow errors as they're not important here
235
- globStream.on("error", () => {});
456
+ }
236
457
 
237
- globStream.on("end", () => {
238
- resolve(false);
239
- });
240
- globStream.read();
241
- });
458
+ return [...new Set(filePaths)];
242
459
 
243
460
  }
244
461
 
@@ -335,47 +552,17 @@ async function findFiles({
335
552
  }
336
553
  });
337
554
 
338
- const globbyResults = await globMultiSearch({
339
- searches,
340
- configs
341
- });
342
-
343
- // if there are no results, tell the user why
344
- if (!results.length && !globbyResults.length) {
345
-
346
- for (const [basePath, { patterns: patternsToCheck, rawPatterns: patternsToReport }] of searches) {
347
-
348
- let index = 0;
349
-
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
- }
367
-
368
- index++;
369
- }
370
- }
371
-
372
- }
373
-
374
555
  // there were patterns that didn't match anything, tell the user
375
556
  if (errorOnUnmatchedPattern && missingPatterns.length) {
376
557
  throw new NoFilesFoundError(missingPatterns[0], globInputPaths);
377
558
  }
378
559
 
560
+ // now we are safe to do the search
561
+ const globbyResults = await globMultiSearch({
562
+ searches,
563
+ configs,
564
+ errorOnUnmatchedPattern
565
+ });
379
566
 
380
567
  return [
381
568
  ...results,
@@ -161,6 +161,16 @@ function createRulesMeta(rules) {
161
161
  }, {});
162
162
  }
163
163
 
164
+ /**
165
+ * Return the absolute path of a file named `"__placeholder__.js"` in a given directory.
166
+ * This is used as a replacement for a missing file path.
167
+ * @param {string} cwd An absolute directory path.
168
+ * @returns {string} The absolute path of a file named `"__placeholder__.js"` in the given directory.
169
+ */
170
+ function getPlaceholderPath(cwd) {
171
+ return path.join(cwd, "__placeholder__.js");
172
+ }
173
+
164
174
  /** @type {WeakMap<ExtractedConfig, DeprecatedRuleInfo[]>} */
165
175
  const usedDeprecatedRulesCache = new WeakMap();
166
176
 
@@ -177,7 +187,7 @@ function getOrFindUsedDeprecatedRules(eslint, maybeFilePath) {
177
187
  } = privateMembers.get(eslint);
178
188
  const filePath = path.isAbsolute(maybeFilePath)
179
189
  ? maybeFilePath
180
- : path.join(cwd, "__placeholder__.js");
190
+ : getPlaceholderPath(cwd);
181
191
  const config = configs.getConfig(filePath);
182
192
 
183
193
  // Most files use the same config, so cache it.
@@ -422,7 +432,7 @@ function verifyText({
422
432
  * `config.extractConfig(filePath)` requires an absolute path, but `linter`
423
433
  * doesn't know CWD, so it gives `linter` an absolute path always.
424
434
  */
425
- const filePathToVerify = filePath === "<text>" ? path.join(cwd, "__placeholder__.js") : filePath;
435
+ const filePathToVerify = filePath === "<text>" ? getPlaceholderPath(cwd) : filePath;
426
436
  const { fixed, messages, output } = linter.verifyAndFix(
427
437
  text,
428
438
  configs,
@@ -519,6 +529,14 @@ function *iterateRuleDeprecationWarnings(configs) {
519
529
  }
520
530
  }
521
531
 
532
+ /**
533
+ * Creates an error to be thrown when an array of results passed to `getRulesMetaForResults` was not created by the current engine.
534
+ * @returns {TypeError} An error object.
535
+ */
536
+ function createExtraneousResultsError() {
537
+ return new TypeError("Results object was not created from this ESLint instance.");
538
+ }
539
+
522
540
  //-----------------------------------------------------------------------------
523
541
  // Main API
524
542
  //-----------------------------------------------------------------------------
@@ -655,7 +673,10 @@ class FlatESLint {
655
673
  }
656
674
 
657
675
  const resultRules = new Map();
658
- const { configs } = privateMembers.get(this);
676
+ const {
677
+ configs,
678
+ options: { cwd }
679
+ } = privateMembers.get(this);
659
680
 
660
681
  /*
661
682
  * We can only accurately return rules meta information for linting results if the
@@ -664,7 +685,7 @@ class FlatESLint {
664
685
  * to let the user know we can't do anything here.
665
686
  */
666
687
  if (!configs) {
667
- throw new TypeError("Results object was not created from this ESLint instance.");
688
+ throw createExtraneousResultsError();
668
689
  }
669
690
 
670
691
  for (const result of results) {
@@ -673,13 +694,7 @@ class FlatESLint {
673
694
  * Normalize filename for <text>.
674
695
  */
675
696
  const filePath = result.filePath === "<text>"
676
- ? "__placeholder__.js" : result.filePath;
677
-
678
- /*
679
- * All of the plugin and rule information is contained within the
680
- * calculated config for the given file.
681
- */
682
- const config = configs.getConfig(filePath);
697
+ ? getPlaceholderPath(cwd) : result.filePath;
683
698
  const allMessages = result.messages.concat(result.suppressedMessages);
684
699
 
685
700
  for (const { ruleId } of allMessages) {
@@ -687,6 +702,15 @@ class FlatESLint {
687
702
  continue;
688
703
  }
689
704
 
705
+ /*
706
+ * All of the plugin and rule information is contained within the
707
+ * calculated config for the given file.
708
+ */
709
+ const config = configs.getConfig(filePath);
710
+
711
+ if (!config) {
712
+ throw createExtraneousResultsError();
713
+ }
690
714
  const rule = getRuleFromConfig(ruleId, config);
691
715
 
692
716
  // ensure the rule exists
@@ -1024,7 +1048,7 @@ class FlatESLint {
1024
1048
  const npmFormat = naming.normalizePackageName(normalizedFormatName, "eslint-formatter");
1025
1049
 
1026
1050
  // TODO: This is pretty dirty...would be nice to clean up at some point.
1027
- formatterPath = ModuleResolver.resolve(npmFormat, path.join(cwd, "__placeholder__.js"));
1051
+ formatterPath = ModuleResolver.resolve(npmFormat, getPlaceholderPath(cwd));
1028
1052
  } catch {
1029
1053
  formatterPath = path.resolve(__dirname, "../", "cli-engine", "formatters", `${normalizedFormatName}.js`);
1030
1054
  }
@@ -50,7 +50,7 @@ function normalizeOptions(optionValue, ecmaVersion) {
50
50
  objects: optionValue,
51
51
  imports: optionValue,
52
52
  exports: optionValue,
53
- functions: (!ecmaVersion || ecmaVersion < 8) ? "ignore" : optionValue
53
+ functions: ecmaVersion < 2017 ? "ignore" : optionValue
54
54
  };
55
55
  }
56
56
  if (typeof optionValue === "object" && optionValue !== null) {
@@ -134,7 +134,7 @@ module.exports = {
134
134
  },
135
135
 
136
136
  create(context) {
137
- const options = normalizeOptions(context.options[0], context.parserOptions.ecmaVersion);
137
+ const options = normalizeOptions(context.options[0], context.languageOptions.ecmaVersion);
138
138
 
139
139
  const sourceCode = context.getSourceCode();
140
140
 
@@ -44,7 +44,7 @@ function isModuleExports(pattern) {
44
44
  * @returns {boolean} True if the string is a valid identifier
45
45
  */
46
46
  function isIdentifier(name, ecmaVersion) {
47
- if (ecmaVersion >= 6) {
47
+ if (ecmaVersion >= 2015) {
48
48
  return esutils.keyword.isIdentifierES6(name);
49
49
  }
50
50
  return esutils.keyword.isIdentifierES5(name);
@@ -104,7 +104,7 @@ module.exports = {
104
104
  const nameMatches = typeof context.options[0] === "string" ? context.options[0] : "always";
105
105
  const considerPropertyDescriptor = options.considerPropertyDescriptor;
106
106
  const includeModuleExports = options.includeCommonJSModuleExports;
107
- const ecmaVersion = context.parserOptions && context.parserOptions.ecmaVersion ? context.parserOptions.ecmaVersion : 5;
107
+ const ecmaVersion = context.languageOptions.ecmaVersion;
108
108
 
109
109
  /**
110
110
  * Check whether node is a certain CallExpression.
@@ -123,6 +123,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({
123
123
  "no-empty-character-class": () => require("./no-empty-character-class"),
124
124
  "no-empty-function": () => require("./no-empty-function"),
125
125
  "no-empty-pattern": () => require("./no-empty-pattern"),
126
+ "no-empty-static-block": () => require("./no-empty-static-block"),
126
127
  "no-eq-null": () => require("./no-eq-null"),
127
128
  "no-eval": () => require("./no-eval"),
128
129
  "no-ex-assign": () => require("./no-ex-assign"),
@@ -167,6 +168,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({
167
168
  "no-nested-ternary": () => require("./no-nested-ternary"),
168
169
  "no-new": () => require("./no-new"),
169
170
  "no-new-func": () => require("./no-new-func"),
171
+ "no-new-native-nonconstructor": () => require("./no-new-native-nonconstructor"),
170
172
  "no-new-object": () => require("./no-new-object"),
171
173
  "no-new-require": () => require("./no-new-require"),
172
174
  "no-new-symbol": () => require("./no-new-symbol"),
@@ -0,0 +1,47 @@
1
+ /**
2
+ * @fileoverview Rule to disallow empty static blocks.
3
+ * @author Sosuke Suzuki
4
+ */
5
+ "use strict";
6
+
7
+ //------------------------------------------------------------------------------
8
+ // Rule Definition
9
+ //------------------------------------------------------------------------------
10
+
11
+ /** @type {import('../shared/types').Rule} */
12
+ module.exports = {
13
+ meta: {
14
+ type: "suggestion",
15
+
16
+ docs: {
17
+ description: "Disallow empty static blocks",
18
+ recommended: false,
19
+ url: "https://eslint.org/docs/rules/no-empty-static-block"
20
+ },
21
+
22
+ schema: [],
23
+
24
+ messages: {
25
+ unexpected: "Unexpected empty static block."
26
+ }
27
+ },
28
+
29
+ create(context) {
30
+ const sourceCode = context.getSourceCode();
31
+
32
+ return {
33
+ StaticBlock(node) {
34
+ if (node.body.length === 0) {
35
+ const closingBrace = sourceCode.getLastToken(node);
36
+
37
+ if (sourceCode.getCommentsBefore(closingBrace).length === 0) {
38
+ context.report({
39
+ node,
40
+ messageId: "unexpected"
41
+ });
42
+ }
43
+ }
44
+ }
45
+ };
46
+ }
47
+ };
@@ -17,6 +17,7 @@ const astUtils = require("./utils/ast-utils");
17
17
  /** @type {import('../shared/types').Rule} */
18
18
  module.exports = {
19
19
  meta: {
20
+ hasSuggestions: true,
20
21
  type: "suggestion",
21
22
 
22
23
  docs: {
@@ -39,7 +40,8 @@ module.exports = {
39
40
  ],
40
41
 
41
42
  messages: {
42
- unexpected: "Empty {{type}} statement."
43
+ unexpected: "Empty {{type}} statement.",
44
+ suggestComment: "Add comment inside empty {{type}} statement."
43
45
  }
44
46
  },
45
47
 
@@ -71,7 +73,22 @@ module.exports = {
71
73
  return;
72
74
  }
73
75
 
74
- context.report({ node, messageId: "unexpected", data: { type: "block" } });
76
+ context.report({
77
+ node,
78
+ messageId: "unexpected",
79
+ data: { type: "block" },
80
+ suggest: [
81
+ {
82
+ messageId: "suggestComment",
83
+ data: { type: "block" },
84
+ fix(fixer) {
85
+ const range = [node.range[0] + 1, node.range[1] - 1];
86
+
87
+ return fixer.replaceTextRange(range, " /* empty */ ");
88
+ }
89
+ }
90
+ ]
91
+ });
75
92
  },
76
93
 
77
94
  SwitchStatement(node) {
@@ -193,15 +193,15 @@ module.exports = {
193
193
  * ecmaVersion doesn't support the `u` flag.
194
194
  */
195
195
  function isValidWithUnicodeFlag(pattern) {
196
- const { ecmaVersion } = context.parserOptions;
196
+ const { ecmaVersion } = context.languageOptions;
197
197
 
198
- // ecmaVersion is unknown or it doesn't support the 'u' flag
199
- if (typeof ecmaVersion !== "number" || ecmaVersion <= 5) {
198
+ // ecmaVersion <= 5 doesn't support the 'u' flag
199
+ if (ecmaVersion <= 5) {
200
200
  return false;
201
201
  }
202
202
 
203
203
  const validator = new RegExpValidator({
204
- ecmaVersion: Math.min(ecmaVersion + 2009, REGEXPP_LATEST_ECMA_VERSION)
204
+ ecmaVersion: Math.min(ecmaVersion, REGEXPP_LATEST_ECMA_VERSION)
205
205
  });
206
206
 
207
207
  try {
@@ -0,0 +1,64 @@
1
+ /**
2
+ * @fileoverview Rule to disallow use of the new operator with global non-constructor functions
3
+ * @author Sosuke Suzuki
4
+ */
5
+
6
+ "use strict";
7
+
8
+ //------------------------------------------------------------------------------
9
+ // Helpers
10
+ //------------------------------------------------------------------------------
11
+
12
+ const nonConstructorGlobalFunctionNames = ["Symbol", "BigInt"];
13
+
14
+ //------------------------------------------------------------------------------
15
+ // Rule Definition
16
+ //------------------------------------------------------------------------------
17
+
18
+ /** @type {import('../shared/types').Rule} */
19
+ module.exports = {
20
+ meta: {
21
+ type: "problem",
22
+
23
+ docs: {
24
+ description: "Disallow `new` operators with global non-constructor functions",
25
+ recommended: false,
26
+ url: "https://eslint.org/docs/rules/no-new-native-nonconstructor"
27
+ },
28
+
29
+ schema: [],
30
+
31
+ messages: {
32
+ noNewNonconstructor: "`{{name}}` cannot be called as a constructor."
33
+ }
34
+ },
35
+
36
+ create(context) {
37
+
38
+ return {
39
+ "Program:exit"() {
40
+ const globalScope = context.getScope();
41
+
42
+ for (const nonConstructorName of nonConstructorGlobalFunctionNames) {
43
+ const variable = globalScope.set.get(nonConstructorName);
44
+
45
+ if (variable && variable.defs.length === 0) {
46
+ variable.references.forEach(ref => {
47
+ const node = ref.identifier;
48
+ const parent = node.parent;
49
+
50
+ if (parent && parent.type === "NewExpression" && parent.callee === node) {
51
+ context.report({
52
+ node,
53
+ messageId: "noNewNonconstructor",
54
+ data: { name: nonConstructorName }
55
+ });
56
+ }
57
+ });
58
+ }
59
+ }
60
+ }
61
+ };
62
+
63
+ }
64
+ };
@@ -248,14 +248,14 @@ module.exports = {
248
248
 
249
249
  /**
250
250
  * Returns a ecmaVersion compatible for regexpp.
251
- * @param {any} ecmaVersion The ecmaVersion to convert.
251
+ * @param {number} ecmaVersion The ecmaVersion to convert.
252
252
  * @returns {import("regexpp/ecma-versions").EcmaVersion} The resulting ecmaVersion compatible for regexpp.
253
253
  */
254
254
  function getRegexppEcmaVersion(ecmaVersion) {
255
- if (typeof ecmaVersion !== "number" || ecmaVersion <= 5) {
255
+ if (ecmaVersion <= 5) {
256
256
  return 5;
257
257
  }
258
- return Math.min(ecmaVersion + 2009, REGEXPP_LATEST_ECMA_VERSION);
258
+ return Math.min(ecmaVersion, REGEXPP_LATEST_ECMA_VERSION);
259
259
  }
260
260
 
261
261
  /**
@@ -320,7 +320,7 @@ module.exports = {
320
320
  flags = getStringValue(node.arguments[1]);
321
321
  }
322
322
 
323
- const regexppEcmaVersion = getRegexppEcmaVersion(context.parserOptions.ecmaVersion);
323
+ const regexppEcmaVersion = getRegexppEcmaVersion(context.languageOptions.ecmaVersion);
324
324
  const RegExpValidatorInstance = new RegExpValidator({ ecmaVersion: regexppEcmaVersion });
325
325
 
326
326
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint",
3
- "version": "8.26.0",
3
+ "version": "8.27.0",
4
4
  "author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
5
5
  "description": "An AST-based pattern checker for JavaScript.",
6
6
  "bin": {