eslint 8.56.0 → 9.0.0-alpha.1

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 (121) hide show
  1. package/README.md +7 -2
  2. package/conf/ecma-version.js +16 -0
  3. package/conf/rule-type-list.json +3 -1
  4. package/lib/api.js +1 -1
  5. package/lib/cli-engine/cli-engine.js +14 -3
  6. package/lib/cli-engine/formatters/formatters-meta.json +1 -29
  7. package/lib/cli-engine/lint-result-cache.js +2 -2
  8. package/lib/cli.js +46 -25
  9. package/lib/config/default-config.js +3 -0
  10. package/lib/config/flat-config-array.js +0 -20
  11. package/lib/config/flat-config-helpers.js +41 -20
  12. package/lib/config/flat-config-schema.js +35 -25
  13. package/lib/config/rule-validator.js +27 -4
  14. package/lib/eslint/eslint-helpers.js +32 -12
  15. package/lib/eslint/eslint.js +856 -373
  16. package/lib/eslint/index.js +2 -2
  17. package/lib/eslint/legacy-eslint.js +722 -0
  18. package/lib/linter/apply-disable-directives.js +35 -7
  19. package/lib/linter/code-path-analysis/code-path.js +5 -19
  20. package/lib/linter/code-path-analysis/fork-context.js +1 -1
  21. package/lib/linter/config-comment-parser.js +8 -11
  22. package/lib/linter/linter.js +196 -100
  23. package/lib/linter/report-translator.js +2 -2
  24. package/lib/linter/rules.js +6 -15
  25. package/lib/linter/source-code-fixer.js +1 -1
  26. package/lib/options.js +9 -1
  27. package/lib/rule-tester/rule-tester.js +234 -291
  28. package/lib/rules/array-bracket-newline.js +1 -1
  29. package/lib/rules/array-bracket-spacing.js +1 -1
  30. package/lib/rules/block-scoped-var.js +1 -1
  31. package/lib/rules/callback-return.js +2 -2
  32. package/lib/rules/comma-dangle.js +1 -1
  33. package/lib/rules/comma-style.js +2 -2
  34. package/lib/rules/complexity.js +1 -1
  35. package/lib/rules/constructor-super.js +1 -1
  36. package/lib/rules/default-case.js +1 -1
  37. package/lib/rules/eol-last.js +2 -2
  38. package/lib/rules/function-paren-newline.js +2 -2
  39. package/lib/rules/indent-legacy.js +5 -5
  40. package/lib/rules/indent.js +5 -5
  41. package/lib/rules/index.js +1 -2
  42. package/lib/rules/key-spacing.js +2 -2
  43. package/lib/rules/line-comment-position.js +1 -1
  44. package/lib/rules/lines-around-directive.js +2 -2
  45. package/lib/rules/max-depth.js +1 -1
  46. package/lib/rules/max-len.js +3 -3
  47. package/lib/rules/max-lines.js +3 -3
  48. package/lib/rules/max-nested-callbacks.js +1 -1
  49. package/lib/rules/max-params.js +1 -1
  50. package/lib/rules/max-statements.js +1 -1
  51. package/lib/rules/multiline-comment-style.js +7 -7
  52. package/lib/rules/new-cap.js +1 -1
  53. package/lib/rules/newline-after-var.js +1 -1
  54. package/lib/rules/newline-before-return.js +1 -1
  55. package/lib/rules/no-constant-binary-expression.js +6 -6
  56. package/lib/rules/no-constructor-return.js +2 -2
  57. package/lib/rules/no-dupe-class-members.js +2 -2
  58. package/lib/rules/no-else-return.js +1 -1
  59. package/lib/rules/no-empty-function.js +2 -2
  60. package/lib/rules/no-empty-static-block.js +1 -1
  61. package/lib/rules/no-extra-semi.js +1 -1
  62. package/lib/rules/no-fallthrough.js +1 -1
  63. package/lib/rules/no-implicit-coercion.js +17 -1
  64. package/lib/rules/no-inner-declarations.js +23 -2
  65. package/lib/rules/no-invalid-regexp.js +1 -1
  66. package/lib/rules/no-invalid-this.js +1 -1
  67. package/lib/rules/no-lone-blocks.js +2 -2
  68. package/lib/rules/no-loss-of-precision.js +1 -1
  69. package/lib/rules/no-misleading-character-class.js +174 -65
  70. package/lib/rules/no-mixed-spaces-and-tabs.js +1 -1
  71. package/lib/rules/no-multiple-empty-lines.js +1 -1
  72. package/lib/rules/no-new-native-nonconstructor.js +1 -1
  73. package/lib/rules/no-new-symbol.js +8 -1
  74. package/lib/rules/no-restricted-globals.js +1 -1
  75. package/lib/rules/no-restricted-imports.js +2 -2
  76. package/lib/rules/no-restricted-modules.js +2 -2
  77. package/lib/rules/no-return-await.js +1 -1
  78. package/lib/rules/no-sequences.js +1 -0
  79. package/lib/rules/no-trailing-spaces.js +2 -3
  80. package/lib/rules/no-unneeded-ternary.js +1 -1
  81. package/lib/rules/no-unsafe-optional-chaining.js +1 -1
  82. package/lib/rules/no-unused-private-class-members.js +1 -1
  83. package/lib/rules/no-unused-vars.js +6 -8
  84. package/lib/rules/no-useless-assignment.js +566 -0
  85. package/lib/rules/no-useless-backreference.js +1 -1
  86. package/lib/rules/object-curly-spacing.js +3 -3
  87. package/lib/rules/object-property-newline.js +1 -1
  88. package/lib/rules/one-var.js +5 -5
  89. package/lib/rules/padded-blocks.js +7 -7
  90. package/lib/rules/prefer-arrow-callback.js +3 -3
  91. package/lib/rules/prefer-reflect.js +1 -1
  92. package/lib/rules/prefer-regex-literals.js +1 -1
  93. package/lib/rules/prefer-template.js +1 -1
  94. package/lib/rules/radix.js +2 -2
  95. package/lib/rules/semi-style.js +1 -1
  96. package/lib/rules/sort-imports.js +1 -1
  97. package/lib/rules/sort-keys.js +1 -1
  98. package/lib/rules/sort-vars.js +1 -1
  99. package/lib/rules/space-unary-ops.js +1 -1
  100. package/lib/rules/strict.js +1 -1
  101. package/lib/rules/utils/ast-utils.js +7 -7
  102. package/lib/rules/yield-star-spacing.js +1 -1
  103. package/lib/shared/types.js +1 -1
  104. package/lib/source-code/source-code.js +5 -83
  105. package/lib/source-code/token-store/index.js +2 -2
  106. package/lib/unsupported-api.js +3 -5
  107. package/package.json +12 -14
  108. package/conf/config-schema.js +0 -93
  109. package/lib/cli-engine/formatters/checkstyle.js +0 -60
  110. package/lib/cli-engine/formatters/compact.js +0 -60
  111. package/lib/cli-engine/formatters/jslint-xml.js +0 -41
  112. package/lib/cli-engine/formatters/junit.js +0 -82
  113. package/lib/cli-engine/formatters/tap.js +0 -95
  114. package/lib/cli-engine/formatters/unix.js +0 -58
  115. package/lib/cli-engine/formatters/visualstudio.js +0 -63
  116. package/lib/eslint/flat-eslint.js +0 -1142
  117. package/lib/rule-tester/flat-rule-tester.js +0 -1122
  118. package/lib/rules/require-jsdoc.js +0 -122
  119. package/lib/rules/valid-jsdoc.js +0 -516
  120. package/lib/shared/config-validator.js +0 -347
  121. package/lib/shared/relative-module-resolver.js +0 -50
