eslint 8.57.0 → 9.2.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.
Files changed (156) hide show
  1. package/README.md +31 -28
  2. package/bin/eslint.js +4 -3
  3. package/conf/ecma-version.js +16 -0
  4. package/conf/globals.js +1 -0
  5. package/conf/rule-type-list.json +3 -1
  6. package/lib/api.js +7 -11
  7. package/lib/cli-engine/cli-engine.js +14 -3
  8. package/lib/cli-engine/formatters/formatters-meta.json +1 -29
  9. package/lib/cli-engine/lint-result-cache.js +2 -2
  10. package/lib/cli.js +115 -36
  11. package/lib/config/default-config.js +3 -0
  12. package/lib/config/flat-config-array.js +110 -24
  13. package/lib/config/flat-config-helpers.js +41 -20
  14. package/lib/config/flat-config-schema.js +1 -7
  15. package/lib/config/rule-validator.js +42 -6
  16. package/lib/eslint/eslint-helpers.js +116 -58
  17. package/lib/eslint/eslint.js +892 -377
  18. package/lib/eslint/index.js +2 -2
  19. package/lib/eslint/legacy-eslint.js +728 -0
  20. package/lib/linter/apply-disable-directives.js +59 -31
  21. package/lib/linter/code-path-analysis/code-path-analyzer.js +0 -1
  22. package/lib/linter/code-path-analysis/code-path.js +32 -30
  23. package/lib/linter/code-path-analysis/fork-context.js +1 -1
  24. package/lib/linter/config-comment-parser.js +8 -11
  25. package/lib/linter/index.js +1 -3
  26. package/lib/linter/interpolate.js +24 -2
  27. package/lib/linter/linter.js +428 -207
  28. package/lib/linter/report-translator.js +3 -3
  29. package/lib/linter/rules.js +6 -15
  30. package/lib/linter/source-code-fixer.js +1 -1
  31. package/lib/linter/timing.js +16 -8
  32. package/lib/options.js +35 -3
  33. package/lib/rule-tester/index.js +3 -1
  34. package/lib/rule-tester/rule-tester.js +424 -347
  35. package/lib/rules/array-bracket-newline.js +1 -1
  36. package/lib/rules/array-bracket-spacing.js +1 -1
  37. package/lib/rules/block-scoped-var.js +1 -1
  38. package/lib/rules/callback-return.js +2 -2
  39. package/lib/rules/camelcase.js +3 -5
  40. package/lib/rules/capitalized-comments.js +10 -7
  41. package/lib/rules/comma-dangle.js +1 -1
  42. package/lib/rules/comma-style.js +2 -2
  43. package/lib/rules/complexity.js +14 -1
  44. package/lib/rules/constructor-super.js +99 -100
  45. package/lib/rules/default-case.js +1 -1
  46. package/lib/rules/eol-last.js +2 -2
  47. package/lib/rules/function-paren-newline.js +2 -2
  48. package/lib/rules/indent-legacy.js +5 -5
  49. package/lib/rules/indent.js +5 -5
  50. package/lib/rules/index.js +1 -2
  51. package/lib/rules/key-spacing.js +2 -2
  52. package/lib/rules/line-comment-position.js +1 -1
  53. package/lib/rules/lines-around-directive.js +2 -2
  54. package/lib/rules/max-depth.js +1 -1
  55. package/lib/rules/max-len.js +3 -3
  56. package/lib/rules/max-lines.js +3 -3
  57. package/lib/rules/max-nested-callbacks.js +1 -1
  58. package/lib/rules/max-params.js +1 -1
  59. package/lib/rules/max-statements.js +1 -1
  60. package/lib/rules/multiline-comment-style.js +7 -7
  61. package/lib/rules/new-cap.js +1 -1
  62. package/lib/rules/newline-after-var.js +1 -1
  63. package/lib/rules/newline-before-return.js +1 -1
  64. package/lib/rules/no-case-declarations.js +13 -1
  65. package/lib/rules/no-constant-binary-expression.js +7 -8
  66. package/lib/rules/no-constant-condition.js +18 -7
  67. package/lib/rules/no-constructor-return.js +2 -2
  68. package/lib/rules/no-dupe-class-members.js +2 -2
  69. package/lib/rules/no-else-return.js +1 -1
  70. package/lib/rules/no-empty-function.js +2 -2
  71. package/lib/rules/no-empty-static-block.js +1 -1
  72. package/lib/rules/no-extend-native.js +1 -2
  73. package/lib/rules/no-extra-semi.js +1 -1
  74. package/lib/rules/no-fallthrough.js +41 -16
  75. package/lib/rules/no-implicit-coercion.js +66 -24
  76. package/lib/rules/no-inner-declarations.js +23 -2
  77. package/lib/rules/no-invalid-regexp.js +1 -1
  78. package/lib/rules/no-invalid-this.js +1 -1
  79. package/lib/rules/no-lone-blocks.js +3 -3
  80. package/lib/rules/no-loss-of-precision.js +1 -1
  81. package/lib/rules/no-misleading-character-class.js +225 -69
  82. package/lib/rules/no-mixed-spaces-and-tabs.js +1 -1
  83. package/lib/rules/no-multiple-empty-lines.js +1 -1
  84. package/lib/rules/no-new-native-nonconstructor.js +1 -1
  85. package/lib/rules/no-new-symbol.js +8 -1
  86. package/lib/rules/no-restricted-globals.js +1 -1
  87. package/lib/rules/no-restricted-imports.js +186 -40
  88. package/lib/rules/no-restricted-modules.js +2 -2
  89. package/lib/rules/no-return-await.js +1 -1
  90. package/lib/rules/no-sequences.js +1 -0
  91. package/lib/rules/no-this-before-super.js +45 -13
  92. package/lib/rules/no-trailing-spaces.js +2 -3
  93. package/lib/rules/no-unneeded-ternary.js +1 -1
  94. package/lib/rules/no-unsafe-optional-chaining.js +1 -1
  95. package/lib/rules/no-unused-private-class-members.js +1 -1
  96. package/lib/rules/no-unused-vars.js +197 -36
  97. package/lib/rules/no-useless-assignment.js +566 -0
  98. package/lib/rules/no-useless-backreference.js +1 -1
  99. package/lib/rules/no-useless-computed-key.js +2 -2
  100. package/lib/rules/no-useless-return.js +7 -2
  101. package/lib/rules/object-curly-spacing.js +3 -3
  102. package/lib/rules/object-property-newline.js +1 -1
  103. package/lib/rules/one-var.js +5 -5
  104. package/lib/rules/padded-blocks.js +7 -7
  105. package/lib/rules/prefer-arrow-callback.js +3 -3
  106. package/lib/rules/prefer-reflect.js +1 -1
  107. package/lib/rules/prefer-regex-literals.js +1 -1
  108. package/lib/rules/prefer-template.js +1 -1
  109. package/lib/rules/radix.js +2 -2
  110. package/lib/rules/semi-style.js +1 -1
  111. package/lib/rules/sort-imports.js +1 -1
  112. package/lib/rules/sort-keys.js +1 -1
  113. package/lib/rules/sort-vars.js +1 -1
  114. package/lib/rules/space-unary-ops.js +1 -1
  115. package/lib/rules/strict.js +1 -1
  116. package/lib/rules/use-isnan.js +101 -7
  117. package/lib/rules/utils/ast-utils.js +16 -7
  118. package/lib/rules/utils/char-source.js +240 -0
  119. package/lib/rules/utils/lazy-loading-rule-map.js +1 -1
  120. package/lib/rules/utils/unicode/index.js +9 -4
  121. package/lib/rules/yield-star-spacing.js +1 -1
  122. package/lib/shared/runtime-info.js +1 -0
  123. package/lib/shared/serialization.js +55 -0
  124. package/lib/shared/stats.js +30 -0
  125. package/lib/shared/string-utils.js +9 -11
  126. package/lib/shared/types.js +35 -1
  127. package/lib/source-code/index.js +3 -1
  128. package/lib/source-code/source-code.js +299 -85
  129. package/lib/source-code/token-store/backward-token-cursor.js +3 -3
  130. package/lib/source-code/token-store/cursors.js +4 -2
  131. package/lib/source-code/token-store/forward-token-comment-cursor.js +3 -3
  132. package/lib/source-code/token-store/forward-token-cursor.js +3 -3
  133. package/lib/source-code/token-store/index.js +2 -2
  134. package/lib/unsupported-api.js +3 -5
  135. package/messages/no-config-found.js +1 -1
  136. package/messages/plugin-conflict.js +1 -1
  137. package/messages/plugin-invalid.js +1 -1
  138. package/messages/plugin-missing.js +1 -1
  139. package/package.json +32 -29
  140. package/conf/config-schema.js +0 -93
  141. package/lib/cli-engine/formatters/checkstyle.js +0 -60
  142. package/lib/cli-engine/formatters/compact.js +0 -60
  143. package/lib/cli-engine/formatters/jslint-xml.js +0 -41
  144. package/lib/cli-engine/formatters/junit.js +0 -82
  145. package/lib/cli-engine/formatters/tap.js +0 -95
  146. package/lib/cli-engine/formatters/unix.js +0 -58
  147. package/lib/cli-engine/formatters/visualstudio.js +0 -63
  148. package/lib/cli-engine/xml-escape.js +0 -34
  149. package/lib/eslint/flat-eslint.js +0 -1155
  150. package/lib/rule-tester/flat-rule-tester.js +0 -1131
  151. package/lib/rules/require-jsdoc.js +0 -122
  152. package/lib/rules/utils/patterns/letters.js +0 -36
  153. package/lib/rules/valid-jsdoc.js +0 -516
  154. package/lib/shared/config-validator.js +0 -347
  155. package/lib/shared/deprecation-warnings.js +0 -58
  156. package/lib/shared/relative-module-resolver.js +0 -50
