eslint 9.0.0-rc.0 → 9.0.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 +6 -10
- package/bin/eslint.js +12 -0
- package/lib/cli.js +2 -0
- package/lib/eslint/eslint-helpers.js +5 -0
- package/lib/eslint/eslint.js +16 -1
- package/lib/linter/linter.js +154 -9
- package/lib/linter/timing.js +16 -8
- package/lib/options.js +25 -1
- package/lib/rule-tester/rule-tester.js +18 -2
- package/lib/rules/camelcase.js +3 -5
- package/lib/rules/constructor-super.js +62 -93
- package/lib/rules/no-lone-blocks.js +1 -1
- package/lib/rules/no-unused-vars.js +179 -29
- package/lib/rules/use-isnan.js +2 -2
- package/lib/shared/stats.js +30 -0
- package/lib/shared/types.js +34 -0
- package/lib/source-code/token-store/backward-token-cursor.js +3 -3
- package/lib/source-code/token-store/cursors.js +4 -2
- package/lib/source-code/token-store/forward-token-comment-cursor.js +3 -3
- package/lib/source-code/token-store/forward-token-cursor.js +3 -3
- package/messages/plugin-conflict.js +1 -1
- package/messages/plugin-invalid.js +1 -1
- package/messages/plugin-missing.js +1 -1
- package/package.json +6 -5
package/README.md
CHANGED
@@ -59,15 +59,11 @@ After that, you can run ESLint on any file or directory like this:
|
|
59
59
|
|
60
60
|
## Configuration
|
61
61
|
|
62
|
-
After running `npm init @eslint/config`, you'll have an
|
63
|
-
|
64
|
-
```
|
65
|
-
|
66
|
-
|
67
|
-
"semi": ["error", "always"],
|
68
|
-
"quotes": ["error", "double"]
|
69
|
-
}
|
70
|
-
}
|
62
|
+
After running `npm init @eslint/config`, you'll have an `eslint.config.js` or `eslint.config.mjs` file in your directory. In it, you'll see some rules configured like this:
|
63
|
+
|
64
|
+
```js
|
65
|
+
import pluginJs from "@eslint/js";
|
66
|
+
export default [ pluginJs.configs.recommended, ];
|
71
67
|
```
|
72
68
|
|
73
69
|
The names `"semi"` and `"quotes"` are the names of [rules](https://eslint.org/docs/rules) in ESLint. The first value is the error level of the rule and can be one of these values:
|
@@ -306,7 +302,7 @@ The following companies, organizations, and individuals support ESLint's ongoing
|
|
306
302
|
<p><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>
|
307
303
|
<p><a href="https://bitwarden.com"><img src="https://avatars.githubusercontent.com/u/15990069?v=4" alt="Bitwarden" height="96"></a> <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>
|
308
304
|
<p><a href="https://www.jetbrains.com/"><img src="https://images.opencollective.com/jetbrains/eb04ddc/logo.png" alt="JetBrains" height="64"></a> <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> <a href="https://www.workleap.com"><img src="https://avatars.githubusercontent.com/u/53535748?u=d1e55d7661d724bf2281c1bfd33cb8f99fe2465f&v=4" alt="Workleap" height="64"></a></p><h3>Bronze Sponsors</h3>
|
309
|
-
<p><a href="https://www.notion.so"><img src="https://images.opencollective.com/notion/bf3b117/logo.png" alt="notion" height="32"></a> <a href="https://
|
305
|
+
<p><a href="https://www.notion.so"><img src="https://images.opencollective.com/notion/bf3b117/logo.png" alt="notion" 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://nx.dev"><img src="https://avatars.githubusercontent.com/u/23692104?v=4" alt="Nx" 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://usenextbase.com"><img src="https://avatars.githubusercontent.com/u/145838380?v=4" alt="Nextbase Starter Kit" height="32"></a></p>
|
310
306
|
<!--sponsorsend-->
|
311
307
|
|
312
308
|
## Technology Sponsors
|
package/bin/eslint.js
CHANGED
@@ -148,6 +148,18 @@ ${getErrorMessage(error)}`;
|
|
148
148
|
return;
|
149
149
|
}
|
150
150
|
|
151
|
+
// Call the config inspector if `--inspect-config` is present.
|
152
|
+
if (process.argv.includes("--inspect-config")) {
|
153
|
+
|
154
|
+
console.warn("You can also run this command directly using 'npx @eslint/config-inspector' in the same directory as your configuration file.");
|
155
|
+
|
156
|
+
const spawn = require("cross-spawn");
|
157
|
+
|
158
|
+
spawn.sync("npx", ["@eslint/config-inspector"], { encoding: "utf8", stdio: "inherit" });
|
159
|
+
|
160
|
+
return;
|
161
|
+
}
|
162
|
+
|
151
163
|
// Otherwise, call the CLI.
|
152
164
|
const cli = require("../lib/cli");
|
153
165
|
const exitCode = await cli.execute(
|
package/lib/cli.js
CHANGED
@@ -131,6 +131,7 @@ async function translateOptions({
|
|
131
131
|
resolvePluginsRelativeTo,
|
132
132
|
rule,
|
133
133
|
rulesdir,
|
134
|
+
stats,
|
134
135
|
warnIgnored,
|
135
136
|
passOnNoPatterns,
|
136
137
|
maxWarnings
|
@@ -222,6 +223,7 @@ async function translateOptions({
|
|
222
223
|
|
223
224
|
if (configType === "flat") {
|
224
225
|
options.ignorePatterns = ignorePattern;
|
226
|
+
options.stats = stats;
|
225
227
|
options.warnIgnored = warnIgnored;
|
226
228
|
|
227
229
|
/*
|
@@ -685,6 +685,7 @@ function processOptions({
|
|
685
685
|
overrideConfig = null,
|
686
686
|
overrideConfigFile = null,
|
687
687
|
plugins = {},
|
688
|
+
stats = false,
|
688
689
|
warnIgnored = true,
|
689
690
|
passOnNoPatterns = false,
|
690
691
|
ruleFilter = () => true,
|
@@ -791,6 +792,9 @@ function processOptions({
|
|
791
792
|
if (Array.isArray(plugins)) {
|
792
793
|
errors.push("'plugins' doesn't add plugins to configuration to load. Please use the 'overrideConfig.plugins' option instead.");
|
793
794
|
}
|
795
|
+
if (typeof stats !== "boolean") {
|
796
|
+
errors.push("'stats' must be a boolean.");
|
797
|
+
}
|
794
798
|
if (typeof warnIgnored !== "boolean") {
|
795
799
|
errors.push("'warnIgnored' must be a boolean.");
|
796
800
|
}
|
@@ -818,6 +822,7 @@ function processOptions({
|
|
818
822
|
globInputPaths,
|
819
823
|
ignore,
|
820
824
|
ignorePatterns,
|
825
|
+
stats,
|
821
826
|
passOnNoPatterns,
|
822
827
|
warnIgnored,
|
823
828
|
ruleFilter
|
package/lib/eslint/eslint.js
CHANGED
@@ -84,6 +84,7 @@ const LintResultCache = require("../cli-engine/lint-result-cache");
|
|
84
84
|
* doesn't do any config file lookup when `true`; considered to be a config filename
|
85
85
|
* when a string.
|
86
86
|
* @property {Record<string,Plugin>} [plugins] An array of plugin implementations.
|
87
|
+
* @property {boolean} [stats] True enables added statistics on lint results.
|
87
88
|
* @property {boolean} warnIgnored Show warnings when the file list includes ignored files
|
88
89
|
* @property {boolean} [passOnNoPatterns=false] When set to true, missing patterns cause
|
89
90
|
* the linting operation to short circuit and not report any failures.
|
@@ -465,6 +466,7 @@ async function calculateConfigArray(eslint, {
|
|
465
466
|
* @param {boolean} config.fix If `true` then it does fix.
|
466
467
|
* @param {boolean} config.allowInlineConfig If `true` then it uses directive comments.
|
467
468
|
* @param {Function} config.ruleFilter A predicate function to filter which rules should be run.
|
469
|
+
* @param {boolean} config.stats If `true`, then if reports extra statistics with the lint results.
|
468
470
|
* @param {Linter} config.linter The linter instance to verify.
|
469
471
|
* @returns {LintResult} The result of linting.
|
470
472
|
* @private
|
@@ -477,6 +479,7 @@ function verifyText({
|
|
477
479
|
fix,
|
478
480
|
allowInlineConfig,
|
479
481
|
ruleFilter,
|
482
|
+
stats,
|
480
483
|
linter
|
481
484
|
}) {
|
482
485
|
const filePath = providedFilePath || "<text>";
|
@@ -497,6 +500,7 @@ function verifyText({
|
|
497
500
|
filename: filePathToVerify,
|
498
501
|
fix,
|
499
502
|
ruleFilter,
|
503
|
+
stats,
|
500
504
|
|
501
505
|
/**
|
502
506
|
* Check if the linter should adopt a given code block or not.
|
@@ -528,6 +532,13 @@ function verifyText({
|
|
528
532
|
result.source = text;
|
529
533
|
}
|
530
534
|
|
535
|
+
if (stats) {
|
536
|
+
result.stats = {
|
537
|
+
times: linter.getTimes(),
|
538
|
+
fixPasses: linter.getFixPassCount()
|
539
|
+
};
|
540
|
+
}
|
541
|
+
|
531
542
|
return result;
|
532
543
|
}
|
533
544
|
|
@@ -808,6 +819,7 @@ class ESLint {
|
|
808
819
|
fix,
|
809
820
|
fixTypes,
|
810
821
|
ruleFilter,
|
822
|
+
stats,
|
811
823
|
globInputPaths,
|
812
824
|
errorOnUnmatchedPattern,
|
813
825
|
warnIgnored
|
@@ -922,6 +934,7 @@ class ESLint {
|
|
922
934
|
fix: fixer,
|
923
935
|
allowInlineConfig,
|
924
936
|
ruleFilter,
|
937
|
+
stats,
|
925
938
|
linter
|
926
939
|
});
|
927
940
|
|
@@ -1010,7 +1023,8 @@ class ESLint {
|
|
1010
1023
|
cwd,
|
1011
1024
|
fix,
|
1012
1025
|
warnIgnored: constructorWarnIgnored,
|
1013
|
-
ruleFilter
|
1026
|
+
ruleFilter,
|
1027
|
+
stats
|
1014
1028
|
} = eslintOptions;
|
1015
1029
|
const results = [];
|
1016
1030
|
const startTime = Date.now();
|
@@ -1034,6 +1048,7 @@ class ESLint {
|
|
1034
1048
|
fix,
|
1035
1049
|
allowInlineConfig,
|
1036
1050
|
ruleFilter,
|
1051
|
+
stats,
|
1037
1052
|
linter
|
1038
1053
|
}));
|
1039
1054
|
}
|
package/lib/linter/linter.js
CHANGED
@@ -41,6 +41,7 @@ const
|
|
41
41
|
ruleReplacements = require("../../conf/replacements.json");
|
42
42
|
const { getRuleFromConfig } = require("../config/flat-config-helpers");
|
43
43
|
const { FlatConfigArray } = require("../config/flat-config-array");
|
44
|
+
const { startTime, endTime } = require("../shared/stats");
|
44
45
|
const { RuleValidator } = require("../config/rule-validator");
|
45
46
|
const { assertIsRuleSeverity } = require("../config/flat-config-schema");
|
46
47
|
const { normalizeSeverityToString } = require("../shared/severity");
|
@@ -68,6 +69,7 @@ const STEP_KIND_CALL = 2;
|
|
68
69
|
/** @typedef {import("../shared/types").LanguageOptions} LanguageOptions */
|
69
70
|
/** @typedef {import("../shared/types").Processor} Processor */
|
70
71
|
/** @typedef {import("../shared/types").Rule} Rule */
|
72
|
+
/** @typedef {import("../shared/types").Times} Times */
|
71
73
|
|
72
74
|
/* eslint-disable jsdoc/valid-types -- https://github.com/jsdoc-type-pratt-parser/jsdoc-type-pratt-parser/issues/4#issuecomment-778805577 */
|
73
75
|
/**
|
@@ -92,6 +94,7 @@ const STEP_KIND_CALL = 2;
|
|
92
94
|
* @property {SourceCode|null} lastSourceCode The `SourceCode` instance that the last `verify()` call used.
|
93
95
|
* @property {SuppressedLintMessage[]} lastSuppressedMessages The `SuppressedLintMessage[]` instance that the last `verify()` call produced.
|
94
96
|
* @property {Map<string, Parser>} parserMap The loaded parsers.
|
97
|
+
* @property {Times} times The times spent on applying a rule to a file (see `stats` option).
|
95
98
|
* @property {Rules} ruleMap The loaded rules.
|
96
99
|
*/
|
97
100
|
|
@@ -736,6 +739,7 @@ function normalizeVerifyOptions(providedOptions, config) {
|
|
736
739
|
: null,
|
737
740
|
reportUnusedDisableDirectives,
|
738
741
|
disableFixes: Boolean(providedOptions.disableFixes),
|
742
|
+
stats: providedOptions.stats,
|
739
743
|
ruleFilter
|
740
744
|
};
|
741
745
|
}
|
@@ -825,6 +829,36 @@ function stripUnicodeBOM(text) {
|
|
825
829
|
return text;
|
826
830
|
}
|
827
831
|
|
832
|
+
/**
|
833
|
+
* Store time measurements in map
|
834
|
+
* @param {number} time Time measurement
|
835
|
+
* @param {Object} timeOpts Options relating which time was measured
|
836
|
+
* @param {WeakMap<Linter, LinterInternalSlots>} slots Linter internal slots map
|
837
|
+
* @returns {void}
|
838
|
+
*/
|
839
|
+
function storeTime(time, timeOpts, slots) {
|
840
|
+
const { type, key } = timeOpts;
|
841
|
+
|
842
|
+
if (!slots.times) {
|
843
|
+
slots.times = { passes: [{}] };
|
844
|
+
}
|
845
|
+
|
846
|
+
const passIndex = slots.fixPasses;
|
847
|
+
|
848
|
+
if (passIndex > slots.times.passes.length - 1) {
|
849
|
+
slots.times.passes.push({});
|
850
|
+
}
|
851
|
+
|
852
|
+
if (key) {
|
853
|
+
slots.times.passes[passIndex][type] ??= {};
|
854
|
+
slots.times.passes[passIndex][type][key] ??= { total: 0 };
|
855
|
+
slots.times.passes[passIndex][type][key].total += time;
|
856
|
+
} else {
|
857
|
+
slots.times.passes[passIndex][type] ??= { total: 0 };
|
858
|
+
slots.times.passes[passIndex][type].total += time;
|
859
|
+
}
|
860
|
+
}
|
861
|
+
|
828
862
|
/**
|
829
863
|
* Get the options for a rule (not including severity), if any
|
830
864
|
* @param {Array|number} ruleConfig rule configuration
|
@@ -986,10 +1020,13 @@ function createRuleListeners(rule, ruleContext) {
|
|
986
1020
|
* @param {string | undefined} cwd cwd of the cli
|
987
1021
|
* @param {string} physicalFilename The full path of the file on disk without any code block information
|
988
1022
|
* @param {Function} ruleFilter A predicate function to filter which rules should be executed.
|
1023
|
+
* @param {boolean} stats If true, stats are collected appended to the result
|
1024
|
+
* @param {WeakMap<Linter, LinterInternalSlots>} slots InternalSlotsMap of linter
|
989
1025
|
* @returns {LintMessage[]} An array of reported problems
|
990
1026
|
* @throws {Error} If traversal into a node fails.
|
991
1027
|
*/
|
992
|
-
function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageOptions, settings, filename, disableFixes, cwd, physicalFilename, ruleFilter
|
1028
|
+
function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageOptions, settings, filename, disableFixes, cwd, physicalFilename, ruleFilter,
|
1029
|
+
stats, slots) {
|
993
1030
|
const emitter = createEmitter();
|
994
1031
|
|
995
1032
|
// must happen first to assign all node.parent properties
|
@@ -1088,7 +1125,14 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageO
|
|
1088
1125
|
)
|
1089
1126
|
);
|
1090
1127
|
|
1091
|
-
const
|
1128
|
+
const ruleListenersReturn = (timing.enabled || stats)
|
1129
|
+
? timing.time(ruleId, createRuleListeners, stats)(rule, ruleContext) : createRuleListeners(rule, ruleContext);
|
1130
|
+
|
1131
|
+
const ruleListeners = stats ? ruleListenersReturn.result : ruleListenersReturn;
|
1132
|
+
|
1133
|
+
if (stats) {
|
1134
|
+
storeTime(ruleListenersReturn.tdiff, { type: "rules", key: ruleId }, slots);
|
1135
|
+
}
|
1092
1136
|
|
1093
1137
|
/**
|
1094
1138
|
* Include `ruleId` in error logs
|
@@ -1098,7 +1142,15 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageO
|
|
1098
1142
|
function addRuleErrorHandler(ruleListener) {
|
1099
1143
|
return function ruleErrorHandler(...listenerArgs) {
|
1100
1144
|
try {
|
1101
|
-
|
1145
|
+
const ruleListenerReturn = ruleListener(...listenerArgs);
|
1146
|
+
|
1147
|
+
const ruleListenerResult = stats ? ruleListenerReturn.result : ruleListenerReturn;
|
1148
|
+
|
1149
|
+
if (stats) {
|
1150
|
+
storeTime(ruleListenerReturn.tdiff, { type: "rules", key: ruleId }, slots);
|
1151
|
+
}
|
1152
|
+
|
1153
|
+
return ruleListenerResult;
|
1102
1154
|
} catch (e) {
|
1103
1155
|
e.ruleId = ruleId;
|
1104
1156
|
throw e;
|
@@ -1112,9 +1164,8 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageO
|
|
1112
1164
|
|
1113
1165
|
// add all the selectors from the rule as listeners
|
1114
1166
|
Object.keys(ruleListeners).forEach(selector => {
|
1115
|
-
const ruleListener = timing.enabled
|
1116
|
-
? timing.time(ruleId, ruleListeners[selector])
|
1117
|
-
: ruleListeners[selector];
|
1167
|
+
const ruleListener = (timing.enabled || stats)
|
1168
|
+
? timing.time(ruleId, ruleListeners[selector], stats) : ruleListeners[selector];
|
1118
1169
|
|
1119
1170
|
emitter.on(
|
1120
1171
|
selector,
|
@@ -1236,7 +1287,6 @@ function assertEslintrcConfig(linter) {
|
|
1236
1287
|
}
|
1237
1288
|
}
|
1238
1289
|
|
1239
|
-
|
1240
1290
|
//------------------------------------------------------------------------------
|
1241
1291
|
// Public Interface
|
1242
1292
|
//------------------------------------------------------------------------------
|
@@ -1342,12 +1392,25 @@ class Linter {
|
|
1342
1392
|
});
|
1343
1393
|
|
1344
1394
|
if (!slots.lastSourceCode) {
|
1395
|
+
let t;
|
1396
|
+
|
1397
|
+
if (options.stats) {
|
1398
|
+
t = startTime();
|
1399
|
+
}
|
1400
|
+
|
1345
1401
|
const parseResult = parse(
|
1346
1402
|
text,
|
1347
1403
|
languageOptions,
|
1348
1404
|
options.filename
|
1349
1405
|
);
|
1350
1406
|
|
1407
|
+
if (options.stats) {
|
1408
|
+
const time = endTime(t);
|
1409
|
+
const timeOpts = { type: "parse" };
|
1410
|
+
|
1411
|
+
storeTime(time, timeOpts, slots);
|
1412
|
+
}
|
1413
|
+
|
1351
1414
|
if (!parseResult.success) {
|
1352
1415
|
return [parseResult.error];
|
1353
1416
|
}
|
@@ -1398,7 +1461,9 @@ class Linter {
|
|
1398
1461
|
options.disableFixes,
|
1399
1462
|
slots.cwd,
|
1400
1463
|
providedOptions.physicalFilename,
|
1401
|
-
null
|
1464
|
+
null,
|
1465
|
+
options.stats,
|
1466
|
+
slots
|
1402
1467
|
);
|
1403
1468
|
} catch (err) {
|
1404
1469
|
err.message += `\nOccurred while linting ${options.filename}`;
|
@@ -1626,12 +1691,24 @@ class Linter {
|
|
1626
1691
|
const settings = config.settings || {};
|
1627
1692
|
|
1628
1693
|
if (!slots.lastSourceCode) {
|
1694
|
+
let t;
|
1695
|
+
|
1696
|
+
if (options.stats) {
|
1697
|
+
t = startTime();
|
1698
|
+
}
|
1699
|
+
|
1629
1700
|
const parseResult = parse(
|
1630
1701
|
text,
|
1631
1702
|
languageOptions,
|
1632
1703
|
options.filename
|
1633
1704
|
);
|
1634
1705
|
|
1706
|
+
if (options.stats) {
|
1707
|
+
const time = endTime(t);
|
1708
|
+
|
1709
|
+
storeTime(time, { type: "parse" }, slots);
|
1710
|
+
}
|
1711
|
+
|
1635
1712
|
if (!parseResult.success) {
|
1636
1713
|
return [parseResult.error];
|
1637
1714
|
}
|
@@ -1841,7 +1918,9 @@ class Linter {
|
|
1841
1918
|
options.disableFixes,
|
1842
1919
|
slots.cwd,
|
1843
1920
|
providedOptions.physicalFilename,
|
1844
|
-
options.ruleFilter
|
1921
|
+
options.ruleFilter,
|
1922
|
+
options.stats,
|
1923
|
+
slots
|
1845
1924
|
);
|
1846
1925
|
} catch (err) {
|
1847
1926
|
err.message += `\nOccurred while linting ${options.filename}`;
|
@@ -2081,6 +2160,22 @@ class Linter {
|
|
2081
2160
|
return internalSlotsMap.get(this).lastSourceCode;
|
2082
2161
|
}
|
2083
2162
|
|
2163
|
+
/**
|
2164
|
+
* Gets the times spent on (parsing, fixing, linting) a file.
|
2165
|
+
* @returns {LintTimes} The times.
|
2166
|
+
*/
|
2167
|
+
getTimes() {
|
2168
|
+
return internalSlotsMap.get(this).times ?? { passes: [] };
|
2169
|
+
}
|
2170
|
+
|
2171
|
+
/**
|
2172
|
+
* Gets the number of autofix passes that were made in the last run.
|
2173
|
+
* @returns {number} The number of autofix passes.
|
2174
|
+
*/
|
2175
|
+
getFixPassCount() {
|
2176
|
+
return internalSlotsMap.get(this).fixPasses ?? 0;
|
2177
|
+
}
|
2178
|
+
|
2084
2179
|
/**
|
2085
2180
|
* Gets the list of SuppressedLintMessage produced in the last running.
|
2086
2181
|
* @returns {SuppressedLintMessage[]} The list of SuppressedLintMessage
|
@@ -2157,6 +2252,7 @@ class Linter {
|
|
2157
2252
|
currentText = text;
|
2158
2253
|
const debugTextDescription = options && options.filename || `${text.slice(0, 10)}...`;
|
2159
2254
|
const shouldFix = options && typeof options.fix !== "undefined" ? options.fix : true;
|
2255
|
+
const stats = options?.stats;
|
2160
2256
|
|
2161
2257
|
/**
|
2162
2258
|
* This loop continues until one of the following is true:
|
@@ -2167,15 +2263,46 @@ class Linter {
|
|
2167
2263
|
* That means anytime a fix is successfully applied, there will be another pass.
|
2168
2264
|
* Essentially, guaranteeing a minimum of two passes.
|
2169
2265
|
*/
|
2266
|
+
const slots = internalSlotsMap.get(this);
|
2267
|
+
|
2268
|
+
// Remove lint times from the last run.
|
2269
|
+
if (stats) {
|
2270
|
+
delete slots.times;
|
2271
|
+
slots.fixPasses = 0;
|
2272
|
+
}
|
2273
|
+
|
2170
2274
|
do {
|
2171
2275
|
passNumber++;
|
2276
|
+
let tTotal;
|
2277
|
+
|
2278
|
+
if (stats) {
|
2279
|
+
tTotal = startTime();
|
2280
|
+
}
|
2172
2281
|
|
2173
2282
|
debug(`Linting code for ${debugTextDescription} (pass ${passNumber})`);
|
2174
2283
|
messages = this.verify(currentText, config, options);
|
2175
2284
|
|
2176
2285
|
debug(`Generating fixed text for ${debugTextDescription} (pass ${passNumber})`);
|
2286
|
+
let t;
|
2287
|
+
|
2288
|
+
if (stats) {
|
2289
|
+
t = startTime();
|
2290
|
+
}
|
2291
|
+
|
2177
2292
|
fixedResult = SourceCodeFixer.applyFixes(currentText, messages, shouldFix);
|
2178
2293
|
|
2294
|
+
if (stats) {
|
2295
|
+
|
2296
|
+
if (fixedResult.fixed) {
|
2297
|
+
const time = endTime(t);
|
2298
|
+
|
2299
|
+
storeTime(time, { type: "fix" }, slots);
|
2300
|
+
slots.fixPasses++;
|
2301
|
+
} else {
|
2302
|
+
storeTime(0, { type: "fix" }, slots);
|
2303
|
+
}
|
2304
|
+
}
|
2305
|
+
|
2179
2306
|
/*
|
2180
2307
|
* stop if there are any syntax errors.
|
2181
2308
|
* 'fixedResult.output' is a empty string.
|
@@ -2190,6 +2317,13 @@ class Linter {
|
|
2190
2317
|
// update to use the fixed output instead of the original text
|
2191
2318
|
currentText = fixedResult.output;
|
2192
2319
|
|
2320
|
+
if (stats) {
|
2321
|
+
tTotal = endTime(tTotal);
|
2322
|
+
const passIndex = slots.times.passes.length - 1;
|
2323
|
+
|
2324
|
+
slots.times.passes[passIndex].total = tTotal;
|
2325
|
+
}
|
2326
|
+
|
2193
2327
|
} while (
|
2194
2328
|
fixedResult.fixed &&
|
2195
2329
|
passNumber < MAX_AUTOFIX_PASSES
|
@@ -2200,7 +2334,18 @@ class Linter {
|
|
2200
2334
|
* the most up-to-date information.
|
2201
2335
|
*/
|
2202
2336
|
if (fixedResult.fixed) {
|
2337
|
+
let tTotal;
|
2338
|
+
|
2339
|
+
if (stats) {
|
2340
|
+
tTotal = startTime();
|
2341
|
+
}
|
2342
|
+
|
2203
2343
|
fixedResult.messages = this.verify(currentText, config, options);
|
2344
|
+
|
2345
|
+
if (stats) {
|
2346
|
+
storeTime(0, { type: "fix" }, slots);
|
2347
|
+
slots.times.passes.at(-1).total = endTime(tTotal);
|
2348
|
+
}
|
2204
2349
|
}
|
2205
2350
|
|
2206
2351
|
// ensure the last result properly reflects if fixes were done
|
package/lib/linter/timing.js
CHANGED
@@ -5,6 +5,8 @@
|
|
5
5
|
|
6
6
|
"use strict";
|
7
7
|
|
8
|
+
const { startTime, endTime } = require("../shared/stats");
|
9
|
+
|
8
10
|
//------------------------------------------------------------------------------
|
9
11
|
// Helpers
|
10
12
|
//------------------------------------------------------------------------------
|
@@ -128,21 +130,27 @@ module.exports = (function() {
|
|
128
130
|
* Time the run
|
129
131
|
* @param {any} key key from the data object
|
130
132
|
* @param {Function} fn function to be called
|
133
|
+
* @param {boolean} stats if 'stats' is true, return the result and the time difference
|
131
134
|
* @returns {Function} function to be executed
|
132
135
|
* @private
|
133
136
|
*/
|
134
|
-
function time(key, fn) {
|
135
|
-
if (typeof data[key] === "undefined") {
|
136
|
-
data[key] = 0;
|
137
|
-
}
|
137
|
+
function time(key, fn, stats) {
|
138
138
|
|
139
139
|
return function(...args) {
|
140
|
-
|
140
|
+
|
141
|
+
const t = startTime();
|
141
142
|
const result = fn(...args);
|
143
|
+
const tdiff = endTime(t);
|
144
|
+
|
145
|
+
if (enabled) {
|
146
|
+
if (typeof data[key] === "undefined") {
|
147
|
+
data[key] = 0;
|
148
|
+
}
|
149
|
+
|
150
|
+
data[key] += tdiff;
|
151
|
+
}
|
142
152
|
|
143
|
-
|
144
|
-
data[key] += t[0] * 1e3 + t[1] / 1e6;
|
145
|
-
return result;
|
153
|
+
return stats ? { result, tdiff } : result;
|
146
154
|
};
|
147
155
|
}
|
148
156
|
|
package/lib/options.js
CHANGED
@@ -60,6 +60,7 @@ const optionator = require("optionator");
|
|
60
60
|
* @property {boolean} [passOnNoPatterns=false] When set to true, missing patterns cause
|
61
61
|
* the linting operation to short circuit and not report any failures.
|
62
62
|
* @property {string[]} _ Positional filenames or patterns
|
63
|
+
* @property {boolean} [stats] Report additional statistics
|
63
64
|
*/
|
64
65
|
|
65
66
|
//------------------------------------------------------------------------------
|
@@ -103,6 +104,16 @@ module.exports = function(usingFlatConfig) {
|
|
103
104
|
};
|
104
105
|
}
|
105
106
|
|
107
|
+
let inspectConfigFlag;
|
108
|
+
|
109
|
+
if (usingFlatConfig) {
|
110
|
+
inspectConfigFlag = {
|
111
|
+
option: "inspect-config",
|
112
|
+
type: "Boolean",
|
113
|
+
description: "Open the config inspector with the current configuration"
|
114
|
+
};
|
115
|
+
}
|
116
|
+
|
106
117
|
let extFlag;
|
107
118
|
|
108
119
|
if (!usingFlatConfig) {
|
@@ -143,6 +154,17 @@ module.exports = function(usingFlatConfig) {
|
|
143
154
|
};
|
144
155
|
}
|
145
156
|
|
157
|
+
let statsFlag;
|
158
|
+
|
159
|
+
if (usingFlatConfig) {
|
160
|
+
statsFlag = {
|
161
|
+
option: "stats",
|
162
|
+
type: "Boolean",
|
163
|
+
default: "false",
|
164
|
+
description: "Add statistics to the lint report"
|
165
|
+
};
|
166
|
+
}
|
167
|
+
|
146
168
|
let warnIgnoredFlag;
|
147
169
|
|
148
170
|
if (usingFlatConfig) {
|
@@ -173,6 +195,7 @@ module.exports = function(usingFlatConfig) {
|
|
173
195
|
? "Use this configuration instead of eslint.config.js, eslint.config.mjs, or eslint.config.cjs"
|
174
196
|
: "Use this configuration, overriding .eslintrc.* config options if present"
|
175
197
|
},
|
198
|
+
inspectConfigFlag,
|
176
199
|
envFlag,
|
177
200
|
extFlag,
|
178
201
|
{
|
@@ -400,7 +423,8 @@ module.exports = function(usingFlatConfig) {
|
|
400
423
|
option: "print-config",
|
401
424
|
type: "path::String",
|
402
425
|
description: "Print the configuration for the given file"
|
403
|
-
}
|
426
|
+
},
|
427
|
+
statsFlag
|
404
428
|
].filter(value => !!value)
|
405
429
|
});
|
406
430
|
};
|
@@ -136,6 +136,15 @@ const suggestionObjectParameters = new Set([
|
|
136
136
|
]);
|
137
137
|
const friendlySuggestionObjectParameterList = `[${[...suggestionObjectParameters].map(key => `'${key}'`).join(", ")}]`;
|
138
138
|
|
139
|
+
/*
|
140
|
+
* Ignored test case properties when checking for test case duplicates.
|
141
|
+
*/
|
142
|
+
const duplicationIgnoredParameters = new Set([
|
143
|
+
"name",
|
144
|
+
"errors",
|
145
|
+
"output"
|
146
|
+
]);
|
147
|
+
|
139
148
|
const forbiddenMethods = [
|
140
149
|
"applyInlineConfig",
|
141
150
|
"applyLanguageOptions",
|
@@ -848,7 +857,7 @@ class RuleTester {
|
|
848
857
|
|
849
858
|
/**
|
850
859
|
* Check if this test case is a duplicate of one we have seen before.
|
851
|
-
* @param {Object} item test case object
|
860
|
+
* @param {string|Object} item test case object
|
852
861
|
* @param {Set<string>} seenTestCases set of serialized test cases we have seen so far (managed by this function)
|
853
862
|
* @returns {void}
|
854
863
|
* @private
|
@@ -863,7 +872,14 @@ class RuleTester {
|
|
863
872
|
return;
|
864
873
|
}
|
865
874
|
|
866
|
-
const
|
875
|
+
const normalizedItem = typeof item === "string" ? { code: item } : item;
|
876
|
+
const serializedTestCase = stringify(normalizedItem, {
|
877
|
+
replacer(key, value) {
|
878
|
+
|
879
|
+
// "this" is the currently stringified object --> only ignore top-level properties
|
880
|
+
return (normalizedItem !== this || !duplicationIgnoredParameters.has(key)) ? value : void 0;
|
881
|
+
}
|
882
|
+
});
|
867
883
|
|
868
884
|
assert(
|
869
885
|
!seenTestCases.has(serializedTestCase),
|