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 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 `.eslintrc` file in your directory. In it, you'll see some rules configured like this:
63
-
64
- ```json
65
- {
66
- "rules": {
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://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://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>
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
@@ -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
  }
@@ -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 ruleListeners = timing.enabled ? timing.time(ruleId, createRuleListeners)(rule, ruleContext) : createRuleListeners(rule, ruleContext);
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
- return ruleListener(...listenerArgs);
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
@@ -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
- let t = process.hrtime();
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
- t = process.hrtime(t);
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 serializedTestCase = stringify(item);
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),
@@ -47,11 +47,9 @@ module.exports = {
47
47
  },
48
48
  allow: {
49
49
  type: "array",
50
- items: [
51
- {
52
- type: "string"
53
- }
54
- ],
50
+ items: {
51
+ type: "string"
52
+ },
55
53
  minItems: 0,
56
54
  uniqueItems: true
57
55
  }