@@ -30,7 +30,6 @@ const
30
30
  } = require("@eslint/eslintrc/universal"),
31
31
  Traverser = require("../shared/traverser"),
32
32
  { SourceCode } = require("../source-code"),
33
- CodePathAnalyzer = require("./code-path-analysis/code-path-analyzer"),
34
33
  applyDisableDirectives = require("./apply-disable-directives"),
35
34
  ConfigCommentParser = require("./config-comment-parser"),
36
35
  NodeEventGenerator = require("./node-event-generator"),
@@ -42,8 +41,9 @@ const
42
41
  ruleReplacements = require("../../conf/replacements.json");
43
42
  const { getRuleFromConfig } = require("../config/flat-config-helpers");
44
43
  const { FlatConfigArray } = require("../config/flat-config-array");
44
+ const { startTime, endTime } = require("../shared/stats");
45
45
  const { RuleValidator } = require("../config/rule-validator");
46
- const { assertIsRuleOptions, assertIsRuleSeverity } = require("../config/flat-config-schema");
46
+ const { assertIsRuleSeverity } = require("../config/flat-config-schema");
47
47
  const { normalizeSeverityToString } = require("../shared/severity");
48
48
  const debug = require("debug")("eslint:linter");
49
49
  const MAX_AUTOFIX_PASSES = 10;
@@ -52,13 +52,14 @@ const DEFAULT_ECMA_VERSION = 5;
52
52
  const commentParser = new ConfigCommentParser();
53
53
  const DEFAULT_ERROR_LOC = { start: { line: 1, column: 0 }, end: { line: 1, column: 1 } };
54
54
  const parserSymbol = Symbol.for("eslint.RuleTester.parser");
55
+ const { LATEST_ECMA_VERSION } = require("../../conf/ecma-version");
56
+ const STEP_KIND_VISIT = 1;
57
+ const STEP_KIND_CALL = 2;
55
58
 
56
59
  //------------------------------------------------------------------------------
57
60
  // Typedefs
58
61
  //------------------------------------------------------------------------------
59
62
 
60
- /** @typedef {InstanceType<import("../cli-engine/config-array").ConfigArray>} ConfigArray */
61
- /** @typedef {InstanceType<import("../cli-engine/config-array").ExtractedConfig>} ExtractedConfig */
62
63
  /** @typedef {import("../shared/types").ConfigData} ConfigData */
63
64
  /** @typedef {import("../shared/types").Environment} Environment */
64
65
  /** @typedef {import("../shared/types").GlobalConf} GlobalConf */
@@ -68,6 +69,7 @@ const parserSymbol = Symbol.for("eslint.RuleTester.parser");
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 parserSymbol = Symbol.for("eslint.RuleTester.parser");
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
 
@@ -105,6 +108,7 @@ const parserSymbol = Symbol.for("eslint.RuleTester.parser");
105
108
  * @property {string} [filename] the filename of the source code.
106
109
  * @property {boolean | "off" | "warn" | "error"} [reportUnusedDisableDirectives] Adds reported errors for
107
110
  * unused `eslint-disable` directives.
