eslint 8.50.0 → 8.52.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/README.md +7 -2
- package/bin/eslint.js +15 -4
- package/conf/rule-type-list.json +25 -33
- package/lib/cli.js +14 -3
- package/lib/config/flat-config-schema.js +56 -39
- package/lib/eslint/eslint-helpers.js +8 -3
- package/lib/eslint/flat-eslint.js +15 -6
- package/lib/linter/apply-disable-directives.js +127 -13
- package/lib/linter/code-path-analysis/code-path-state.js +1108 -243
- package/lib/linter/code-path-analysis/code-path.js +127 -33
- package/lib/linter/code-path-analysis/fork-context.js +173 -72
- package/lib/linter/config-comment-parser.js +1 -1
- package/lib/options.js +15 -2
- package/lib/rules/logical-assignment-operators.js +31 -3
- package/lib/rules/no-object-constructor.js +103 -5
- package/package.json +4 -3
package/README.md
CHANGED
@@ -254,6 +254,11 @@ Francesco Trotta
|
|
254
254
|
<img src="https://github.com/ota-meshi.png?s=75" width="75" height="75"><br />
|
255
255
|
Yosuke Ota
|
256
256
|
</a>
|
257
|
+
</td><td align="center" valign="top" width="11%">
|
258
|
+
<a href="https://github.com/Tanujkanti4441">
|
259
|
+
<img src="https://github.com/Tanujkanti4441.png?s=75" width="75" height="75"><br />
|
260
|
+
Tanuj Kanti
|
261
|
+
</a>
|
257
262
|
</td></tr></tbody></table>
|
258
263
|
|
259
264
|
### Website Team
|
@@ -288,8 +293,8 @@ The following companies, organizations, and individuals support ESLint's ongoing
|
|
288
293
|
<h3>Platinum Sponsors</h3>
|
289
294
|
<p><a href="#"><img src="https://images.opencollective.com/2021-frameworks-fund/logo.png" alt="Chrome Frameworks Fund" height="undefined"></a> <a href="https://automattic.com"><img src="https://images.opencollective.com/automattic/d0ef3e1/logo.png" alt="Automattic" height="undefined"></a></p><h3>Gold Sponsors</h3>
|
290
295
|
<p><a href="https://engineering.salesforce.com"><img src="https://images.opencollective.com/salesforce/ca8f997/logo.png" alt="Salesforce" height="96"></a> <a href="https://www.airbnb.com/"><img src="https://images.opencollective.com/airbnb/d327d66/logo.png" alt="Airbnb" height="96"></a></p><h3>Silver Sponsors</h3>
|
291
|
-
<p><a href="https://
|
292
|
-
<p><a href="https://themeisle.com"><img src="https://images.opencollective.com/themeisle/d5592fe/logo.png" alt="ThemeIsle" height="32"></a> <a href="https://
|
296
|
+
<p><a href="https://liftoff.io/"><img src="https://images.opencollective.com/liftoff/5c4fa84/logo.png" alt="Liftoff" height="64"></a> <a href="https://americanexpress.io"><img src="https://avatars.githubusercontent.com/u/3853301?v=4" alt="American Express" height="64"></a></p><h3>Bronze Sponsors</h3>
|
297
|
+
<p><a href="https://themeisle.com"><img src="https://images.opencollective.com/themeisle/d5592fe/logo.png" alt="ThemeIsle" height="32"></a> <a href="https://www.crosswordsolver.org/anagram-solver/"><img src="https://images.opencollective.com/anagram-solver/2666271/logo.png" alt="Anagram Solver" height="32"></a> <a href="https://icons8.com/"><img src="https://images.opencollective.com/icons8/7fa1641/logo.png" alt="Icons8" height="32"></a> <a href="https://discord.com"><img src="https://images.opencollective.com/discordapp/f9645d9/logo.png" alt="Discord" height="32"></a> <a href="https://transloadit.com/"><img src="https://avatars.githubusercontent.com/u/125754?v=4" alt="Transloadit" height="32"></a> <a href="https://www.ignitionapp.com"><img src="https://avatars.githubusercontent.com/u/5753491?v=4" alt="Ignition" height="32"></a> <a href="https://herocoders.com"><img src="https://avatars.githubusercontent.com/u/37549774?v=4" alt="HeroCoders" height="32"></a> <a href="https://quickbookstoolhub.com"><img src="https://avatars.githubusercontent.com/u/95090305?u=e5bc398ef775c9ed19f955c675cdc1fb6abf01df&v=4" alt="QuickBooks Tool hub" height="32"></a></p>
|
293
298
|
<!--sponsorsend-->
|
294
299
|
|
295
300
|
## Technology Sponsors
|
package/bin/eslint.js
CHANGED
@@ -92,6 +92,14 @@ function getErrorMessage(error) {
|
|
92
92
|
return util.format("%o", error);
|
93
93
|
}
|
94
94
|
|
95
|
+
/**
|
96
|
+
* Tracks error messages that are shown to the user so we only ever show the
|
97
|
+
* same message once.
|
98
|
+
* @type {Set<string>}
|
99
|
+
*/
|
100
|
+
|
101
|
+
const displayedErrors = new Set();
|
102
|
+
|
95
103
|
/**
|
96
104
|
* Catch and report unexpected error.
|
97
105
|
* @param {any} error The thrown error object.
|
@@ -101,14 +109,17 @@ function onFatalError(error) {
|
|
101
109
|
process.exitCode = 2;
|
102
110
|
|
103
111
|
const { version } = require("../package.json");
|
104
|
-
const message =
|
105
|
-
|
106
|
-
console.error(`
|
112
|
+
const message = `
|
107
113
|
Oops! Something went wrong! :(
|
108
114
|
|
109
115
|
ESLint: ${version}
|
110
116
|
|
111
|
-
${
|
117
|
+
${getErrorMessage(error)}`;
|
118
|
+
|
119
|
+
if (!displayedErrors.has(message)) {
|
120
|
+
console.error(message);
|
121
|
+
displayedErrors.add(message);
|
122
|
+
}
|
112
123
|
}
|
113
124
|
|
114
125
|
//------------------------------------------------------------------------------
|
package/conf/rule-type-list.json
CHANGED
@@ -1,36 +1,28 @@
|
|
1
1
|
{
|
2
|
-
"types":
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
],
|
7
|
-
"deprecated": {
|
8
|
-
"name": "Deprecated",
|
9
|
-
"description": "These rules have been deprecated in accordance with the <a href=\"{{ '/use/rule-deprecation' | url }}\">deprecation policy</a>, and replaced by newer rules:",
|
10
|
-
"rules": []
|
2
|
+
"types": {
|
3
|
+
"problem": [],
|
4
|
+
"suggestion": [],
|
5
|
+
"layout": []
|
11
6
|
},
|
12
|
-
"
|
13
|
-
|
14
|
-
"
|
15
|
-
"
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
{ "removed": "spaced-line-comment", "replacedBy": ["spaced-comment"] }
|
34
|
-
]
|
35
|
-
}
|
7
|
+
"deprecated": [],
|
8
|
+
"removed": [
|
9
|
+
{ "removed": "generator-star", "replacedBy": ["generator-star-spacing"] },
|
10
|
+
{ "removed": "global-strict", "replacedBy": ["strict"] },
|
11
|
+
{ "removed": "no-arrow-condition", "replacedBy": ["no-confusing-arrow", "no-constant-condition"] },
|
12
|
+
{ "removed": "no-comma-dangle", "replacedBy": ["comma-dangle"] },
|
13
|
+
{ "removed": "no-empty-class", "replacedBy": ["no-empty-character-class"] },
|
14
|
+
{ "removed": "no-empty-label", "replacedBy": ["no-labels"] },
|
15
|
+
{ "removed": "no-extra-strict", "replacedBy": ["strict"] },
|
16
|
+
{ "removed": "no-reserved-keys", "replacedBy": ["quote-props"] },
|
17
|
+
{ "removed": "no-space-before-semi", "replacedBy": ["semi-spacing"] },
|
18
|
+
{ "removed": "no-wrap-func", "replacedBy": ["no-extra-parens"] },
|
19
|
+
{ "removed": "space-after-function-name", "replacedBy": ["space-before-function-paren"] },
|
20
|
+
{ "removed": "space-after-keywords", "replacedBy": ["keyword-spacing"] },
|
21
|
+
{ "removed": "space-before-function-parentheses", "replacedBy": ["space-before-function-paren"] },
|
22
|
+
{ "removed": "space-before-keywords", "replacedBy": ["keyword-spacing"] },
|
23
|
+
{ "removed": "space-in-brackets", "replacedBy": ["object-curly-spacing", "array-bracket-spacing"] },
|
24
|
+
{ "removed": "space-return-throw-case", "replacedBy": ["keyword-spacing"] },
|
25
|
+
{ "removed": "space-unary-word-ops", "replacedBy": ["space-unary-ops"] },
|
26
|
+
{ "removed": "spaced-line-comment", "replacedBy": ["spaced-comment"] }
|
27
|
+
]
|
36
28
|
}
|
package/lib/cli.js
CHANGED
@@ -91,7 +91,8 @@ async function translateOptions({
|
|
91
91
|
reportUnusedDisableDirectives,
|
92
92
|
resolvePluginsRelativeTo,
|
93
93
|
rule,
|
94
|
-
rulesdir
|
94
|
+
rulesdir,
|
95
|
+
warnIgnored
|
95
96
|
}, configType) {
|
96
97
|
|
97
98
|
let overrideConfig, overrideConfigFile;
|
@@ -182,6 +183,7 @@ async function translateOptions({
|
|
182
183
|
|
183
184
|
if (configType === "flat") {
|
184
185
|
options.ignorePatterns = ignorePattern;
|
186
|
+
options.warnIgnored = warnIgnored;
|
185
187
|
} else {
|
186
188
|
options.resolvePluginsRelativeTo = resolvePluginsRelativeTo;
|
187
189
|
options.rulePaths = rulesdir;
|
@@ -316,7 +318,14 @@ const cli = {
|
|
316
318
|
options = CLIOptions.parse(args);
|
317
319
|
} catch (error) {
|
318
320
|
debug("Error parsing CLI options:", error.message);
|
319
|
-
|
321
|
+
|
322
|
+
let errorMessage = error.message;
|
323
|
+
|
324
|
+
if (usingFlatConfig) {
|
325
|
+
errorMessage += "\nYou're using eslint.config.js, some command line flags are no longer available. Please see https://eslint.org/docs/latest/use/command-line-interface for details.";
|
326
|
+
}
|
327
|
+
|
328
|
+
log.error(errorMessage);
|
320
329
|
return 2;
|
321
330
|
}
|
322
331
|
|
@@ -385,7 +394,9 @@ const cli = {
|
|
385
394
|
if (useStdin) {
|
386
395
|
results = await engine.lintText(text, {
|
387
396
|
filePath: options.stdinFilename,
|
388
|
-
|
397
|
+
|
398
|
+
// flatConfig respects CLI flag and constructor warnIgnored, eslintrc forces true for backwards compatibility
|
399
|
+
warnIgnored: usingFlatConfig ? void 0 : true
|
389
400
|
});
|
390
401
|
} else {
|
391
402
|
results = await engine.lintFiles(files);
|
@@ -5,6 +5,16 @@
|
|
5
5
|
|
6
6
|
"use strict";
|
7
7
|
|
8
|
+
//-----------------------------------------------------------------------------
|
9
|
+
// Requirements
|
10
|
+
//-----------------------------------------------------------------------------
|
11
|
+
|
12
|
+
/*
|
13
|
+
* Note: This can be removed in ESLint v9 because structuredClone is available globally
|
14
|
+
* starting in Node.js v17.
|
15
|
+
*/
|
16
|
+
const structuredClone = require("@ungap/structured-clone").default;
|
17
|
+
|
8
18
|
//-----------------------------------------------------------------------------
|
9
19
|
// Type Definitions
|
10
20
|
//-----------------------------------------------------------------------------
|
@@ -119,7 +129,7 @@ function normalizeRuleOptions(ruleOptions) {
|
|
119
129
|
: [ruleOptions];
|
120
130
|
|
121
131
|
finalOptions[0] = ruleSeverities.get(finalOptions[0]);
|
122
|
-
return finalOptions;
|
132
|
+
return structuredClone(finalOptions);
|
123
133
|
}
|
124
134
|
|
125
135
|
//-----------------------------------------------------------------------------
|
@@ -179,9 +189,7 @@ class InvalidRuleSeverityError extends Error {
|
|
179
189
|
* @throws {InvalidRuleSeverityError} If the value isn't a valid rule severity.
|
180
190
|
*/
|
181
191
|
function assertIsRuleSeverity(ruleId, value) {
|
182
|
-
const severity =
|
183
|
-
? ruleSeverities.get(value.toLowerCase())
|
184
|
-
: ruleSeverities.get(value);
|
192
|
+
const severity = ruleSeverities.get(value);
|
185
193
|
|
186
194
|
if (typeof severity === "undefined") {
|
187
195
|
throw new InvalidRuleSeverityError(ruleId, value);
|
@@ -380,48 +388,57 @@ const rulesSchema = {
|
|
380
388
|
...second
|
381
389
|
};
|
382
390
|
|
383
|
-
for (const ruleId of Object.keys(result)) {
|
384
|
-
|
385
|
-
// avoid hairy edge case
|
386
|
-
if (ruleId === "__proto__") {
|
387
|
-
|
388
|
-
/* eslint-disable-next-line no-proto -- Though deprecated, may still be present */
|
389
|
-
delete result.__proto__;
|
390
|
-
continue;
|
391
|
-
}
|
392
|
-
|
393
|
-
result[ruleId] = normalizeRuleOptions(result[ruleId]);
|
394
|
-
|
395
|
-
/*
|
396
|
-
* If either rule config is missing, then the correct
|
397
|
-
* config is already present and we just need to normalize
|
398
|
-
* the severity.
|
399
|
-
*/
|
400
|
-
if (!(ruleId in first) || !(ruleId in second)) {
|
401
|
-
continue;
|
402
|
-
}
|
403
391
|
|
404
|
-
|
405
|
-
const secondRuleOptions = normalizeRuleOptions(second[ruleId]);
|
392
|
+
for (const ruleId of Object.keys(result)) {
|
406
393
|
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
394
|
+
try {
|
395
|
+
|
396
|
+
// avoid hairy edge case
|
397
|
+
if (ruleId === "__proto__") {
|
398
|
+
|
399
|
+
/* eslint-disable-next-line no-proto -- Though deprecated, may still be present */
|
400
|
+
delete result.__proto__;
|
401
|
+
continue;
|
402
|
+
}
|
403
|
+
|
404
|
+
result[ruleId] = normalizeRuleOptions(result[ruleId]);
|
405
|
+
|
406
|
+
/*
|
407
|
+
* If either rule config is missing, then the correct
|
408
|
+
* config is already present and we just need to normalize
|
409
|
+
* the severity.
|
410
|
+
*/
|
411
|
+
if (!(ruleId in first) || !(ruleId in second)) {
|
412
|
+
continue;
|
413
|
+
}
|
414
|
+
|
415
|
+
const firstRuleOptions = normalizeRuleOptions(first[ruleId]);
|
416
|
+
const secondRuleOptions = normalizeRuleOptions(second[ruleId]);
|
417
|
+
|
418
|
+
/*
|
419
|
+
* If the second rule config only has a severity (length of 1),
|
420
|
+
* then use that severity and keep the rest of the options from
|
421
|
+
* the first rule config.
|
422
|
+
*/
|
423
|
+
if (secondRuleOptions.length === 1) {
|
424
|
+
result[ruleId] = [secondRuleOptions[0], ...firstRuleOptions.slice(1)];
|
425
|
+
continue;
|
426
|
+
}
|
427
|
+
|
428
|
+
/*
|
429
|
+
* In any other situation, then the second rule config takes
|
430
|
+
* precedence. That means the value at `result[ruleId]` is
|
431
|
+
* already correct and no further work is necessary.
|
432
|
+
*/
|
433
|
+
} catch (ex) {
|
434
|
+
throw new Error(`Key "${ruleId}": ${ex.message}`, { cause: ex });
|
415
435
|
}
|
416
436
|
|
417
|
-
/*
|
418
|
-
* In any other situation, then the second rule config takes
|
419
|
-
* precedence. That means the value at `result[ruleId]` is
|
420
|
-
* already correct and no further work is necessary.
|
421
|
-
*/
|
422
437
|
}
|
423
438
|
|
424
439
|
return result;
|
440
|
+
|
441
|
+
|
425
442
|
},
|
426
443
|
|
427
444
|
validate(value) {
|
@@ -594,9 +594,9 @@ function createIgnoreResult(filePath, baseDir) {
|
|
594
594
|
const isInNodeModules = baseDir && path.dirname(path.relative(baseDir, filePath)).split(path.sep).includes("node_modules");
|
595
595
|
|
596
596
|
if (isInNodeModules) {
|
597
|
-
message = "File ignored by default because it is located under the node_modules directory. Use ignore pattern \"!**/node_modules/\" to
|
597
|
+
message = "File ignored by default because it is located under the node_modules directory. Use ignore pattern \"!**/node_modules/\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning.";
|
598
598
|
} else {
|
599
|
-
message = "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to
|
599
|
+
message = "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning.";
|
600
600
|
}
|
601
601
|
|
602
602
|
return {
|
@@ -676,6 +676,7 @@ function processOptions({
|
|
676
676
|
overrideConfigFile = null,
|
677
677
|
plugins = {},
|
678
678
|
reportUnusedDisableDirectives = null, // ← should be null by default because if it's a string then it overrides the 'reportUnusedDisableDirectives' setting in config files. And we cannot use `overrideConfig.reportUnusedDisableDirectives` instead because we cannot configure the `error` severity with that.
|
679
|
+
warnIgnored = true,
|
679
680
|
...unknownOptions
|
680
681
|
}) {
|
681
682
|
const errors = [];
|
@@ -781,6 +782,9 @@ function processOptions({
|
|
781
782
|
) {
|
782
783
|
errors.push("'reportUnusedDisableDirectives' must be any of \"error\", \"warn\", \"off\", and null.");
|
783
784
|
}
|
785
|
+
if (typeof warnIgnored !== "boolean") {
|
786
|
+
errors.push("'warnIgnored' must be a boolean.");
|
787
|
+
}
|
784
788
|
if (errors.length > 0) {
|
785
789
|
throw new ESLintInvalidOptionsError(errors);
|
786
790
|
}
|
@@ -802,7 +806,8 @@ function processOptions({
|
|
802
806
|
globInputPaths,
|
803
807
|
ignore,
|
804
808
|
ignorePatterns,
|
805
|
-
reportUnusedDisableDirectives
|
809
|
+
reportUnusedDisableDirectives,
|
810
|
+
warnIgnored
|
806
811
|
};
|
807
812
|
}
|
808
813
|
|
@@ -84,6 +84,7 @@ const LintResultCache = require("../cli-engine/lint-result-cache");
|
|
84
84
|
* when a string.
|
85
85
|
* @property {Record<string,Plugin>} [plugins] An array of plugin implementations.
|
86
86
|
* @property {"error" | "warn" | "off"} [reportUnusedDisableDirectives] the severity to report unused eslint-disable directives.
|
87
|
+
* @property {boolean} warnIgnored Show warnings when the file list includes ignored files
|
87
88
|
*/
|
88
89
|
|
89
90
|
//------------------------------------------------------------------------------
|
@@ -749,7 +750,8 @@ class FlatESLint {
|
|
749
750
|
fixTypes,
|
750
751
|
reportUnusedDisableDirectives,
|
751
752
|
globInputPaths,
|
752
|
-
errorOnUnmatchedPattern
|
753
|
+
errorOnUnmatchedPattern,
|
754
|
+
warnIgnored
|
753
755
|
} = eslintOptions;
|
754
756
|
const startTime = Date.now();
|
755
757
|
const fixTypesSet = fixTypes ? new Set(fixTypes) : null;
|
@@ -795,7 +797,11 @@ class FlatESLint {
|
|
795
797
|
* pattern, then notify the user.
|
796
798
|
*/
|
797
799
|
if (ignored) {
|
798
|
-
|
800
|
+
if (warnIgnored) {
|
801
|
+
return createIgnoreResult(filePath, cwd);
|
802
|
+
}
|
803
|
+
|
804
|
+
return void 0;
|
799
805
|
}
|
800
806
|
|
801
807
|
const config = configs.getConfig(filePath);
|
@@ -908,7 +914,7 @@ class FlatESLint {
|
|
908
914
|
|
909
915
|
const {
|
910
916
|
filePath,
|
911
|
-
warnIgnored
|
917
|
+
warnIgnored,
|
912
918
|
...unknownOptions
|
913
919
|
} = options || {};
|
914
920
|
|
@@ -922,7 +928,7 @@ class FlatESLint {
|
|
922
928
|
throw new Error("'options.filePath' must be a non-empty string or undefined");
|
923
929
|
}
|
924
930
|
|
925
|
-
if (typeof warnIgnored !== "boolean") {
|
931
|
+
if (typeof warnIgnored !== "boolean" && typeof warnIgnored !== "undefined") {
|
926
932
|
throw new Error("'options.warnIgnored' must be a boolean or undefined");
|
927
933
|
}
|
928
934
|
|
@@ -937,7 +943,8 @@ class FlatESLint {
|
|
937
943
|
allowInlineConfig,
|
938
944
|
cwd,
|
939
945
|
fix,
|
940
|
-
reportUnusedDisableDirectives
|
946
|
+
reportUnusedDisableDirectives,
|
947
|
+
warnIgnored: constructorWarnIgnored
|
941
948
|
} = eslintOptions;
|
942
949
|
const results = [];
|
943
950
|
const startTime = Date.now();
|
@@ -945,7 +952,9 @@ class FlatESLint {
|
|
945
952
|
|
946
953
|
// Clear the last used config arrays.
|
947
954
|
if (resolvedFilename && await this.isPathIgnored(resolvedFilename)) {
|
948
|
-
|
955
|
+
const shouldWarnIgnored = typeof warnIgnored === "boolean" ? warnIgnored : constructorWarnIgnored;
|
956
|
+
|
957
|
+
if (shouldWarnIgnored) {
|
949
958
|
results.push(createIgnoreResult(resolvedFilename, cwd));
|
950
959
|
}
|
951
960
|
} else {
|
@@ -30,7 +30,7 @@ function compareLocations(itemA, itemB) {
|
|
30
30
|
|
31
31
|
/**
|
32
32
|
* Groups a set of directives into sub-arrays by their parent comment.
|
33
|
-
* @param {Directive
|
33
|
+
* @param {Iterable<Directive>} directives Unused directives to be removed.
|
34
34
|
* @returns {Directive[][]} Directives grouped by their parent comment.
|
35
35
|
*/
|
36
36
|
function groupByParentComment(directives) {
|
@@ -87,7 +87,7 @@ function createIndividualDirectivesRemoval(directives, commentToken) {
|
|
87
87
|
return directives.map(directive => {
|
88
88
|
const { ruleId } = directive;
|
89
89
|
|
90
|
-
const regex = new RegExp(String.raw`(?:^|\s*,\s*)${escapeRegExp(ruleId)}(?:\s*,\s*|$)`, "u");
|
90
|
+
const regex = new RegExp(String.raw`(?:^|\s*,\s*)(?<quote>['"]?)${escapeRegExp(ruleId)}\k<quote>(?:\s*,\s*|$)`, "u");
|
91
91
|
const match = regex.exec(listText);
|
92
92
|
const matchedText = match[0];
|
93
93
|
const matchStartOffset = listStartOffset + match.index;
|
@@ -177,10 +177,10 @@ function createCommentRemoval(directives, commentToken) {
|
|
177
177
|
|
178
178
|
/**
|
179
179
|
* Parses details from directives to create output Problems.
|
180
|
-
* @param {Directive
|
180
|
+
* @param {Iterable<Directive>} allDirectives Unused directives to be removed.
|
181
181
|
* @returns {{ description, fix, unprocessedDirective }[]} Details for later creation of output Problems.
|
182
182
|
*/
|
183
|
-
function
|
183
|
+
function processUnusedDirectives(allDirectives) {
|
184
184
|
const directiveGroups = groupByParentComment(allDirectives);
|
185
185
|
|
186
186
|
return directiveGroups.flatMap(
|
@@ -199,6 +199,95 @@ function processUnusedDisableDirectives(allDirectives) {
|
|
199
199
|
);
|
200
200
|
}
|
201
201
|
|
202
|
+
/**
|
203
|
+
* Collect eslint-enable comments that are removing suppressions by eslint-disable comments.
|
204
|
+
* @param {Directive[]} directives The directives to check.
|
205
|
+
* @returns {Set<Directive>} The used eslint-enable comments
|
206
|
+
*/
|
207
|
+
function collectUsedEnableDirectives(directives) {
|
208
|
+
|
209
|
+
/**
|
210
|
+
* A Map of `eslint-enable` keyed by ruleIds that may be marked as used.
|
211
|
+
* If `eslint-enable` does not have a ruleId, the key will be `null`.
|
212
|
+
* @type {Map<string|null, Directive>}
|
213
|
+
*/
|
214
|
+
const enabledRules = new Map();
|
215
|
+
|
216
|
+
/**
|
217
|
+
* A Set of `eslint-enable` marked as used.
|
218
|
+
* It is also the return value of `collectUsedEnableDirectives` function.
|
219
|
+
* @type {Set<Directive>}
|
220
|
+
*/
|
221
|
+
const usedEnableDirectives = new Set();
|
222
|
+
|
223
|
+
/*
|
224
|
+
* Checks the directives backwards to see if the encountered `eslint-enable` is used by the previous `eslint-disable`,
|
225
|
+
* and if so, stores the `eslint-enable` in `usedEnableDirectives`.
|
226
|
+
*/
|
227
|
+
for (let index = directives.length - 1; index >= 0; index--) {
|
228
|
+
const directive = directives[index];
|
229
|
+
|
230
|
+
if (directive.type === "disable") {
|
231
|
+
if (enabledRules.size === 0) {
|
232
|
+
continue;
|
233
|
+
}
|
234
|
+
if (directive.ruleId === null) {
|
235
|
+
|
236
|
+
// If encounter `eslint-disable` without ruleId,
|
237
|
+
// mark all `eslint-enable` currently held in enabledRules as used.
|
238
|
+
// e.g.
|
239
|
+
// /* eslint-disable */ <- current directive
|
240
|
+
// /* eslint-enable rule-id1 */ <- used
|
241
|
+
// /* eslint-enable rule-id2 */ <- used
|
242
|
+
// /* eslint-enable */ <- used
|
243
|
+
for (const enableDirective of enabledRules.values()) {
|
244
|
+
usedEnableDirectives.add(enableDirective);
|
245
|
+
}
|
246
|
+
enabledRules.clear();
|
247
|
+
} else {
|
248
|
+
const enableDirective = enabledRules.get(directive.ruleId);
|
249
|
+
|
250
|
+
if (enableDirective) {
|
251
|
+
|
252
|
+
// If encounter `eslint-disable` with ruleId, and there is an `eslint-enable` with the same ruleId in enabledRules,
|
253
|
+
// mark `eslint-enable` with ruleId as used.
|
254
|
+
// e.g.
|
255
|
+
// /* eslint-disable rule-id */ <- current directive
|
256
|
+
// /* eslint-enable rule-id */ <- used
|
257
|
+
usedEnableDirectives.add(enableDirective);
|
258
|
+
} else {
|
259
|
+
const enabledDirectiveWithoutRuleId = enabledRules.get(null);
|
260
|
+
|
261
|
+
if (enabledDirectiveWithoutRuleId) {
|
262
|
+
|
263
|
+
// If encounter `eslint-disable` with ruleId, and there is no `eslint-enable` with the same ruleId in enabledRules,
|
264
|
+
// mark `eslint-enable` without ruleId as used.
|
265
|
+
// e.g.
|
266
|
+
// /* eslint-disable rule-id */ <- current directive
|
267
|
+
// /* eslint-enable */ <- used
|
268
|
+
usedEnableDirectives.add(enabledDirectiveWithoutRuleId);
|
269
|
+
}
|
270
|
+
}
|
271
|
+
}
|
272
|
+
} else if (directive.type === "enable") {
|
273
|
+
if (directive.ruleId === null) {
|
274
|
+
|
275
|
+
// If encounter `eslint-enable` without ruleId, the `eslint-enable` that follows it are unused.
|
276
|
+
// So clear enabledRules.
|
277
|
+
// e.g.
|
278
|
+
// /* eslint-enable */ <- current directive
|
279
|
+
// /* eslint-enable rule-id *// <- unused
|
280
|
+
// /* eslint-enable */ <- unused
|
281
|
+
enabledRules.clear();
|
282
|
+
enabledRules.set(null, directive);
|
283
|
+
} else {
|
284
|
+
enabledRules.set(directive.ruleId, directive);
|
285
|
+
}
|
286
|
+
}
|
287
|
+
}
|
288
|
+
return usedEnableDirectives;
|
289
|
+
}
|
290
|
+
|
202
291
|
/**
|
203
292
|
* This is the same as the exported function, except that it
|
204
293
|
* doesn't handle disable-line and disable-next-line directives, and it always reports unused
|
@@ -206,7 +295,7 @@ function processUnusedDisableDirectives(allDirectives) {
|
|
206
295
|
* @param {Object} options options for applying directives. This is the same as the options
|
207
296
|
* for the exported function, except that `reportUnusedDisableDirectives` is not supported
|
208
297
|
* (this function always reports unused disable directives).
|
209
|
-
* @returns {{problems: LintMessage[],
|
298
|
+
* @returns {{problems: LintMessage[], unusedDirectives: LintMessage[]}} An object with a list
|
210
299
|
* of problems (including suppressed ones) and unused eslint-disable directives
|
211
300
|
*/
|
212
301
|
function applyDirectives(options) {
|
@@ -258,17 +347,42 @@ function applyDirectives(options) {
|
|
258
347
|
const unusedDisableDirectivesToReport = options.directives
|
259
348
|
.filter(directive => directive.type === "disable" && !usedDisableDirectives.has(directive));
|
260
349
|
|
261
|
-
const processed = processUnusedDisableDirectives(unusedDisableDirectivesToReport);
|
262
350
|
|
263
|
-
const
|
351
|
+
const unusedEnableDirectivesToReport = new Set(
|
352
|
+
options.directives.filter(directive => directive.unprocessedDirective.type === "enable")
|
353
|
+
);
|
354
|
+
|
355
|
+
/*
|
356
|
+
* If directives has the eslint-enable directive,
|
357
|
+
* check whether the eslint-enable comment is used.
|
358
|
+
*/
|
359
|
+
if (unusedEnableDirectivesToReport.size > 0) {
|
360
|
+
for (const directive of collectUsedEnableDirectives(options.directives)) {
|
361
|
+
unusedEnableDirectivesToReport.delete(directive);
|
362
|
+
}
|
363
|
+
}
|
364
|
+
|
365
|
+
const processed = processUnusedDirectives(unusedDisableDirectivesToReport)
|
366
|
+
.concat(processUnusedDirectives(unusedEnableDirectivesToReport));
|
367
|
+
|
368
|
+
const unusedDirectives = processed
|
264
369
|
.map(({ description, fix, unprocessedDirective }) => {
|
265
370
|
const { parentComment, type, line, column } = unprocessedDirective;
|
266
371
|
|
372
|
+
let message;
|
373
|
+
|
374
|
+
if (type === "enable") {
|
375
|
+
message = description
|
376
|
+
? `Unused eslint-enable directive (no matching eslint-disable directives were found for ${description}).`
|
377
|
+
: "Unused eslint-enable directive (no matching eslint-disable directives were found).";
|
378
|
+
} else {
|
379
|
+
message = description
|
380
|
+
? `Unused eslint-disable directive (no problems were reported from ${description}).`
|
381
|
+
: "Unused eslint-disable directive (no problems were reported).";
|
382
|
+
}
|
267
383
|
return {
|
268
384
|
ruleId: null,
|
269
|
-
message
|
270
|
-
? `Unused eslint-disable directive (no problems were reported from ${description}).`
|
271
|
-
: "Unused eslint-disable directive (no problems were reported).",
|
385
|
+
message,
|
272
386
|
line: type === "disable-next-line" ? parentComment.commentToken.loc.start.line : line,
|
273
387
|
column: type === "disable-next-line" ? parentComment.commentToken.loc.start.column + 1 : column,
|
274
388
|
severity: options.reportUnusedDisableDirectives === "warn" ? 1 : 2,
|
@@ -277,7 +391,7 @@ function applyDirectives(options) {
|
|
277
391
|
};
|
278
392
|
});
|
279
393
|
|
280
|
-
return { problems,
|
394
|
+
return { problems, unusedDirectives };
|
281
395
|
}
|
282
396
|
|
283
397
|
/**
|
@@ -344,8 +458,8 @@ module.exports = ({ directives, disableFixes, problems, reportUnusedDisableDirec
|
|
344
458
|
|
345
459
|
return reportUnusedDisableDirectives !== "off"
|
346
460
|
? lineDirectivesResult.problems
|
347
|
-
.concat(blockDirectivesResult.
|
348
|
-
.concat(lineDirectivesResult.
|
461
|
+
.concat(blockDirectivesResult.unusedDirectives)
|
462
|
+
.concat(lineDirectivesResult.unusedDirectives)
|
349
463
|
.sort(compareLocations)
|
350
464
|
: lineDirectivesResult.problems;
|
351
465
|
};
|