@@ -1,68 +1,38 @@
1
1
  /**
2
- * @fileoverview Mocha test wrapper
2
+ * @fileoverview Mocha/Jest test wrapper
3
3
  * @author Ilya Volodin
4
4
  */
5
5
  "use strict";
6
6
 
7
7
  /* globals describe, it -- Mocha globals */
8
8
 
9
- /*
10
- * This is a wrapper around mocha to allow for DRY unittests for eslint
11
- * Format:
12
- * RuleTester.run("{ruleName}", {
13
- * valid: [
14
- * "{code}",
15
- * { code: "{code}", options: {options}, globals: {globals}, parser: "{parser}", settings: {settings} }
16
- * ],
17
- * invalid: [
18
- * { code: "{code}", errors: {numErrors} },
19
- * { code: "{code}", errors: ["{errorMessage}"] },
20
- * { code: "{code}", options: {options}, globals: {globals}, parser: "{parser}", settings: {settings}, errors: [{ message: "{errorMessage}", type: "{errorNodeType}"}] }
21
- * ]
22
- * });
23
- *
24
- * Variables:
25
- * {code} - String that represents the code to be tested
26
- * {options} - Arguments that are passed to the configurable rules.
27
- * {globals} - An object representing a list of variables that are
28
- * registered as globals
29
- * {parser} - String representing the parser to use
30
- * {settings} - An object representing global settings for all rules
31
- * {numErrors} - If failing case doesn't need to check error message,
32
- * this integer will specify how many errors should be
33
- * received
34
- * {errorMessage} - Message that is returned by the rule on failure
35
- * {errorNodeType} - AST node type that is returned by they rule as
36
- * a cause of the failure.
37
- */
38
-
39
9
  //------------------------------------------------------------------------------
40
10
  // Requirements
41
11
  //------------------------------------------------------------------------------
42
12
 
43
13
  const
44
14
  assert = require("assert"),
45
- path = require("path"),
46
15
  util = require("util"),
47
- merge = require("lodash.merge"),
48
16
  equal = require("fast-deep-equal"),
49
- Traverser = require("../../lib/shared/traverser"),
50
- { getRuleOptionsSchema, validate } = require("../shared/config-validator"),
51
- { Linter, SourceCodeFixer, interpolate } = require("../linter"),
52
- CodePath = require("../linter/code-path-analysis/code-path");
17
+ Traverser = require("../shared/traverser"),
18
+ { getRuleOptionsSchema } = require("../config/flat-config-helpers"),
19
+ { Linter, SourceCodeFixer, interpolate } = require("../linter");
20
+
21
+ const { FlatConfigArray } = require("../config/flat-config-array");
22
+ const { defaultConfig } = require("../config/default-config");
53
23
 