111
+ * @property {Function} [ruleFilter] A predicate function that determines whether a given rule should run.
108
112
  */
109
113
 
110
114
  /**
@@ -230,7 +234,7 @@ function addDeclaredGlobals(globalScope, configGlobals, { exportedVariables, ena
230
234
  * @private
231
235
  */
232
236
  function createMissingRuleMessage(ruleId) {
233
- return Object.prototype.hasOwnProperty.call(ruleReplacements.rules, ruleId)
237
+ return Object.hasOwn(ruleReplacements.rules, ruleId)
234
238
  ? `Rule '${ruleId}' was removed and replaced by: ${ruleReplacements.rules[ruleId].join(", ")}`
235
239
  : `Definition for rule '${ruleId}' was not found.`;
236
240
  }
@@ -269,23 +273,21 @@ function createLintingProblem(options) {
269
273
  * Creates a collection of disable directives from a comment
270
274
  * @param {Object} options to create disable directives
271
275
  * @param {("disable"|"enable"|"disable-line"|"disable-next-line")} options.type The type of directive comment
272
- * @param {token} options.commentToken The Comment token
273
276
  * @param {string} options.value The value after the directive in the comment
274
277
  * comment specified no specific rules, so it applies to all rules (e.g. `eslint-disable`)
275
278
  * @param {string} options.justification The justification of the directive
276
- * @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
277
281
  * @returns {Object} Directives and problems from the comment
278
282
  */
279
- function createDisableDirectives(options) {
280
- const { commentToken, type, value, justification, ruleMapper } = options;
283
+ function createDisableDirectives({ type, value, justification, node }, ruleMapper) {
281
284
  const ruleIds = Object.keys(commentParser.parseListConfig(value));
282
285
  const directiveRules = ruleIds.length ? ruleIds : [null];
283
286
  const result = {
284
287
  directives: [], // valid disable directives
285
288
  directiveProblems: [] // problems in directives
286
289
  };
287
-
288
- const parentComment = { commentToken, ruleIds };
290
+ const parentDirective = { node, ruleIds };
289
291
 
290
292
  for (const ruleId of directiveRules) {
291
293
 
@@ -293,25 +295,25 @@ function createDisableDirectives(options) {
293
295
  if (ruleId === null || !!ruleMapper(ruleId)) {
294
296
  if (type === "disable-next-line") {
295
297
  result.directives.push({
296
- parentComment,
298
+ parentDirective,
297
299
  type,
298
- line: commentToken.loc.end.line,
299
- column: commentToken.loc.end.column + 1,
300
+ line: node.loc.end.line,
301
+ column: node.loc.end.column + 1,
300
302
  ruleId,
301
303
  justification
302
304
  });
303
305
  } else {
304
306
  result.directives.push({
305
- parentComment,
307
+ parentDirective,
306
308
  type,
307
- line: commentToken.loc.start.line,
308
- column: commentToken.loc.start.column + 1,
309
+ line: node.loc.start.line,
310
+ column: node.loc.start.column + 1,
309
311
  ruleId,
310
312
  justification
311
313
  });
312
314
  }
313
315
  } else {
314
- result.directiveProblems.push(createLintingProblem({ ruleId, loc: commentToken.loc }));
316
+ result.directiveProblems.push(createLintingProblem({ ruleId, loc: node.loc }));
315
317
  }
316
318
  }
317
319
  return result;
@@ -324,10 +326,11 @@ function createDisableDirectives(options) {
324
326
  * @param {SourceCode} sourceCode The SourceCode object to get comments from.
325
327
  * @param {function(string): {create: Function}} ruleMapper A map from rule IDs to defined rules
326
328
  * @param {string|null} warnInlineConfig If a string then it should warn directive comments as disabled. The string value is the config name what the setting came from.
329
+ * @param {ConfigData} config Provided config.
327
330
  * @returns {{configuredRules: Object, enabledGlobals: {value:string,comment:Token}[], exportedVariables: Object, problems: LintMessage[], disableDirectives: DisableDirective[]}}
328
331
  * A collection of the directive comments that were found, along with any problems that occurred when parsing
329
332
  */
330
- function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig) {
333
+ function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig, config) {
331
334
  const configuredRules = {};
332
335
  const enabledGlobals = Object.create(null);
333
336
  const exportedVariables = {};
@@ -383,8 +386,12 @@ function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig) {
383
386
  case "eslint-disable-next-line":
384
387
  case "eslint-disable-line": {
385
388
  const directiveType = directiveText.slice("eslint-".length);
386
- const options = { commentToken: comment, type: directiveType, value: directiveValue, justification: justificationPart, ruleMapper };
387
- const { directives, directiveProblems } = createDisableDirectives(options);
389
+ const { directives, directiveProblems } = createDisableDirectives({
390
+ type: directiveType,
391
+ value: directiveValue,
392
+ justification: justificationPart,
393
+ node: comment
394
+ }, ruleMapper);
388
395
 
389
396
  disableDirectives.push(...directives);
390
397
  problems.push(...directiveProblems);
@@ -392,7 +399,7 @@ function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig) {
392
399
  }
393
400
 
394
401
  case "exported":
395
- Object.assign(exportedVariables, commentParser.parseStringConfig(directiveValue, comment));
402
+ Object.assign(exportedVariables, commentParser.parseListConfig(directiveValue, comment));
396
403
  break;
397
404
 
398
405
  case "globals":
@@ -436,9 +443,68 @@ function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig) {
436
443
  return;
437
444
  }
438
445
 
446
+ if (Object.hasOwn(configuredRules, name)) {
447
+ problems.push(createLintingProblem({
448
+ message: `Rule "${name}" is already configured by another configuration comment in the preceding code. This configuration is ignored.`,
449
+ loc: comment.loc
450
+ }));
451
+ return;
452
+ }
453
+
454
+ let ruleOptions = Array.isArray(ruleValue) ? ruleValue : [ruleValue];
455
+
456
+ /*
457
+ * If the rule was already configured, inline rule configuration that
458
+ * only has severity should retain options from the config and just override the severity.
459
+ *
460
+ * Example:
461
+ *
462
+ * {
463
+ * rules: {
464
+ * curly: ["error", "multi"]
465
+ * }
466
+ * }
467
+ *
468
+ * /* eslint curly: ["warn"] * /
469
+ *
470
+ * Results in:
471
+ *
472
+ * curly: ["warn", "multi"]
473
+ */
474
+ if (
475
+
476
+ /*
477
+ * If inline config for the rule has only severity
478
+ */
479
+ ruleOptions.length === 1 &&
480
+
481
+ /*
482
+ * And the rule was already configured
483
+ */
484
+ config.rules && Object.hasOwn(config.rules, name)
485
+ ) {
486
+
487
+ /*
488
+ * Then use severity from the inline config and options from the provided config
489
+ */
490
+ ruleOptions = [
491
+ ruleOptions[0], // severity from the inline config
492
+ ...Array.isArray(config.rules[name]) ? config.rules[name].slice(1) : [] // options from the provided config
493
+ ];
494
+ }
495
+
439
496
  try {
440
- validator.validateRuleOptions(rule, name, ruleValue);
497
+ validator.validateRuleOptions(rule, name, ruleOptions);
441
498
  } catch (err) {
499
+
500
+ /*
501
+ * If the rule has invalid `meta.schema`, throw the error because
502
+ * this is not an invalid inline configuration but an invalid rule.
503
+ */
504
+ if (err.code === "ESLINT_INVALID_RULE_OPTIONS_SCHEMA") {
505
+ throw err;
506
+ }
507
+
442
508
  problems.push(createLintingProblem({
443
509
  ruleId: name,
444
510
  message: err.message,
@@ -449,7 +515,7 @@ function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig) {
449
515
  return;
450
516
  }
451
517
 
452
- configuredRules[name] = ruleValue;
518
+ configuredRules[name] = ruleOptions;
453
519
  });
454
520
  } else {
455
521
  problems.push(parseResult.error);
@@ -479,53 +545,21 @@ function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig) {
479
545
  * A collection of the directive comments that were found, along with any problems that occurred when parsing
480
546
  */
481
547
  function getDirectiveCommentsForFlatConfig(sourceCode, ruleMapper) {
482
- const problems = [];
483
548
  const disableDirectives = [];
549
+ const problems = [];
484
550
 
485
- sourceCode.getInlineConfigNodes().filter(token => token.type !== "Shebang").forEach(comment => {
486
- const { directivePart, justificationPart } = commentParser.extractDirectiveComment(comment.value);
487
-
488
- const match = directivesPattern.exec(directivePart);
489
-
490
- if (!match) {
491
- return;
492
- }
493
- const directiveText = match[1];
494
- const lineCommentSupported = /^eslint-disable-(next-)?line$/u.test(directiveText);
495
-
496
- if (comment.type === "Line" && !lineCommentSupported) {
497
- return;
498
- }
499
-
500
- if (directiveText === "eslint-disable-line" && comment.loc.start.line !== comment.loc.end.line) {
501
- const message = `${directiveText} comment should not span multiple lines.`;
502
-
503
- problems.push(createLintingProblem({
504
- ruleId: null,
505
- message,
506
- loc: comment.loc
507
- }));
508
- return;
509
- }
510
-
511
- const directiveValue = directivePart.slice(match.index + directiveText.length);
551
+ const {
552
+ directives: directivesSources,
553
+ problems: directivesProblems
554
+ } = sourceCode.getDisableDirectives();
512
555
 
513
- switch (directiveText) {
514
- case "eslint-disable":
515
- case "eslint-enable":
516
- case "eslint-disable-next-line":
517
- case "eslint-disable-line": {
518
- const directiveType = directiveText.slice("eslint-".length);
519
- const options = { commentToken: comment, type: directiveType, value: directiveValue, justification: justificationPart, ruleMapper };
520
- const { directives, directiveProblems } = createDisableDirectives(options);
556
+ problems.push(...directivesProblems.map(createLintingProblem));
521
557
 
522
- disableDirectives.push(...directives);
523
- problems.push(...directiveProblems);
524
- break;
525
- }
558
+ directivesSources.forEach(directive => {
559
+ const { directives, directiveProblems } = createDisableDirectives(directive, ruleMapper);
526
560
 
527
- // no default
528
- }
561
+ disableDirectives.push(...directives);
562
+ problems.push(...directiveProblems);
529
563
  });
530
564
 
531
565
  return {
@@ -584,7 +618,7 @@ function normalizeEcmaVersionForLanguageOptions(ecmaVersion) {
584
618
  * that is used for a number of processes inside of ESLint. It's normally
585
619
  * safe to assume people want the latest unless otherwise specified.
586
620
  */
587
- return espree.latestEcmaVersion + 2009;
621
+ return LATEST_ECMA_VERSION;
588
622
  }
589
623
 
590
624
  const eslintEnvPattern = /\/\*\s*eslint-env\s(.+?)(?:\*\/|$)/gsu;
@@ -661,6 +695,12 @@ function normalizeVerifyOptions(providedOptions, config) {
661
695
  }
662
696
  }
663
697
 
698
+ let ruleFilter = providedOptions.ruleFilter;
699
+
700
+ if (typeof ruleFilter !== "function") {
701
+ ruleFilter = () => true;
702
+ }
703
+
664
704
  return {
665
705
  filename: normalizeFilename(providedOptions.filename || "<input>"),
666
706
  allowInlineConfig: !ignoreInlineConfig,
@@ -668,7 +708,9 @@ function normalizeVerifyOptions(providedOptions, config) {
668
708
  ? `your config${configNameOfNoInlineConfig}`
669
709
  : null,
670
710
  reportUnusedDisableDirectives,
671
- disableFixes: Boolean(providedOptions.disableFixes)
711
+ disableFixes: Boolean(providedOptions.disableFixes),
712
+ stats: providedOptions.stats,
713
+ ruleFilter
672
714
  };
673
715
  }
674
716
 
@@ -733,7 +775,7 @@ function createLanguageOptions({ globals: configuredGlobals, parser, parserOptio
733
775
  */
734
776
  function resolveGlobals(providedGlobals, enabledEnvironments) {
735
777
  return Object.assign(
736
- {},
778
+ Object.create(null),
737
779
  ...enabledEnvironments.filter(env => env.globals).map(env => env.globals),
738
780
  providedGlobals
739
781
  );
@@ -757,6 +799,36 @@ function stripUnicodeBOM(text) {
757
799
  return text;
758
800
  }
759
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
+
760
832
  /**
761
833
  * Get the options for a rule (not including severity), if any
762
834
  * @param {Array|number} ruleConfig rule configuration
@@ -885,12 +957,18 @@ function parse(text, languageOptions, filePath) {
885
957
 
886
958
  /**
887
959
  * Runs a rule, and gets its listeners
888
- * @param {Rule} rule A normalized rule with a `create` method
960
+ * @param {Rule} rule A rule object
889
961
  * @param {Context} ruleContext The context that should be passed to the rule
962
+ * @throws {TypeError} If `rule` is not an object with a `create` method
890
963
  * @throws {any} Any error during the rule's `create`
891
964
  * @returns {Object} A map of selector listeners provided by the rule
892
965
  */
893
966
  function createRuleListeners(rule, ruleContext) {
967
+
968
+ if (!rule || typeof rule !== "object" || typeof rule.create !== "function") {
969
+ throw new TypeError(`Error while loading rule '${ruleContext.id}': Rule must be an object with a \`create\` method`);
970
+ }
971
+
894
972
  try {
895
973
  return rule.create(ruleContext);
896
974
  } catch (ex) {
@@ -899,43 +977,6 @@ function createRuleListeners(rule, ruleContext) {
899
977
  }
900
978
  }
901
979
 
902
- // methods that exist on SourceCode object
903
- const DEPRECATED_SOURCECODE_PASSTHROUGHS = {
904
- getSource: "getText",
905
- getSourceLines: "getLines",
906
- getAllComments: "getAllComments",
907
- getNodeByRangeIndex: "getNodeByRangeIndex",
908
- getComments: "getComments",
909
- getCommentsBefore: "getCommentsBefore",
910
- getCommentsAfter: "getCommentsAfter",
911
- getCommentsInside: "getCommentsInside",
912
- getJSDocComment: "getJSDocComment",
913
- getFirstToken: "getFirstToken",
914
- getFirstTokens: "getFirstTokens",
915
- getLastToken: "getLastToken",
916
- getLastTokens: "getLastTokens",
917
- getTokenAfter: "getTokenAfter",
918
- getTokenBefore: "getTokenBefore",
919
- getTokenByRangeStart: "getTokenByRangeStart",
920
- getTokens: "getTokens",
921
- getTokensAfter: "getTokensAfter",
922
- getTokensBefore: "getTokensBefore",
923
- getTokensBetween: "getTokensBetween"
924
- };
925
-
926
-
927
- const BASE_TRAVERSAL_CONTEXT = Object.freeze(
928
- Object.keys(DEPRECATED_SOURCECODE_PASSTHROUGHS).reduce(
929
- (contextInfo, methodName) =>
930
- Object.assign(contextInfo, {
931
- [methodName](...args) {
932
- return this.sourceCode[DEPRECATED_SOURCECODE_PASSTHROUGHS[methodName]](...args);
933
- }
934
- }),
935
- {}
936
- )
937
- );
938
-
939
980
  /**
940
981
  * Runs the given rules on the given SourceCode object
941
982
  * @param {SourceCode} sourceCode A SourceCode object for the given text
@@ -948,23 +989,18 @@ const BASE_TRAVERSAL_CONTEXT = Object.freeze(
948
989
  * @param {boolean} disableFixes If true, it doesn't make `fix` properties.
949
990
  * @param {string | undefined} cwd cwd of the cli
950
991
  * @param {string} physicalFilename The full path of the file on disk without any code block information
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
951
995
  * @returns {LintMessage[]} An array of reported problems
996
+ * @throws {Error} If traversal into a node fails.
952
997
  */
953
- function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageOptions, settings, filename, disableFixes, cwd, physicalFilename) {
998
+ function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageOptions, settings, filename, disableFixes, cwd, physicalFilename, ruleFilter,
999
+ stats, slots) {
954
1000
  const emitter = createEmitter();
955
- const nodeQueue = [];
956
- let currentNode = sourceCode.ast;
957
-
958
- Traverser.traverse(sourceCode.ast, {
959
- enter(node, parent) {
960
- node.parent = parent;
961
- nodeQueue.push({ isEntering: true, node });
962
- },
963
- leave(node) {
964
- nodeQueue.push({ isEntering: false, node });
965
- },
966
- visitorKeys: sourceCode.visitorKeys
967
- });
1001
+
1002
+ // must happen first to assign all node.parent properties
1003
+ const eventQueue = sourceCode.traverse();
968
1004
 
969
1005
  /*
970
1006
  * Create a frozen object with the ruleContext properties and methods that are shared by all rules.
@@ -972,30 +1008,22 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageO
972
1008
  * properties once for each rule.
973
1009
  */
974
1010
  const sharedTraversalContext = Object.freeze(
975
- Object.assign(
976
- Object.create(BASE_TRAVERSAL_CONTEXT),
977
- {
978
- getAncestors: () => sourceCode.getAncestors(currentNode),
979
- getDeclaredVariables: node => sourceCode.getDeclaredVariables(node),
980
- getCwd: () => cwd,
981
- cwd,
982
- getFilename: () => filename,
983
- filename,
984
- getPhysicalFilename: () => physicalFilename || filename,
985
- physicalFilename: physicalFilename || filename,
986
- getScope: () => sourceCode.getScope(currentNode),
987
- getSourceCode: () => sourceCode,
988
- sourceCode,
989
- markVariableAsUsed: name => sourceCode.markVariableAsUsed(name, currentNode),
990
- parserOptions: {
991
- ...languageOptions.parserOptions
992
- },
993
- parserPath: parserName,
994
- languageOptions,
995
- parserServices: sourceCode.parserServices,
996
- settings
997
- }
998
- )
1011
+ {
1012
+ getCwd: () => cwd,
1013
+ cwd,
1014
+ getFilename: () => filename,
1015
+ filename,
1016
+ getPhysicalFilename: () => physicalFilename || filename,
1017
+ physicalFilename: physicalFilename || filename,
1018
+ getSourceCode: () => sourceCode,
1019
+ sourceCode,
1020
+ parserOptions: {
1021
+ ...languageOptions.parserOptions
1022
+ },
1023
+ parserPath: parserName,
1024
+ languageOptions,
1025
+ settings
1026
+ }
999
1027
  );
1000
1028
 
1001
1029
  const lintingProblems = [];
@@ -1008,6 +1036,10 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageO
1008
1036
  return;
1009
1037
  }
1010
1038
 
1039
+ if (ruleFilter && !ruleFilter({ ruleId, severity })) {
1040
+ return;
1041
+ }
1042
+
1011
1043
  const rule = ruleMapper(ruleId);
1012
1044
 
1013
1045
  if (!rule) {
@@ -1063,7 +1095,14 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageO
1063
1095
  )
1064
1096
  );
1065
1097
 
1066
- 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
+ }
1067
1106
 
1068
1107
  /**
1069
1108
  * Include `ruleId` in error logs
@@ -1073,7 +1112,15 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageO
1073
1112
  function addRuleErrorHandler(ruleListener) {
1074
1113
  return function ruleErrorHandler(...listenerArgs) {
1075
1114
  try {
1076
- 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;
1077
1124
  } catch (e) {
1078
1125
  e.ruleId = ruleId;
1079
1126
  throw e;
@@ -1087,9 +1134,8 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageO
1087
1134
 
1088
1135
  // add all the selectors from the rule as listeners
1089
1136
  Object.keys(ruleListeners).forEach(selector => {
1090
- const ruleListener = timing.enabled
1091
- ? timing.time(ruleId, ruleListeners[selector])
1092
- : ruleListeners[selector];
1137
+ const ruleListener = (timing.enabled || stats)
1138
+ ? timing.time(ruleId, ruleListeners[selector], stats) : ruleListeners[selector];
1093
1139
 
1094
1140
  emitter.on(
1095
1141
  selector,
@@ -1098,25 +1144,34 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageO
1098
1144
  });
1099
1145
  });
1100
1146
 
1101
- // only run code path analyzer if the top level node is "Program", skip otherwise
1102
- const eventGenerator = nodeQueue[0].node.type === "Program"
1103
- ? new CodePathAnalyzer(new NodeEventGenerator(emitter, { visitorKeys: sourceCode.visitorKeys, fallback: Traverser.getKeys }))
1104
- : new NodeEventGenerator(emitter, { visitorKeys: sourceCode.visitorKeys, fallback: Traverser.getKeys });
1147
+ const eventGenerator = new NodeEventGenerator(emitter, { visitorKeys: sourceCode.visitorKeys, fallback: Traverser.getKeys });
1105
1148
 
1106
- nodeQueue.forEach(traversalInfo => {
1107
- currentNode = traversalInfo.node;
1149
+ for (const step of eventQueue) {
1150
+ switch (step.kind) {
1151
+ case STEP_KIND_VISIT: {
1152
+ try {
1153
+ if (step.phase === 1) {
1154
+ eventGenerator.enterNode(step.target);
1155
+ } else {
1156
+ eventGenerator.leaveNode(step.target);
1157
+ }
1158
+ } catch (err) {
1159
+ err.currentNode = step.target;
1160
+ throw err;
1161
+ }
1162
+ break;
1163
+ }
1108
1164
 
1109
- try {
1110
- if (traversalInfo.isEntering) {
1111
- eventGenerator.enterNode(currentNode);
1112
- } else {
1113
- eventGenerator.leaveNode(currentNode);
1165
+ case STEP_KIND_CALL: {
1166
+ emitter.emit(step.target, ...step.args);
1167
+ break;
1114
1168
  }
1115
- } catch (err) {
1116
- err.currentNode = currentNode;
1117
- throw err;
1169
+
1170
+ default:
1171
+ throw new Error(`Invalid traversal step found: "${step.type}".`);
1118
1172
  }
1119
- });
1173
+
1174
+ }
1120
1175
 
1121
1176
  return lintingProblems;
1122
1177
  }
@@ -1202,7 +1257,6 @@ function assertEslintrcConfig(linter) {
1202
1257
  }
1203
1258
  }
1204
1259
 
1205
-
1206
1260
  //------------------------------------------------------------------------------
1207
1261
  // Public Interface
1208
1262
  //------------------------------------------------------------------------------
@@ -1217,9 +1271,9 @@ class Linter {
1217
1271
  * Initialize the Linter.
1218
1272
  * @param {Object} [config] the config object
1219
1273
  * @param {string} [config.cwd] path to a directory that should be considered as the current working directory, can be undefined.
1220
- * @param {"flat"|"eslintrc"} [config.configType="eslintrc"] the type of config used.
1274
+ * @param {"flat"|"eslintrc"} [config.configType="flat"] the type of config used.
1221
1275
  */
1222
- constructor({ cwd, configType } = {}) {
1276
+ constructor({ cwd, configType = "flat" } = {}) {
1223
1277
  internalSlotsMap.set(this, {
1224
1278
  cwd: normalizeCwd(cwd),
1225
1279
  lastConfigArray: null,
@@ -1308,12 +1362,25 @@ class Linter {
1308
1362
  });
1309
1363
 
1310
1364
  if (!slots.lastSourceCode) {
1365
+ let t;
1366
+
1367
+ if (options.stats) {
1368
+ t = startTime();
1369
+ }
1370
+
1311
1371
  const parseResult = parse(
1312
1372
  text,
1313
1373
  languageOptions,
1314
1374
  options.filename
1315
1375
  );
1316
1376
 
1377
+ if (options.stats) {
1378
+ const time = endTime(t);
1379
+ const timeOpts = { type: "parse" };
1380
+
1381
+ storeTime(time, timeOpts, slots);
1382
+ }
1383
+
1317
1384
  if (!parseResult.success) {
1318
1385
  return [parseResult.error];
1319
1386
  }
@@ -1338,7 +1405,7 @@ class Linter {
1338
1405
 
1339
1406
  const sourceCode = slots.lastSourceCode;
1340
1407
  const commentDirectives = options.allowInlineConfig
1341
- ? getDirectiveComments(sourceCode, ruleId => getRule(slots, ruleId), options.warnInlineConfig)
1408
+ ? getDirectiveComments(sourceCode, ruleId => getRule(slots, ruleId), options.warnInlineConfig, config)
1342
1409
  : { configuredRules: {}, enabledGlobals: {}, exportedVariables: {}, problems: [], disableDirectives: [] };
1343
1410
 
1344
1411
  // augment global scope with declared global variables
@@ -1349,6 +1416,7 @@ class Linter {
1349
1416
  );
1350
1417
 
1351
1418
  const configuredRules = Object.assign({}, config.rules, commentDirectives.configuredRules);
1419
+
1352
1420
  let lintingProblems;
1353
1421
 
1354
1422
  try {
@@ -1362,7 +1430,10 @@ class Linter {
1362
1430
  options.filename,
1363
1431
  options.disableFixes,
1364
1432
  slots.cwd,
1365
- providedOptions.physicalFilename
1433
+ providedOptions.physicalFilename,
1434
+ null,
1435
+ options.stats,
1436
+ slots
1366
1437
  );
1367
1438
  } catch (err) {
1368
1439
  err.message += `\nOccurred while linting ${options.filename}`;
@@ -1413,29 +1484,29 @@ class Linter {
1413
1484
  ? { filename: filenameOrOptions }
1414
1485
  : filenameOrOptions || {};
1415
1486
 
1416
- if (config) {
1417
- if (configType === "flat") {
1418
-
1419
- /*
1420
- * Because of how Webpack packages up the files, we can't
1421
- * compare directly to `FlatConfigArray` using `instanceof`
1422
- * because it's not the same `FlatConfigArray` as in the tests.
1423
- * So, we work around it by assuming an array is, in fact, a
1424
- * `FlatConfigArray` if it has a `getConfig()` method.
1425
- */
1426
- let configArray = config;
1427
-
1428
- if (!Array.isArray(config) || typeof config.getConfig !== "function") {
1429
- configArray = new FlatConfigArray(config, { basePath: cwd });
1430
- configArray.normalizeSync();
1431
- }
1487
+ const configToUse = config ?? {};
1432
1488
 
1433
- return this._distinguishSuppressedMessages(this._verifyWithFlatConfigArray(textOrSourceCode, configArray, options, true));
1434
- }
1489
+ if (configType !== "eslintrc") {
1435
1490
 
1436
- if (typeof config.extractConfig === "function") {
1437
- return this._distinguishSuppressedMessages(this._verifyWithConfigArray(textOrSourceCode, config, options));
1491
+ /*
1492
+ * Because of how Webpack packages up the files, we can't
1493
+ * compare directly to `FlatConfigArray` using `instanceof`
1494
+ * because it's not the same `FlatConfigArray` as in the tests.
1495
+ * So, we work around it by assuming an array is, in fact, a
1496
+ * `FlatConfigArray` if it has a `getConfig()` method.
1497
+ */
1498
+ let configArray = configToUse;
1499
+
1500
+ if (!Array.isArray(configToUse) || typeof configToUse.getConfig !== "function") {
1501
+ configArray = new FlatConfigArray(configToUse, { basePath: cwd });
1502
+ configArray.normalizeSync();
1438
1503
  }
1504
+
1505
+ return this._distinguishSuppressedMessages(this._verifyWithFlatConfigArray(textOrSourceCode, configArray, options, true));
1506
+ }
1507
+
1508
+ if (typeof configToUse.extractConfig === "function") {
1509
+ return this._distinguishSuppressedMessages(this._verifyWithConfigArray(textOrSourceCode, configToUse, options));
1439
1510
  }
1440
1511
 
1441
1512
  /*
@@ -1448,9 +1519,9 @@ class Linter {
1448
1519
  * So we cannot apply multiple processors.
1449
1520
  */
1450
1521
  if (options.preprocess || options.postprocess) {
1451
- return this._distinguishSuppressedMessages(this._verifyWithProcessor(textOrSourceCode, config, options));
1522
+ return this._distinguishSuppressedMessages(this._verifyWithProcessor(textOrSourceCode, configToUse, options));
1452
1523
  }
1453
- return this._distinguishSuppressedMessages(this._verifyWithoutProcessors(textOrSourceCode, config, options));
1524
+ return this._distinguishSuppressedMessages(this._verifyWithoutProcessors(textOrSourceCode, configToUse, options));
1454
1525
  }
1455
1526
 
1456
1527
  /**
@@ -1590,12 +1661,24 @@ class Linter {
1590
1661
  const settings = config.settings || {};
1591
1662
 
1592
1663
  if (!slots.lastSourceCode) {
1664
+ let t;
1665
+
1666
+ if (options.stats) {
1667
+ t = startTime();
1668
+ }
1669
+
1593
1670
  const parseResult = parse(
1594
1671
  text,
1595
1672
  languageOptions,
1596
1673
  options.filename
1597
1674
  );
1598
1675
 
1676
+ if (options.stats) {
1677
+ const time = endTime(t);
1678
+
1679
+ storeTime(time, { type: "parse" }, slots);
1680
+ }
1681
+
1599
1682
  if (!parseResult.success) {
1600
1683
  return [parseResult.error];
1601
1684
  }
@@ -1677,22 +1760,88 @@ class Linter {
1677
1760
  return;
1678
1761
  }
1679
1762
 
1763
+ if (Object.hasOwn(mergedInlineConfig.rules, ruleId)) {
1764
+ inlineConfigProblems.push(createLintingProblem({
1765
+ message: `Rule "${ruleId}" is already configured by another configuration comment in the preceding code. This configuration is ignored.`,
1766
+ loc: node.loc
1767
+ }));
1768
+ return;
1769
+ }
1770
+
1680
1771
  try {
1681
1772
 
1682
- const ruleOptions = Array.isArray(ruleValue) ? ruleValue : [ruleValue];
1773
+ let ruleOptions = Array.isArray(ruleValue) ? ruleValue : [ruleValue];
1683
1774
 
1684
- assertIsRuleOptions(ruleId, ruleValue);
1685
1775
  assertIsRuleSeverity(ruleId, ruleOptions[0]);
1686
1776
 
1687
- ruleValidator.validate({
1688
- plugins: config.plugins,
1689
- rules: {
1690
- [ruleId]: ruleOptions
1777
+ /*
1778
+ * If the rule was already configured, inline rule configuration that
1779
+ * only has severity should retain options from the config and just override the severity.
1780
+ *
1781
+ * Example:
1782
+ *
1783
+ * {
1784
+ * rules: {
1785
+ * curly: ["error", "multi"]
1786
+ * }
1787
+ * }
1788
+ *
1789
+ * /* eslint curly: ["warn"] * /
1790
+ *
1791
+ * Results in:
1792
+ *
1793
+ * curly: ["warn", "multi"]
1794
+ */
1795
+
1796
+ let shouldValidateOptions = true;
1797
+
1798
+ if (
1799
+
1800
+ /*
1801
+ * If inline config for the rule has only severity
1802
+ */
1803
+ ruleOptions.length === 1 &&
1804
+
1805
+ /*
1806
+ * And the rule was already configured
1807
+ */
1808
+ config.rules && Object.hasOwn(config.rules, ruleId)
1809
+ ) {
1810
+
1811
+ /*
1812
+ * Then use severity from the inline config and options from the provided config
1813
+ */
1814
+ ruleOptions = [
1815
+ ruleOptions[0], // severity from the inline config
1816
+ ...config.rules[ruleId].slice(1) // options from the provided config
1817
+ ];
1818
+
1819
+ // if the rule was enabled, the options have already been validated
1820
+ if (config.rules[ruleId][0] > 0) {
1821
+ shouldValidateOptions = false;
1691
1822
  }
1692
- });
1693
- mergedInlineConfig.rules[ruleId] = ruleValue;
1823
+ }
1824
+
1825
+ if (shouldValidateOptions) {
1826
+ ruleValidator.validate({
1827
+ plugins: config.plugins,
1828
+ rules: {
1829
+ [ruleId]: ruleOptions
1830
+ }
1831
+ });
1832
+ }
1833
+
1834
+ mergedInlineConfig.rules[ruleId] = ruleOptions;
1694
1835
  } catch (err) {
1695
1836
 
1837
+ /*
1838
+ * If the rule has invalid `meta.schema`, throw the error because
1839
+ * this is not an invalid inline configuration but an invalid rule.
1840
+ */
1841
+ if (err.code === "ESLINT_INVALID_RULE_OPTIONS_SCHEMA") {
1842
+ throw err;
1843
+ }
1844
+
1696
1845
  let baseMessage = err.message.slice(
1697
1846
  err.message.startsWith("Key \"rules\":")
1698
1847
  ? err.message.indexOf(":", 12) + 1
@@ -1722,6 +1871,7 @@ class Linter {
1722
1871
  : { problems: [], disableDirectives: [] };
1723
1872
 
1724
1873
  const configuredRules = Object.assign({}, config.rules, mergedInlineConfig.rules);
1874
+
1725
1875
  let lintingProblems;
1726
1876
 
1727
1877
  sourceCode.finalize();
@@ -1737,7 +1887,10 @@ class Linter {
1737
1887
  options.filename,
1738
1888
  options.disableFixes,
1739
1889
  slots.cwd,
1740
- providedOptions.physicalFilename
1890
+ providedOptions.physicalFilename,
1891
+ options.ruleFilter,
1892
+ options.stats,
1893
+ slots
1741
1894
  );
1742
1895
  } catch (err) {
1743
1896
  err.message += `\nOccurred while linting ${options.filename}`;
@@ -1768,7 +1921,9 @@ class Linter {
1768
1921
  .concat(commentDirectives.problems)
1769
1922
  .concat(inlineConfigProblems)
1770
1923
  .sort((problemA, problemB) => problemA.line - problemB.line || problemA.column - problemB.column),
1771
- reportUnusedDisableDirectives: options.reportUnusedDisableDirectives
1924
+ reportUnusedDisableDirectives: options.reportUnusedDisableDirectives,
1925
+ ruleFilter: options.ruleFilter,
1926
+ configuredRules
1772
1927
  });
1773
1928
  }
1774
1929
 
@@ -1975,6 +2130,22 @@ class Linter {
1975
2130
  return internalSlotsMap.get(this).lastSourceCode;
1976
2131
  }
1977
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
+
1978
2149
  /**
1979
2150
  * Gets the list of SuppressedLintMessage produced in the last running.
1980
2151
  * @returns {SuppressedLintMessage[]} The list of SuppressedLintMessage
@@ -1986,17 +2157,17 @@ class Linter {
1986
2157
  /**
1987
2158
  * Defines a new linting rule.
1988
2159
  * @param {string} ruleId A unique rule identifier
1989
- * @param {Function | Rule} ruleModule Function from context to object mapping AST node types to event handlers
2160
+ * @param {Rule} rule A rule object
1990
2161
  * @returns {void}
1991
2162
  */
1992
- defineRule(ruleId, ruleModule) {
2163
+ defineRule(ruleId, rule) {
1993
2164
  assertEslintrcConfig(this);
1994
- internalSlotsMap.get(this).ruleMap.define(ruleId, ruleModule);
2165
+ internalSlotsMap.get(this).ruleMap.define(ruleId, rule);
1995
2166
  }
1996
2167
 
1997
2168
  /**
1998
2169
  * Defines many new linting rules.
1999
- * @param {Record<string, Function | Rule>} rulesToDefine map from unique rule identifier to rule
2170
+ * @param {Record<string, Rule>} rulesToDefine map from unique rule identifier to rule
2000
2171
  * @returns {void}
2001
2172
  */
2002
2173
  defineRules(rulesToDefine) {
@@ -2044,13 +2215,14 @@ class Linter {
2044
2215
  * SourceCodeFixer.
2045
2216
  */
2046
2217
  verifyAndFix(text, config, options) {
2047
- let messages = [],
2218
+ let messages,
2048
2219
  fixedResult,
2049
2220
  fixed = false,
2050
2221
  passNumber = 0,
2051
2222
  currentText = text;
2052
2223
  const debugTextDescription = options && options.filename || `${text.slice(0, 10)}...`;
2053
2224
  const shouldFix = options && typeof options.fix !== "undefined" ? options.fix : true;
2225
+ const stats = options?.stats;
2054
2226
 
2055
2227
  /**
2056
2228
  * This loop continues until one of the following is true:
@@ -2061,15 +2233,46 @@ class Linter {
2061
2233
  * That means anytime a fix is successfully applied, there will be another pass.
2062
2234
  * Essentially, guaranteeing a minimum of two passes.
2063
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
+
2064
2244
  do {
2065
2245
  passNumber++;
2246
+ let tTotal;
2247
+
2248
+ if (stats) {
2249
+ tTotal = startTime();
2250
+ }
2066
2251
 
2067
2252
  debug(`Linting code for ${debugTextDescription} (pass ${passNumber})`);
2068
2253
  messages = this.verify(currentText, config, options);
2069
2254
 
2070
2255
  debug(`Generating fixed text for ${debugTextDescription} (pass ${passNumber})`);
2256
+ let t;
2257
+
2258
+ if (stats) {
2259
+ t = startTime();
2260
+ }
2261
+
2071
2262
  fixedResult = SourceCodeFixer.applyFixes(currentText, messages, shouldFix);
2072
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
+
2073
2276
  /*
2074
2277
  * stop if there are any syntax errors.
2075
2278
  * 'fixedResult.output' is a empty string.
@@ -2084,6 +2287,13 @@ class Linter {
2084
2287
  // update to use the fixed output instead of the original text
2085
2288
  currentText = fixedResult.output;
2086
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
+
2087
2297
  } while (
2088
2298
  fixedResult.fixed &&
2089
2299
  passNumber < MAX_AUTOFIX_PASSES
@@ -2094,7 +2304,18 @@ class Linter {
2094
2304
  * the most up-to-date information.
2095
2305
  */
2096
2306
  if (fixedResult.fixed) {
2307
+ let tTotal;
2308
+
2309
+ if (stats) {
2310
+ tTotal = startTime();
2311
+ }
2312
+
2097
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
+ }
2098
2319
  }
2099
2320
 
2100
2321
  // ensure the last result properly reflects if fixes were done