eslint 9.0.0-rc.0 → 9.1.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.
@@ -38,16 +38,16 @@ function compareLocations(itemA, itemB) {
38
38
  * @param {Iterable<Directive>} directives Unused directives to be removed.
39
39
  * @returns {Directive[][]} Directives grouped by their parent comment.
40
40
  */
41
- function groupByParentComment(directives) {
41
+ function groupByParentDirective(directives) {
42
42
  const groups = new Map();
43
43
 
44
44
  for (const directive of directives) {
45
- const { unprocessedDirective: { parentComment } } = directive;
45
+ const { unprocessedDirective: { parentDirective } } = directive;
46
46
 
47
- if (groups.has(parentComment)) {
48
- groups.get(parentComment).push(directive);
47
+ if (groups.has(parentDirective)) {
48
+ groups.get(parentDirective).push(directive);
49
49
  } else {
50
- groups.set(parentComment, [directive]);
50
+ groups.set(parentDirective, [directive]);
51
51
  }
52
52
  }
53
53
 
@@ -57,19 +57,19 @@ function groupByParentComment(directives) {
57
57
  /**
58
58
  * Creates removal details for a set of directives within the same comment.
59
59
  * @param {Directive[]} directives Unused directives to be removed.
60
- * @param {Token} commentToken The backing Comment token.
60
+ * @param {Token} node The backing Comment token.
61
61
  * @returns {{ description, fix, unprocessedDirective }[]} Details for later creation of output Problems.
62
62
  */
63
- function createIndividualDirectivesRemoval(directives, commentToken) {
63
+ function createIndividualDirectivesRemoval(directives, node) {
64
64
 
65
65
  /*
66
- * `commentToken.value` starts right after `//` or `/*`.
66
+ * `node.value` starts right after `//` or `/*`.
67
67
  * All calculated offsets will be relative to this index.
68
68
  */
69
- const commentValueStart = commentToken.range[0] + "//".length;
69
+ const commentValueStart = node.range[0] + "//".length;
70
70
 
71
71
  // Find where the list of rules starts. `\S+` matches with the directive name (e.g. `eslint-disable-line`)
72
- const listStartOffset = /^\s*\S+\s+/u.exec(commentToken.value)[0].length;
72
+ const listStartOffset = /^\s*\S+\s+/u.exec(node.value)[0].length;
73
73
 
74
74
  /*
75
75
  * Get the list text without any surrounding whitespace. In order to preserve the original
@@ -78,7 +78,7 @@ function createIndividualDirectivesRemoval(directives, commentToken) {
78
78
  * // eslint-disable-line rule-one , rule-two , rule-three -- comment
79
79
  * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
80
80
  */
81
- const listText = commentToken.value
81
+ const listText = node.value
82
82
  .slice(listStartOffset) // remove directive name and all whitespace before the list
83
83
  .split(/\s-{2,}\s/u)[0] // remove `-- comment`, if it exists
84
84
  .trimEnd(); // remove all whitespace after the list
@@ -159,13 +159,13 @@ function createIndividualDirectivesRemoval(directives, commentToken) {
159
159
  }
160
160
 
161
161
  /**
162
- * Creates a description of deleting an entire unused disable comment.
162
+ * Creates a description of deleting an entire unused disable directive.
163
163
  * @param {Directive[]} directives Unused directives to be removed.
164
- * @param {Token} commentToken The backing Comment token.
165
- * @returns {{ description, fix, unprocessedDirective }} Details for later creation of an output Problem.
164
+ * @param {Token} node The backing Comment token.
165
+ * @returns {{ description, fix, unprocessedDirective }} Details for later creation of an output problem.
166
166
  */
167
- function createCommentRemoval(directives, commentToken) {
168
- const { range } = commentToken;
167
+ function createDirectiveRemoval(directives, node) {
168
+ const { range } = node;
169
169
  const ruleIds = directives.filter(directive => directive.ruleId).map(directive => `'${directive.ruleId}'`);
170
170
 
171
171
  return {
@@ -186,20 +186,20 @@ function createCommentRemoval(directives, commentToken) {
186
186
  * @returns {{ description, fix, unprocessedDirective }[]} Details for later creation of output Problems.
187
187
  */
188
188
  function processUnusedDirectives(allDirectives) {
189
- const directiveGroups = groupByParentComment(allDirectives);
189
+ const directiveGroups = groupByParentDirective(allDirectives);
190
190
 
191
191
  return directiveGroups.flatMap(
192
192
  directives => {
193
- const { parentComment } = directives[0].unprocessedDirective;
194
- const remainingRuleIds = new Set(parentComment.ruleIds);
193
+ const { parentDirective } = directives[0].unprocessedDirective;
194
+ const remainingRuleIds = new Set(parentDirective.ruleIds);
195
195
 
196
196
  for (const directive of directives) {
197
197
  remainingRuleIds.delete(directive.ruleId);
198
198
  }
199
199
 
200
200
  return remainingRuleIds.size
201
- ? createIndividualDirectivesRemoval(directives, parentComment.commentToken)
202
- : [createCommentRemoval(directives, parentComment.commentToken)];
201
+ ? createIndividualDirectivesRemoval(directives, parentDirective.node)
202
+ : [createDirectiveRemoval(directives, parentDirective.node)];
203
203
  }
204
204
  );
205
205
  }
@@ -372,7 +372,7 @@ function applyDirectives(options) {
372
372
 
373
373
  const unusedDirectives = processed
374
374
  .map(({ description, fix, unprocessedDirective }) => {
375
- const { parentComment, type, line, column } = unprocessedDirective;
375
+ const { parentDirective, type, line, column } = unprocessedDirective;
376
376
 
377
377
  let message;
378
378
 
@@ -388,8 +388,8 @@ function applyDirectives(options) {
388
388
  return {
389
389
  ruleId: null,
390
390
  message,
391
- line: type === "disable-next-line" ? parentComment.commentToken.loc.start.line : line,
392
- column: type === "disable-next-line" ? parentComment.commentToken.loc.start.column + 1 : column,
391
+ line: type === "disable-next-line" ? parentDirective.node.loc.start.line : line,
392
+ column: type === "disable-next-line" ? parentDirective.node.loc.start.column + 1 : column,
393
393
  severity: options.reportUnusedDisableDirectives === "warn" ? 1 : 2,
394
394
  nodeType: null,
395
395
  ...options.disableFixes ? {} : { fix }
@@ -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
 
@@ -270,23 +273,21 @@ function createLintingProblem(options) {
270
273
  * Creates a collection of disable directives from a comment
271
274
  * @param {Object} options to create disable directives
272
275
  * @param {("disable"|"enable"|"disable-line"|"disable-next-line")} options.type The type of directive comment
273
- * @param {token} options.commentToken The Comment token
274
276
  * @param {string} options.value The value after the directive in the comment
275
277
  * comment specified no specific rules, so it applies to all rules (e.g. `eslint-disable`)
276
278
  * @param {string} options.justification The justification of the directive
277
- * @param {function(string): {create: Function}} options.ruleMapper A map from rule IDs to defined rules
279
+ * @param {ASTNode|token} options.node The Comment node/token.
280
+ * @param {function(string): {create: Function}} ruleMapper A map from rule IDs to defined rules
278
281
  * @returns {Object} Directives and problems from the comment
279
282
  */
280
- function createDisableDirectives(options) {
281
- const { commentToken, type, value, justification, ruleMapper } = options;
283
+ function createDisableDirectives({ type, value, justification, node }, ruleMapper) {
282
284
  const ruleIds = Object.keys(commentParser.parseListConfig(value));
283
285
  const directiveRules = ruleIds.length ? ruleIds : [null];
284
286
  const result = {
285
287
  directives: [], // valid disable directives
286
288
  directiveProblems: [] // problems in directives
287
289
  };
288
-
289
- const parentComment = { commentToken, ruleIds };
290
+ const parentDirective = { node, ruleIds };
290
291
 
291
292
  for (const ruleId of directiveRules) {
292
293
 
@@ -294,25 +295,25 @@ function createDisableDirectives(options) {
294
295
  if (ruleId === null || !!ruleMapper(ruleId)) {
295
296
  if (type === "disable-next-line") {
296
297
  result.directives.push({
297
- parentComment,
298
+ parentDirective,
298
299
  type,
299
- line: commentToken.loc.end.line,
300
- column: commentToken.loc.end.column + 1,
300
+ line: node.loc.end.line,
301
+ column: node.loc.end.column + 1,
301
302
  ruleId,
302
303
  justification
303
304
  });
304
305
  } else {
305
306
  result.directives.push({
306
- parentComment,
307
+ parentDirective,
307
308
  type,
308
- line: commentToken.loc.start.line,
309
- column: commentToken.loc.start.column + 1,
309
+ line: node.loc.start.line,
310
+ column: node.loc.start.column + 1,
310
311
  ruleId,
311
312
  justification
312
313
  });
313
314
  }
314
315
  } else {
315
- result.directiveProblems.push(createLintingProblem({ ruleId, loc: commentToken.loc }));
316
+ result.directiveProblems.push(createLintingProblem({ ruleId, loc: node.loc }));
316
317
  }
317
318
  }
318
319
  return result;
@@ -385,8 +386,12 @@ function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig, config)
385
386
  case "eslint-disable-next-line":
386
387
  case "eslint-disable-line": {
387
388
  const directiveType = directiveText.slice("eslint-".length);
388
- const options = { commentToken: comment, type: directiveType, value: directiveValue, justification: justificationPart, ruleMapper };
389
- const { directives, directiveProblems } = createDisableDirectives(options);
389
+ const { directives, directiveProblems } = createDisableDirectives({
390
+ type: directiveType,
391
+ value: directiveValue,
392
+ justification: justificationPart,
393
+ node: comment
394
+ }, ruleMapper);
390
395
 
391
396
  disableDirectives.push(...directives);
392
397
  problems.push(...directiveProblems);
@@ -540,53 +545,21 @@ function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig, config)
540
545
  * A collection of the directive comments that were found, along with any problems that occurred when parsing
541
546
  */
542
547
  function getDirectiveCommentsForFlatConfig(sourceCode, ruleMapper) {
543
- const problems = [];
544
548
  const disableDirectives = [];
549
+ const problems = [];
545
550
 
546
- sourceCode.getInlineConfigNodes().filter(token => token.type !== "Shebang").forEach(comment => {
547
- const { directivePart, justificationPart } = commentParser.extractDirectiveComment(comment.value);
548
-
549
- const match = directivesPattern.exec(directivePart);
550
-
551
- if (!match) {
552
- return;
553
- }
554
- const directiveText = match[1];
555
- const lineCommentSupported = /^eslint-disable-(next-)?line$/u.test(directiveText);
556
-
557
- if (comment.type === "Line" && !lineCommentSupported) {
558
- return;
559
- }
560
-
561
- if (directiveText === "eslint-disable-line" && comment.loc.start.line !== comment.loc.end.line) {
562
- const message = `${directiveText} comment should not span multiple lines.`;
563
-
564
- problems.push(createLintingProblem({
565
- ruleId: null,
566
- message,
567
- loc: comment.loc
568
- }));
569
- return;
570
- }
571
-
572
- const directiveValue = directivePart.slice(match.index + directiveText.length);
551
+ const {
552
+ directives: directivesSources,
553
+ problems: directivesProblems
554
+ } = sourceCode.getDisableDirectives();
573
555
 
574
- switch (directiveText) {
575
- case "eslint-disable":
576
- case "eslint-enable":
577
- case "eslint-disable-next-line":
578
- case "eslint-disable-line": {
579
- const directiveType = directiveText.slice("eslint-".length);
580
- const options = { commentToken: comment, type: directiveType, value: directiveValue, justification: justificationPart, ruleMapper };
581
- const { directives, directiveProblems } = createDisableDirectives(options);
556
+ problems.push(...directivesProblems.map(createLintingProblem));
582
557
 
583
- disableDirectives.push(...directives);
584
- problems.push(...directiveProblems);
585
- break;
586
- }
558
+ directivesSources.forEach(directive => {
559
+ const { directives, directiveProblems } = createDisableDirectives(directive, ruleMapper);
587
560
 
588
- // no default
589
- }
561
+ disableDirectives.push(...directives);
562
+ problems.push(...directiveProblems);
590
563
  });
591
564
 
592
565
  return {
@@ -736,6 +709,7 @@ function normalizeVerifyOptions(providedOptions, config) {
736
709
  : null,
737
710
  reportUnusedDisableDirectives,
738
711
  disableFixes: Boolean(providedOptions.disableFixes),
712
+ stats: providedOptions.stats,
739
713
  ruleFilter
740
714
  };
741
715
  }
@@ -825,6 +799,36 @@ function stripUnicodeBOM(text) {
825
799
  return text;
826
800
  }
827
801
 
802
+ /**
803
+ * Store time measurements in map
804
+ * @param {number} time Time measurement
805
+ * @param {Object} timeOpts Options relating which time was measured
806
+ * @param {WeakMap<Linter, LinterInternalSlots>} slots Linter internal slots map
807
+ * @returns {void}
808
+ */
809
+ function storeTime(time, timeOpts, slots) {
810
+ const { type, key } = timeOpts;
811
+
812
+ if (!slots.times) {
813
+ slots.times = { passes: [{}] };
814
+ }
815
+
816
+ const passIndex = slots.fixPasses;
817
+
818
+ if (passIndex > slots.times.passes.length - 1) {
819
+ slots.times.passes.push({});
820
+ }
821
+
822
+ if (key) {
823
+ slots.times.passes[passIndex][type] ??= {};
824
+ slots.times.passes[passIndex][type][key] ??= { total: 0 };
825
+ slots.times.passes[passIndex][type][key].total += time;
826
+ } else {
827
+ slots.times.passes[passIndex][type] ??= { total: 0 };
828
+ slots.times.passes[passIndex][type].total += time;
829
+ }
830
+ }
831
+
828
832
  /**
829
833
  * Get the options for a rule (not including severity), if any
830
834
  * @param {Array|number} ruleConfig rule configuration
@@ -986,10 +990,13 @@ function createRuleListeners(rule, ruleContext) {
986
990
  * @param {string | undefined} cwd cwd of the cli
987
991
  * @param {string} physicalFilename The full path of the file on disk without any code block information
988
992
  * @param {Function} ruleFilter A predicate function to filter which rules should be executed.
993
+ * @param {boolean} stats If true, stats are collected appended to the result
994
+ * @param {WeakMap<Linter, LinterInternalSlots>} slots InternalSlotsMap of linter
989
995
  * @returns {LintMessage[]} An array of reported problems
990
996
  * @throws {Error} If traversal into a node fails.
991
997
  */
992
- function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageOptions, settings, filename, disableFixes, cwd, physicalFilename, ruleFilter) {
998
+ function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageOptions, settings, filename, disableFixes, cwd, physicalFilename, ruleFilter,
999
+ stats, slots) {
993
1000
  const emitter = createEmitter();
994
1001
 
995
1002
  // must happen first to assign all node.parent properties
@@ -1088,7 +1095,14 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageO
1088
1095
  )
1089
1096
  );
1090
1097
 
1091
- const ruleListeners = timing.enabled ? timing.time(ruleId, createRuleListeners)(rule, ruleContext) : createRuleListeners(rule, ruleContext);
1098
+ const ruleListenersReturn = (timing.enabled || stats)
1099
+ ? timing.time(ruleId, createRuleListeners, stats)(rule, ruleContext) : createRuleListeners(rule, ruleContext);
1100
+
1101
+ const ruleListeners = stats ? ruleListenersReturn.result : ruleListenersReturn;
1102
+
1103
+ if (stats) {
1104
+ storeTime(ruleListenersReturn.tdiff, { type: "rules", key: ruleId }, slots);
1105
+ }
1092
1106
 
1093
1107
  /**
1094
1108
  * Include `ruleId` in error logs
@@ -1098,7 +1112,15 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageO
1098
1112
  function addRuleErrorHandler(ruleListener) {
1099
1113
  return function ruleErrorHandler(...listenerArgs) {
1100
1114
  try {
1101
- return ruleListener(...listenerArgs);
1115
+ const ruleListenerReturn = ruleListener(...listenerArgs);
1116
+
1117
+ const ruleListenerResult = stats ? ruleListenerReturn.result : ruleListenerReturn;
1118
+
1119
+ if (stats) {
1120
+ storeTime(ruleListenerReturn.tdiff, { type: "rules", key: ruleId }, slots);
1121
+ }
1122
+
1123
+ return ruleListenerResult;
1102
1124
  } catch (e) {
1103
1125
  e.ruleId = ruleId;
1104
1126
  throw e;
@@ -1112,9 +1134,8 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageO
1112
1134
 
1113
1135
  // add all the selectors from the rule as listeners
1114
1136
  Object.keys(ruleListeners).forEach(selector => {
1115
- const ruleListener = timing.enabled
1116
- ? timing.time(ruleId, ruleListeners[selector])
1117
- : ruleListeners[selector];
1137
+ const ruleListener = (timing.enabled || stats)
1138
+ ? timing.time(ruleId, ruleListeners[selector], stats) : ruleListeners[selector];
1118
1139
 
1119
1140
  emitter.on(
1120
1141
  selector,
@@ -1236,7 +1257,6 @@ function assertEslintrcConfig(linter) {
1236
1257
  }
1237
1258
  }
1238
1259
 
1239
-
1240
1260
  //------------------------------------------------------------------------------
1241
1261
  // Public Interface
1242
1262
  //------------------------------------------------------------------------------
@@ -1342,12 +1362,25 @@ class Linter {
1342
1362
  });
1343
1363
 
1344
1364
  if (!slots.lastSourceCode) {
1365
+ let t;
1366
+
1367
+ if (options.stats) {
1368
+ t = startTime();
1369
+ }
1370
+
1345
1371
  const parseResult = parse(
1346
1372
  text,
1347
1373
  languageOptions,
1348
1374
  options.filename
1349
1375
  );
1350
1376
 
1377
+ if (options.stats) {
1378
+ const time = endTime(t);
1379
+ const timeOpts = { type: "parse" };
1380
+
1381
+ storeTime(time, timeOpts, slots);
1382
+ }
1383
+
1351
1384
  if (!parseResult.success) {
1352
1385
  return [parseResult.error];
1353
1386
  }
@@ -1398,7 +1431,9 @@ class Linter {
1398
1431
  options.disableFixes,
1399
1432
  slots.cwd,
1400
1433
  providedOptions.physicalFilename,
1401
- null
1434
+ null,
1435
+ options.stats,
1436
+ slots
1402
1437
  );
1403
1438
  } catch (err) {
1404
1439
  err.message += `\nOccurred while linting ${options.filename}`;
@@ -1626,12 +1661,24 @@ class Linter {
1626
1661
  const settings = config.settings || {};
1627
1662
 
1628
1663
  if (!slots.lastSourceCode) {
1664
+ let t;
1665
+
1666
+ if (options.stats) {
1667
+ t = startTime();
1668
+ }
1669
+
1629
1670
  const parseResult = parse(
1630
1671
  text,
1631
1672
  languageOptions,
1632
1673
  options.filename
1633
1674
  );
1634
1675
 
1676
+ if (options.stats) {
1677
+ const time = endTime(t);
1678
+
1679
+ storeTime(time, { type: "parse" }, slots);
1680
+ }
1681
+
1635
1682
  if (!parseResult.success) {
1636
1683
  return [parseResult.error];
1637
1684
  }
@@ -1841,7 +1888,9 @@ class Linter {
1841
1888
  options.disableFixes,
1842
1889
  slots.cwd,
1843
1890
  providedOptions.physicalFilename,
1844
- options.ruleFilter
1891
+ options.ruleFilter,
1892
+ options.stats,
1893
+ slots
1845
1894
  );
1846
1895
  } catch (err) {
1847
1896
  err.message += `\nOccurred while linting ${options.filename}`;
@@ -2081,6 +2130,22 @@ class Linter {
2081
2130
  return internalSlotsMap.get(this).lastSourceCode;
2082
2131
  }
2083
2132
 
2133
+ /**
2134
+ * Gets the times spent on (parsing, fixing, linting) a file.
2135
+ * @returns {LintTimes} The times.
2136
+ */
2137
+ getTimes() {
2138
+ return internalSlotsMap.get(this).times ?? { passes: [] };
2139
+ }
2140
+
2141
+ /**
2142
+ * Gets the number of autofix passes that were made in the last run.
2143
+ * @returns {number} The number of autofix passes.
2144
+ */
2145
+ getFixPassCount() {
2146
+ return internalSlotsMap.get(this).fixPasses ?? 0;
2147
+ }
2148
+
2084
2149
  /**
2085
2150
  * Gets the list of SuppressedLintMessage produced in the last running.
2086
2151
  * @returns {SuppressedLintMessage[]} The list of SuppressedLintMessage
@@ -2157,6 +2222,7 @@ class Linter {
2157
2222
  currentText = text;
2158
2223
  const debugTextDescription = options && options.filename || `${text.slice(0, 10)}...`;
2159
2224
  const shouldFix = options && typeof options.fix !== "undefined" ? options.fix : true;
2225
+ const stats = options?.stats;
2160
2226
 
2161
2227
  /**
2162
2228
  * This loop continues until one of the following is true:
@@ -2167,15 +2233,46 @@ class Linter {
2167
2233
  * That means anytime a fix is successfully applied, there will be another pass.
2168
2234
  * Essentially, guaranteeing a minimum of two passes.
2169
2235
  */
2236
+ const slots = internalSlotsMap.get(this);
2237
+
2238
+ // Remove lint times from the last run.
2239
+ if (stats) {
2240
+ delete slots.times;
2241
+ slots.fixPasses = 0;
2242
+ }
2243
+
2170
2244
  do {
2171
2245
  passNumber++;
2246
+ let tTotal;
2247
+
2248
+ if (stats) {
2249
+ tTotal = startTime();
2250
+ }
2172
2251
 
2173
2252
  debug(`Linting code for ${debugTextDescription} (pass ${passNumber})`);
2174
2253
  messages = this.verify(currentText, config, options);
2175
2254
 
2176
2255
  debug(`Generating fixed text for ${debugTextDescription} (pass ${passNumber})`);
2256
+ let t;
2257
+
2258
+ if (stats) {
2259
+ t = startTime();
2260
+ }
2261
+
2177
2262
  fixedResult = SourceCodeFixer.applyFixes(currentText, messages, shouldFix);
2178
2263
 
2264
+ if (stats) {
2265
+
2266
+ if (fixedResult.fixed) {
2267
+ const time = endTime(t);
2268
+
2269
+ storeTime(time, { type: "fix" }, slots);
2270
+ slots.fixPasses++;
2271
+ } else {
2272
+ storeTime(0, { type: "fix" }, slots);
2273
+ }
2274
+ }
2275
+
2179
2276
  /*
2180
2277
  * stop if there are any syntax errors.
2181
2278
  * 'fixedResult.output' is a empty string.
@@ -2190,6 +2287,13 @@ class Linter {
2190
2287
  // update to use the fixed output instead of the original text
2191
2288
  currentText = fixedResult.output;
2192
2289
 
2290
+ if (stats) {
2291
+ tTotal = endTime(tTotal);
2292
+ const passIndex = slots.times.passes.length - 1;
2293
+
2294
+ slots.times.passes[passIndex].total = tTotal;
2295
+ }
2296
+
2193
2297
  } while (
2194
2298
  fixedResult.fixed &&
2195
2299
  passNumber < MAX_AUTOFIX_PASSES
@@ -2200,7 +2304,18 @@ class Linter {
2200
2304
  * the most up-to-date information.
2201
2305
  */
2202
2306
  if (fixedResult.fixed) {
2307
+ let tTotal;
2308
+
2309
+ if (stats) {
2310
+ tTotal = startTime();
2311
+ }
2312
+
2203
2313
  fixedResult.messages = this.verify(currentText, config, options);
2314
+
2315
+ if (stats) {
2316
+ storeTime(0, { type: "fix" }, slots);
2317
+ slots.times.passes.at(-1).total = endTime(tTotal);
2318
+ }
2204
2319
  }
2205
2320
 
2206
2321
  // 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