54
24
  const ajv = require("../shared/ajv")({ strictDefaults: true });
55
25
 
56
- const espreePath = require.resolve("espree");
57
26
  const parserSymbol = Symbol.for("eslint.RuleTester.parser");
58
-
59
27
  const { SourceCode } = require("../source-code");
28
+ const { ConfigArraySymbol } = require("@humanwhocodes/config-array");
60
29
 
61
30
  //------------------------------------------------------------------------------
62
31
  // Typedefs
63
32
  //------------------------------------------------------------------------------
64
33
 
65
34
  /** @typedef {import("../shared/types").Parser} Parser */
35
+ /** @typedef {import("../shared/types").LanguageOptions} LanguageOptions */
66
36
  /** @typedef {import("../shared/types").Rule} Rule */
67
37
 
68
38
 
@@ -72,12 +42,9 @@ const { SourceCode } = require("../source-code");
72
42
  * @property {string} [name] Name for the test case.
73
43
  * @property {string} code Code for the test case.
74
44
  * @property {any[]} [options] Options for the test case.
45
+ * @property {LanguageOptions} [languageOptions] The language options to use in the test case.
75
46
  * @property {{ [name: string]: any }} [settings] Settings for the test case.
76
47
  * @property {string} [filename] The fake filename for the test case. Useful for rules that make assertion about filenames.
77
- * @property {string} [parser] The absolute path for the parser.
78
- * @property {{ [name: string]: any }} [parserOptions] Options for the parser.
79
- * @property {{ [name: string]: "readonly" | "writable" | "off" }} [globals] The additional global variables.
80
- * @property {{ [name: string]: boolean }} [env] Environments for the test case.
81
48
  * @property {boolean} [only] Run only this test case or the subset of test cases with this property.
82
49
  */
83
50
 
@@ -91,10 +58,7 @@ const { SourceCode } = require("../source-code");
91
58
  * @property {any[]} [options] Options for the test case.
92
59
  * @property {{ [name: string]: any }} [settings] Settings for the test case.
93
60
  * @property {string} [filename] The fake filename for the test case. Useful for rules that make assertion about filenames.
94
- * @property {string} [parser] The absolute path for the parser.
95
- * @property {{ [name: string]: any }} [parserOptions] Options for the parser.
96
- * @property {{ [name: string]: "readonly" | "writable" | "off" }} [globals] The additional global variables.
97
- * @property {{ [name: string]: boolean }} [env] Environments for the test case.
61
+ * @property {LanguageOptions} [languageOptions] The language options to use in the test case.
98
62
  * @property {boolean} [only] Run only this test case or the subset of test cases with this property.
99
63
  */
100
64
 
@@ -120,7 +84,12 @@ const { SourceCode } = require("../source-code");
120
84
  * the initial default configuration
121
85
  */
122
86
  const testerDefaultConfig = { rules: {} };
123
- let defaultConfig = { rules: {} };
87
+
88
+ /*
89
+ * RuleTester uses this config as its default. This can be overwritten via
90
+ * setDefaultConfig().
91
+ */
92
+ let sharedDefaultConfig = { rules: {} };
124
93
 
125
94
  /*
126
95
  * List every parameters possible on a test case that are not related to eslint
@@ -169,36 +138,10 @@ const forbiddenMethods = [
169
138
  "finalize"
170
139
  ];
171
140
 
172
- const hasOwnProperty = Function.call.bind(Object.hasOwnProperty);
141
+ /** @type {Map<string,WeakSet>} */
142
+ const forbiddenMethodCalls = new Map(forbiddenMethods.map(methodName => ([methodName, new WeakSet()])));
173
143
 
174
- const DEPRECATED_SOURCECODE_PASSTHROUGHS = {
175
- getSource: "getText",
176
- getSourceLines: "getLines",
177
- getAllComments: "getAllComments",
178
- getNodeByRangeIndex: "getNodeByRangeIndex",
179
-
180
- // getComments: "getComments", -- already handled by a separate error
181
- getCommentsBefore: "getCommentsBefore",
182
- getCommentsAfter: "getCommentsAfter",
183
- getCommentsInside: "getCommentsInside",
184
- getJSDocComment: "getJSDocComment",
185
- getFirstToken: "getFirstToken",
186
- getFirstTokens: "getFirstTokens",
187
- getLastToken: "getLastToken",
188
- getLastTokens: "getLastTokens",
189
- getTokenAfter: "getTokenAfter",
190
- getTokenBefore: "getTokenBefore",
191
- getTokenByRangeStart: "getTokenByRangeStart",
192
- getTokens: "getTokens",
193
- getTokensAfter: "getTokensAfter",
194
- getTokensBefore: "getTokensBefore",
195
- getTokensBetween: "getTokensBetween",
196
-
197
- getScope: "getScope",
198
- getAncestors: "getAncestors",
199
- getDeclaredVariables: "getDeclaredVariables",
200
- markVariableAsUsed: "markVariableAsUsed"
201
- };
144
+ const hasOwnProperty = Function.call.bind(Object.hasOwnProperty);
202
145
 
203
146
  /**
204
147
  * Clones a given value deeply.
@@ -331,105 +274,40 @@ function wrapParser(parser) {
331
274
  }
332
275
 
333
276
  /**
334
- * Function to replace `SourceCode.prototype.getComments`.
335
- * @returns {void}
336
- * @throws {Error} Deprecation message.
337
- */
338
- function getCommentsDeprecation() {
339
- throw new Error(
340
- "`SourceCode#getComments()` is deprecated and will be removed in a future major version. Use `getCommentsBefore()`, `getCommentsAfter()`, and `getCommentsInside()` instead."
341
- );
342
- }
343
-
344
- /**
345
- * Function to replace forbidden `SourceCode` methods.
277
+ * Function to replace forbidden `SourceCode` methods. Allows just one call per method.
346
278
  * @param {string} methodName The name of the method to forbid.
279
+ * @param {Function} prototype The prototype with the original method to call.
347
280
  * @returns {Function} The function that throws the error.
348
281
  */
349
- function throwForbiddenMethodError(methodName) {
350
- return () => {
351
- throw new Error(
352
- `\`SourceCode#${methodName}()\` cannot be called inside a rule.`
353
- );
354
- };
355
- }
282
+ function throwForbiddenMethodError(methodName, prototype) {
356
283
 
357
- /**
358
- * Emit a deprecation warning if function-style format is being used.
359
- * @param {string} ruleName Name of the rule.
360
- * @returns {void}
361
- */
362
- function emitLegacyRuleAPIWarning(ruleName) {
363
- if (!emitLegacyRuleAPIWarning[`warned-${ruleName}`]) {
364
- emitLegacyRuleAPIWarning[`warned-${ruleName}`] = true;
365
- process.emitWarning(
366
- `"${ruleName}" rule is using the deprecated function-style format and will stop working in ESLint v9. Please use object-style format: https://eslint.org/docs/latest/extend/custom-rules`,
367
- "DeprecationWarning"
368
- );
369
- }
370
- }
284
+ const original = prototype[methodName];
371
285
 
372
- /**
373
- * Emit a deprecation warning if rule has options but is missing the "meta.schema" property
374
- * @param {string} ruleName Name of the rule.
375
- * @returns {void}
376
- */
377
- function emitMissingSchemaWarning(ruleName) {
378
- if (!emitMissingSchemaWarning[`warned-${ruleName}`]) {
379
- emitMissingSchemaWarning[`warned-${ruleName}`] = true;
380
- process.emitWarning(
381
- `"${ruleName}" rule has options but is missing the "meta.schema" property and will stop working in ESLint v9. Please add a schema: https://eslint.org/docs/latest/extend/custom-rules#options-schemas`,
382
- "DeprecationWarning"
383
- );
384
- }
385
- }
286
+ return function(...args) {
386
287
 
387
- /**
388
- * Emit a deprecation warning if a rule uses a deprecated `context` method.
389
- * @param {string} ruleName Name of the rule.
390
- * @param {string} methodName The name of the method on `context` that was used.
391
- * @returns {void}
392
- */
393
- function emitDeprecatedContextMethodWarning(ruleName, methodName) {
394
- if (!emitDeprecatedContextMethodWarning[`warned-${ruleName}-${methodName}`]) {
395
- emitDeprecatedContextMethodWarning[`warned-${ruleName}-${methodName}`] = true;
396
- process.emitWarning(
397
- `"${ruleName}" rule is using \`context.${methodName}()\`, which is deprecated and will be removed in ESLint v9. Please use \`sourceCode.${DEPRECATED_SOURCECODE_PASSTHROUGHS[methodName]}()\` instead.`,
398
- "DeprecationWarning"
399
- );
400
- }
401
- }
288
+ const called = forbiddenMethodCalls.get(methodName);
402
289
 
403
- /**
404
- * Emit a deprecation warning if rule uses CodePath#currentSegments.
405
- * @param {string} ruleName Name of the rule.
406
- * @returns {void}
407
- */
408
- function emitCodePathCurrentSegmentsWarning(ruleName) {
409
- if (!emitCodePathCurrentSegmentsWarning[`warned-${ruleName}`]) {
410
- emitCodePathCurrentSegmentsWarning[`warned-${ruleName}`] = true;
411
- process.emitWarning(
412
- `"${ruleName}" rule uses CodePath#currentSegments and will stop working in ESLint v9. Please read the documentation for how to update your code: https://eslint.org/docs/latest/extend/code-path-analysis#usage-examples`,
413
- "DeprecationWarning"
414
- );
415
- }
416
- }
290
+ /* eslint-disable no-invalid-this -- needed to operate as a method. */
291
+ if (!called.has(this)) {
292
+ called.add(this);
417
293
 
418
- /**
419
- * Emit a deprecation warning if `context.parserServices` is used.
420
- * @param {string} ruleName Name of the rule.
421
- * @returns {void}
422
- */
423
- function emitParserServicesWarning(ruleName) {
424
- if (!emitParserServicesWarning[`warned-${ruleName}`]) {
425
- emitParserServicesWarning[`warned-${ruleName}`] = true;
426
- process.emitWarning(
427
- `"${ruleName}" rule is using \`context.parserServices\`, which is deprecated and will be removed in ESLint v9. Please use \`sourceCode.parserServices\` instead.`,
428
- "DeprecationWarning"
294
+ return original.apply(this, args);
295
+ }
296
+ /* eslint-enable no-invalid-this -- not needed past this point */
297
+
298
+ throw new Error(
299
+ `\`SourceCode#${methodName}()\` cannot be called inside a rule.`
429
300
  );
430
- }
301
+ };
431
302
  }
432
303
 
304
+ const metaSchemaDescription = `
305
+ \t- If the rule has options, set \`meta.schema\` to an array or non-empty object to enable options validation.
306
+ \t- If the rule doesn't have options, omit \`meta.schema\` to enforce that no options can be passed to the rule.
307
+ \t- You can also set \`meta.schema\` to \`false\` to opt-out of options validation (not recommended).
308
+
309
+ \thttps://eslint.org/docs/latest/extend/custom-rules#options-schemas
310
+ `;
433
311
 
434
312
  //------------------------------------------------------------------------------
435
313
  // Public Interface
@@ -479,26 +357,20 @@ class RuleTester {
479
357
  * Creates a new instance of RuleTester.
480
358
  * @param {Object} [testerConfig] Optional, extra configuration for the tester
481
359
  */
482
- constructor(testerConfig) {
360
+ constructor(testerConfig = {}) {
483
361
 
484
362
  /**
485
363
  * The configuration to use for this tester. Combination of the tester
486
364
  * configuration and the default configuration.
487
365
  * @type {Object}
488
366
  */
489
- this.testerConfig = merge(
490
- {},
491
- defaultConfig,
367
+ this.testerConfig = [
368
+ sharedDefaultConfig,
492
369
  testerConfig,
493
370
  { rules: { "rule-tester/validate-ast": "error" } }
494
- );
371
+ ];
495
372
 
496
- /**
497
- * Rule definitions to define before tests.
498
- * @type {Object}
499
- */
500
- this.rules = {};
501
- this.linter = new Linter();
373
+ this.linter = new Linter({ configType: "flat" });
502
374
  }
503
375
 
504
376
  /**
@@ -511,10 +383,10 @@ class RuleTester {
511
383
  if (typeof config !== "object" || config === null) {
512
384
  throw new TypeError("RuleTester.setDefaultConfig: config must be an object");
513
385
  }
514
- defaultConfig = config;
386
+ sharedDefaultConfig = config;
515
387
 
516
388
  // Make sure the rules object exists since it is assumed to exist later
517
- defaultConfig.rules = defaultConfig.rules || {};
389
+ sharedDefaultConfig.rules = sharedDefaultConfig.rules || {};
518
390
  }
519
391
 
520
392
  /**
@@ -522,7 +394,7 @@ class RuleTester {
522
394
  * @returns {Object} the current configuration
523
395
  */
524
396
  static getDefaultConfig() {
525
- return defaultConfig;
397
+ return sharedDefaultConfig;
526
398
  }
527
399
 
528
400
  /**
@@ -531,7 +403,11 @@ class RuleTester {
531
403
  * @returns {void}
532
404
  */
533
405
  static resetDefaultConfig() {
534
- defaultConfig = merge({}, testerDefaultConfig);
406
+ sharedDefaultConfig = {
407
+ rules: {
408
+ ...testerDefaultConfig.rules
409
+ }
410
+ };
535
411
  }
536
412
 
537
413
 
@@ -602,29 +478,17 @@ class RuleTester {
602
478
  this[IT_ONLY] = value;
603
479
  }
604
480
 
605
- /**
606
- * Define a rule for one particular run of tests.
607
- * @param {string} name The name of the rule to define.
608
- * @param {Function | Rule} rule The rule definition.
609
- * @returns {void}
610
- */
611
- defineRule(name, rule) {
612
- if (typeof rule === "function") {
613
- emitLegacyRuleAPIWarning(name);
614
- }
615
- this.rules[name] = rule;
616
- }
617
481
 
618
482
  /**
619
483
  * Adds a new rule test to execute.
620
484
  * @param {string} ruleName The name of the rule to run.
621
- * @param {Function | Rule} rule The rule to test.
485
+ * @param {Rule} rule The rule to test.
622
486
  * @param {{
623
487
  * valid: (ValidTestCase | string)[],
624
488
  * invalid: InvalidTestCase[]
625
489
  * }} test The collection of tests to run.
626
- * @throws {TypeError|Error} If non-object `test`, or if a required
627
- * scenario of the given type is missing.
490
+ * @throws {TypeError|Error} If `rule` is not an object with a `create` method,
491
+ * or if non-object `test`, or if a required scenario of the given type is missing.
628
492
  * @returns {void}
629
493
  */
630
494
  run(ruleName, rule, test) {
@@ -632,7 +496,12 @@ class RuleTester {
632
496
  const testerConfig = this.testerConfig,
633
497
  requiredScenarios = ["valid", "invalid"],
634
498
  scenarioErrors = [],
635
- linter = this.linter;
499
+ linter = this.linter,
500
+ ruleId = `rule-to-test/${ruleName}`;
501
+
502
+ if (!rule || typeof rule !== "object" || typeof rule.create !== "function") {
503
+ throw new TypeError("Rule must be an object with a `create` method");
504
+ }
636
505
 
637
506
  if (!test || typeof test !== "object") {
638
507
  throw new TypeError(`Test Scenarios for rule ${ruleName} : Could not find test scenario object`);
@@ -650,54 +519,55 @@ class RuleTester {
650
519
  ].concat(scenarioErrors).join("\n"));
651
520
  }
652
521
 
653
- if (typeof rule === "function") {
654
- emitLegacyRuleAPIWarning(ruleName);
655
- }
522
+ const baseConfig = [
523
+ { files: ["**"] }, // Make sure the default config matches for all files
524
+ {
525
+ plugins: {
656
526
 
657
- linter.defineRule(ruleName, Object.assign({}, rule, {
658
-
659
- // Create a wrapper rule that freezes the `context` properties.
660
- create(context) {
661
- freezeDeeply(context.options);
662
- freezeDeeply(context.settings);
663
- freezeDeeply(context.parserOptions);
664
-
665
- // wrap all deprecated methods
666
- const newContext = Object.create(
667
- context,
668
- Object.fromEntries(Object.keys(DEPRECATED_SOURCECODE_PASSTHROUGHS).map(methodName => [
669
- methodName,
670
- {
671
- value(...args) {
672
-
673
- // emit deprecation warning
674
- emitDeprecatedContextMethodWarning(ruleName, methodName);
675
-
676
- // call the original method
677
- return context[methodName].call(this, ...args);
678
- },
679
- enumerable: true
680
- }
681
- ]))
682
- );
527
+ // copy root plugin over
528
+ "@": {
683
529
 
684
- // emit warning about context.parserServices
685
- const parserServices = context.parserServices;
530
+ /*
531
+ * Parsers are wrapped to detect more errors, so this needs
532
+ * to be a new object for each call to run(), otherwise the
533
+ * parsers will be wrapped multiple times.
534
+ */
535
+ parsers: {
536
+ ...defaultConfig[0].plugins["@"].parsers
537
+ },
686
538
 
687
- Object.defineProperty(newContext, "parserServices", {
688
- get() {
689
- emitParserServicesWarning(ruleName);
690
- return parserServices;
691
- }
692
- });
539
+ /*
540
+ * The rules key on the default plugin is a proxy to lazy-load
541
+ * just the rules that are needed. So, don't create a new object
542
+ * here, just use the default one to keep that performance
543
+ * enhancement.
544
+ */
545
+ rules: defaultConfig[0].plugins["@"].rules
546
+ },
547
+ "rule-to-test": {
548
+ rules: {
549
+ [ruleName]: Object.assign({}, rule, {
693
550
 
694
- Object.freeze(newContext);
551
+ // Create a wrapper rule that freezes the `context` properties.
552
+ create(context) {
553
+ freezeDeeply(context.options);
554
+ freezeDeeply(context.settings);
555
+ freezeDeeply(context.parserOptions);
695
556
 
696
- return (typeof rule === "function" ? rule : rule.create)(newContext);
697
- }
698
- }));
557
+ // freezeDeeply(context.languageOptions);
699
558
 
700
- linter.defineRules(this.rules);
559
+ return rule.create(context);
560
+ }
561
+ })
562
+ }
563
+ }
564
+ },
565
+ languageOptions: {
566
+ ...defaultConfig[0].languageOptions
567
+ }
568
+ },
569
+ ...defaultConfig.slice(1)
570
+ ];
701
571
 
702
572
  /**
703
573
  * Run the rule for the given item
@@ -707,8 +577,26 @@ class RuleTester {
707
577
  * @private
708
578
  */
709
579
  function runRuleForItem(item) {
710
- let config = merge({}, testerConfig),
711
- code, filename, output, beforeAST, afterAST;
580
+ const configs = new FlatConfigArray(testerConfig, { baseConfig });
581
+
582
+ /*
583
+ * Modify the returned config so that the parser is wrapped to catch
584
+ * access of the start/end properties. This method is called just
585
+ * once per code snippet being tested, so each test case gets a clean
586
+ * parser.
587
+ */
588
+ configs[ConfigArraySymbol.finalizeConfig] = function(...args) {
589
+
590
+ // can't do super here :(
591
+ const proto = Object.getPrototypeOf(this);
592
+ const calculatedConfig = proto[ConfigArraySymbol.finalizeConfig].apply(this, args);
593
+
594
+ // wrap the parser to catch start/end property access
595
+ calculatedConfig.languageOptions.parser = wrapParser(calculatedConfig.languageOptions.parser);
596
+ return calculatedConfig;
597
+ };
598
+
599
+ let code, filename, output, beforeAST, afterAST;
712
600
 
713
601
  if (typeof item === "string") {
714
602
  code = item;
@@ -725,64 +613,93 @@ class RuleTester {
725
613
  delete itemConfig[parameter];
726
614
  }
727
615
 
616
+ // wrap any parsers
617
+ if (itemConfig.languageOptions && itemConfig.languageOptions.parser) {
618
+
619
+ const parser = itemConfig.languageOptions.parser;
620
+
621
+ if (parser && typeof parser !== "object") {
622
+ throw new Error("Parser must be an object with a parse() or parseForESLint() method.");
623
+ }
624
+
625
+ }
626
+
728
627
  /*
729
628
  * Create the config object from the tester config and this item
730
629
  * specific configurations.
731
630
  */
732
- config = merge(
733
- config,
734
- itemConfig
735
- );
631
+ configs.push(itemConfig);
736
632
  }
737
633
 
738
634
  if (item.filename) {
739
635
  filename = item.filename;
740
636
  }
741
637
 
638
+ let ruleConfig = 1;
639
+
742
640
  if (hasOwnProperty(item, "options")) {
743
641
  assert(Array.isArray(item.options), "options must be an array");
744
- if (
745
- item.options.length > 0 &&
746
- typeof rule === "object" &&
747
- (
748
- !rule.meta || (rule.meta && (typeof rule.meta.schema === "undefined" || rule.meta.schema === null))
749
- )
750
- ) {
751
- emitMissingSchemaWarning(ruleName);
642
+ ruleConfig = [1, ...item.options];
643
+ }
644
+
645
+ configs.push({
646
+ rules: {
647
+ [ruleId]: ruleConfig
752
648
  }
753
- config.rules[ruleName] = [1].concat(item.options);
754
- } else {
755
- config.rules[ruleName] = 1;
649
+ });
650
+
651
+ let schema;
652
+
653
+ try {
654
+ schema = getRuleOptionsSchema(rule);
655
+ } catch (err) {
656
+ err.message += metaSchemaDescription;
657
+ throw err;
756
658
  }
757
659
 
758
- const schema = getRuleOptionsSchema(rule);
660
+ /*
661
+ * Check and throw an error if the schema is an empty object (`schema:{}`), because such schema
662
+ * doesn't validate or enforce anything and is therefore considered a possible error. If the intent
663
+ * was to skip options validation, `schema:false` should be set instead (explicit opt-out).
664
+ *
665
+ * For this purpose, a schema object is considered empty if it doesn't have any own enumerable string-keyed
666
+ * properties. While `ajv.compile()` does use enumerable properties from the prototype chain as well,
667
+ * it caches compiled schemas by serializing only own enumerable properties, so it's generally not a good idea
668
+ * to use inherited properties in schemas because schemas that differ only in inherited properties would end up
669
+ * having the same cache entry that would be correct for only one of them.
670
+ *
671
+ * At this point, `schema` can only be an object or `null`.
672
+ */
673
+ if (schema && Object.keys(schema).length === 0) {
674
+ throw new Error(`\`schema: {}\` is a no-op${metaSchemaDescription}`);
675
+ }
759
676
 
760
677
  /*
761
678
  * Setup AST getters.
762
679
  * The goal is to check whether or not AST was modified when
763
680
  * running the rule under test.
764
681
  */
765
- linter.defineRule("rule-tester/validate-ast", {
766
- create() {
767
- return {
768
- Program(node) {
769
- beforeAST = cloneDeeplyExcludesParent(node);
770
- },
771
- "Program:exit"(node) {
772
- afterAST = node;
682
+ configs.push({
683
+ plugins: {
684
+ "rule-tester": {
685
+ rules: {
686
+ "validate-ast": {
687
+ create() {
688
+ return {
689
+ Program(node) {
690
+ beforeAST = cloneDeeplyExcludesParent(node);
691
+ },
692
+ "Program:exit"(node) {
693
+ afterAST = node;
694
+ }
695
+ };
696
+ }
697
+ }
773
698
  }
774
- };
699
+ }
775
700
  }
776
701
  });
777
702
 
778
- if (typeof config.parser === "string") {
779
- assert(path.isAbsolute(config.parser), "Parsers provided as strings to RuleTester must be absolute paths");
780
- } else {
781
- config.parser = espreePath;
782
- }
783
-
784
- linter.defineParser(config.parser, wrapParser(require(config.parser)));
785
-
786
703
  if (schema) {
787
704
  ajv.validateSchema(schema);
788
705
 
@@ -809,35 +726,32 @@ class RuleTester {
809
726
  }
810
727
  }
811
728
 
812
- validate(config, "rule-tester", id => (id === ruleName ? rule : null));
729
+ // check for validation errors
730
+ try {
731
+ configs.normalizeSync();
732
+ configs.getConfig("test.js");
733
+ } catch (error) {
734
+ error.message = `ESLint configuration in rule-tester is invalid: ${error.message}`;
735
+ throw error;
736
+ }
813
737
 
814
738
  // Verify the code.
815
- const { getComments, applyLanguageOptions, applyInlineConfig, finalize } = SourceCode.prototype;
816
- const originalCurrentSegments = Object.getOwnPropertyDescriptor(CodePath.prototype, "currentSegments");
739
+ const { applyLanguageOptions, applyInlineConfig, finalize } = SourceCode.prototype;
817
740
  let messages;
818
741
 
819
742
  try {
820
- SourceCode.prototype.getComments = getCommentsDeprecation;
821
- Object.defineProperty(CodePath.prototype, "currentSegments", {
822
- get() {
823
- emitCodePathCurrentSegmentsWarning(ruleName);
824
- return originalCurrentSegments.get.call(this);
825
- }
826
- });
827
-
828
743
  forbiddenMethods.forEach(methodName => {
829
- SourceCode.prototype[methodName] = throwForbiddenMethodError(methodName);
744
+ SourceCode.prototype[methodName] = throwForbiddenMethodError(methodName, SourceCode.prototype);
830
745
  });
831
746
 
832
- messages = linter.verify(code, config, filename);
747
+ messages = linter.verify(code, configs, filename);
833
748
  } finally {
834
- SourceCode.prototype.getComments = getComments;
835
- Object.defineProperty(CodePath.prototype, "currentSegments", originalCurrentSegments);
836
749
  SourceCode.prototype.applyInlineConfig = applyInlineConfig;
837
750
  SourceCode.prototype.applyLanguageOptions = applyLanguageOptions;
838
751
  SourceCode.prototype.finalize = finalize;
839
752
  }
840
753
 
754
+
841
755
  const fatalErrorMessage = messages.find(m => m.fatal);
842
756
 
843
757
  assert(!fatalErrorMessage, `A fatal parsing error occurred: ${fatalErrorMessage && fatalErrorMessage.message}`);
@@ -845,7 +759,7 @@ class RuleTester {
845
759
  // Verify if autofix makes a syntax error or not.
846
760
  if (messages.some(m => m.fix)) {
847
761
  output = SourceCodeFixer.applyFixes(code, messages).output;
848
- const errorMessageInFix = linter.verify(output, config, filename).find(m => m.fatal);
762
+ const errorMessageInFix = linter.verify(output, configs, filename).find(m => m.fatal);
849
763
 
850
764
  assert(!errorMessageInFix, [
851
765
  "A fatal parsing error occurred in autofix.",
@@ -861,7 +775,9 @@ class RuleTester {
861
775
  messages,
862
776
  output,
863
777
  beforeAST,
864
- afterAST: cloneDeeplyExcludesParent(afterAST)
778
+ afterAST: cloneDeeplyExcludesParent(afterAST),
779
+ configs,
780
+ filename
865
781
  };
866
782
  }
867
783
 
@@ -950,6 +866,22 @@ class RuleTester {
950
866
  const result = runRuleForItem(item);
951
867
  const messages = result.messages;
952
868
 
869
+ for (const message of messages) {
870
+ if (hasOwnProperty(message, "suggestions")) {
871
+
872
+ /** @type {Map<string, number>} */
873
+ const seenMessageIndices = new Map();
874
+
875
+ for (let i = 0; i < message.suggestions.length; i += 1) {
876
+ const suggestionMessage = message.suggestions[i].desc;
877
+ const previous = seenMessageIndices.get(suggestionMessage);
878
+
879
+ assert.ok(!seenMessageIndices.has(suggestionMessage), `Suggestion message '${suggestionMessage}' reported from suggestion ${i} was previously reported by suggestion ${previous}. Suggestion messages should be unique within an error.`);
880
+ seenMessageIndices.set(suggestionMessage, i);
881
+ }
882
+ }
883
+ }
884
+
953
885
  if (typeof item.errors === "number") {
954
886
 
955
887
  if (item.errors === 0) {
@@ -972,7 +904,7 @@ class RuleTester {
972
904
  )
973
905
  );
974
906
 
975
- const hasMessageOfThisRule = messages.some(m => m.ruleId === ruleName);
907
+ const hasMessageOfThisRule = messages.some(m => m.ruleId === ruleId);
976
908
 
977
909
  for (let i = 0, l = item.errors.length; i < l; i++) {
978
910
  const error = item.errors[i];
@@ -1131,6 +1063,17 @@ class RuleTester {
1131
1063
  if (hasOwnProperty(expectedSuggestion, "output")) {
1132
1064
  const codeWithAppliedSuggestion = SourceCodeFixer.applyFixes(item.code, [actualSuggestion]).output;
1133
1065
 
1066
+ // Verify if suggestion fix makes a syntax error or not.
1067
+ const errorMessageInSuggestion =
1068
+ linter.verify(codeWithAppliedSuggestion, result.configs, result.filename).find(m => m.fatal);
1069
+
1070
+ assert(!errorMessageInSuggestion, [
1071
+ "A fatal parsing error occurred in suggestion fix.",
1072
+ `Error: ${errorMessageInSuggestion && errorMessageInSuggestion.message}`,
1073
+ "Suggestion output:",
1074
+ codeWithAppliedSuggestion
1075
+ ].join("\n"));
1076
+
1134
1077
  assert.strictEqual(codeWithAppliedSuggestion, expectedSuggestion.output, `Expected the applied suggestion fix to match the test suggestion output for suggestion at index: ${index} on error with message: "${message.message}"`);
1135
1078
  }
1136
1079
  });