Repository: eslint/eslint
Pull request #14677 (2021-06-06 20:54:25)

New: Add only to RuleTester (refs eslint/rfcs#73)
<!--
    Thank you for contributing!

    ESLint adheres to the [JS Foundation Code of Conduct](https://js.foundation/community/code-of-conduct).
-->

#### Prerequisites checklist

- [x] I have read the [contributing guidelines](https://github.com/eslint/eslint/blob/master/CONTRIBUTING.md).

#### What is the purpose of this pull request? (put an "X" next to an item)

<!--
    The following template is intentionally not a markdown checkbox list for the reasons
    explained in https://github.com/eslint/eslint/pull/12848#issuecomment-580302888
-->

[ ] Documentation update
[ ] Bug fix ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/bug-report.md))
[ ] New rule ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/rule-proposal.md))
[ ] Changes an existing rule ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/rule-change-proposal.md))
[ ] Add autofixing to a rule
[ ] Add a CLI option
[x] Add something to the core
[ ] Other, please explain:

<!--
    If the item you've checked above has a template, please paste the template questions below and answer them. (If this pull request is addressing an issue, you can just paste a link to the issue here instead.)
-->

<!--
    Please ensure your pull request is ready:

    - Read the pull request guide (https://eslint.org/docs/developer-guide/contributing/pull-requests)
    - Include tests for this change
    - Update documentation for this change (if appropriate)
-->

<!--
    The following is required for all pull requests:
-->

#### What changes did you make? (Give an overview)

This implements [RFC73](https://github.com/eslint/rfcs/tree/main/designs/2020-rule-tester-only). `RuleTester` test cases support a new `only` property to run an individual case or subset of cases for less noisy debugging. We do not implement test exclusivity inside `RuleTester`, instead relying on the test framework to provide that functionality when we call `only()`.

#### Is there anything you'd like reviewers to focus on?

1. I made one change from the RFC. Under [Detailed Design](https://github.com/eslint/rfcs/tree/main/designs/2020-rule-tester-only#detailed-design), step 3, I inserted a new sub-step between i and ii: "If `RuleTester[IT]` is set and has an `only` function property, return `Function.bind.call(this[IT].only, this[IT])`." For users who customize `RuleTester.it`, this could make `only` work without any additional configuration, but if their overridden `it` has an `only` function property with different semanticts than Mocha's, this step wouldn't work. Are we okay with this change?
1. Because the `RuleTester` tests override `RuleTester.it` and `RuleTester.only`, testing the `itOnly` getter was complex.


Patch:
diff --git a/Makefile.js b/Makefile.js
index 6cffb770c..62b1d7666 100644
--- a/Makefile.js
+++ b/Makefile.js
@@ -544,7 +544,7 @@ target.mocha = () => {
 
     echo("Running unit tests");
 
-    lastReturn = exec(`${getBinFile("nyc")} -- ${MOCHA} -R progress -t ${MOCHA_TIMEOUT} -c ${TEST_FILES}`);
+    lastReturn = exec(`${getBinFile("nyc")} -- ${MOCHA} --forbid-only -R progress -t ${MOCHA_TIMEOUT} -c ${TEST_FILES}`);
     if (lastReturn.code !== 0) {
         errors++;
     }
diff --git a/README.md b/README.md
index 438a087ff..2202900d1 100644
--- a/README.md
+++ b/README.md
@@ -281,9 +281,9 @@ The following companies, organizations, and individuals support ESLint's ongoing
 <!--sponsorsstart-->
 <h3>Platinum Sponsors</h3>
 <p><a href="https://automattic.com"><img src="https://images.opencollective.com/photomatt/d0ef3e1/logo.png" alt="Automattic" height="undefined"></a></p><h3>Gold Sponsors</h3>
-<p><a href="https://nx.dev"><img src="https://images.opencollective.com/nx/0efbe42/logo.png" alt="Nx (by Nrwl)" height="96"></a> <a href="https://google.com/chrome"><img src="https://images.opencollective.com/chrome/dc55bd4/logo.png" alt="Chrome's Web Framework & Tools Performance Fund" height="96"></a> <a href="https://www.salesforce.com"><img src="https://images.opencollective.com/salesforce/ca8f997/logo.png" alt="Salesforce" height="96"></a> <a href="https://www.airbnb.com/"><img src="https://images.opencollective.com/airbnb/d327d66/logo.png" alt="Airbnb" height="96"></a> <a href="https://substack.com/"><img src="https://avatars.githubusercontent.com/u/53023767?v=4" alt="Substack" height="96"></a></p><h3>Silver Sponsors</h3>
+<p><a href="https://nx.dev"><img src="https://images.opencollective.com/nx/0efbe42/logo.png" alt="Nx (by Nrwl)" height="96"></a> <a href="https://google.com/chrome"><img src="https://images.opencollective.com/chrome/dc55bd4/logo.png" alt="Chrome's Web Framework & Tools Performance Fund" height="96"></a> <a href="https://www.salesforce.com"><img src="https://images.opencollective.com/salesforce/ca8f997/logo.png" alt="Salesforce" height="96"></a> <a href="https://www.airbnb.com/"><img src="https://images.opencollective.com/airbnb/d327d66/logo.png" alt="Airbnb" height="96"></a> <a href="https://coinbase.com"><img src="https://avatars.githubusercontent.com/u/1885080?v=4" alt="Coinbase" height="96"></a> <a href="https://substack.com/"><img src="https://avatars.githubusercontent.com/u/53023767?v=4" alt="Substack" height="96"></a></p><h3>Silver Sponsors</h3>
 <p><a href="https://retool.com/"><img src="https://images.opencollective.com/retool/98ea68e/logo.png" alt="Retool" height="64"></a> <a href="https://liftoff.io/"><img src="https://images.opencollective.com/liftoff/5c4fa84/logo.png" alt="Liftoff" height="64"></a></p><h3>Bronze Sponsors</h3>
-<p><a href="https://www.crosswordsolver.org/anagram-solver/"><img src="https://images.opencollective.com/anagram-solver/2666271/logo.png" alt="Anagram Solver" height="32"></a> <a href="null"><img src="https://images.opencollective.com/bugsnag-stability-monitoring/c2cef36/logo.png" alt="Bugsnag Stability Monitoring" height="32"></a> <a href="https://mixpanel.com"><img src="https://images.opencollective.com/mixpanel/cd682f7/logo.png" alt="Mixpanel" height="32"></a> <a href="https://www.vpsserver.com"><img src="https://images.opencollective.com/vpsservercom/logo.png" alt="VPS Server" height="32"></a> <a href="https://icons8.com"><img src="https://images.opencollective.com/icons8/7fa1641/logo.png" alt="Icons8: free icons, photos, illustrations, and music" height="32"></a> <a href="https://discord.com"><img src="https://images.opencollective.com/discordapp/7e3d9a9/logo.png" alt="Discord" height="32"></a> <a href="https://themeisle.com"><img src="https://images.opencollective.com/themeisle/d5592fe/logo.png" alt="ThemeIsle" height="32"></a> <a href="https://www.firesticktricks.com"><img src="https://images.opencollective.com/fire-stick-tricks/b8fbe2c/logo.png" alt="Fire Stick Tricks" height="32"></a></p>
+<p><a href="https://www.crosswordsolver.org/anagram-solver/"><img src="https://images.opencollective.com/anagram-solver/2666271/logo.png" alt="Anagram Solver" height="32"></a> <a href="null"><img src="https://images.opencollective.com/bugsnag-stability-monitoring/c2cef36/logo.png" alt="Bugsnag Stability Monitoring" height="32"></a> <a href="https://mixpanel.com"><img src="https://images.opencollective.com/mixpanel/cd682f7/logo.png" alt="Mixpanel" height="32"></a> <a href="https://www.vpsserver.com"><img src="https://images.opencollective.com/vpsservercom/logo.png" alt="VPS Server" height="32"></a> <a href="https://icons8.com"><img src="https://images.opencollective.com/icons8/7fa1641/logo.png" alt="Icons8: free icons, photos, illustrations, and music" height="32"></a> <a href="https://discord.com"><img src="https://images.opencollective.com/discordapp/7e3d9a9/logo.png" alt="Discord" height="32"></a> <a href="https://themeisle.com"><img src="https://images.opencollective.com/themeisle/d5592fe/logo.png" alt="ThemeIsle" height="32"></a> <a href="https://www.firesticktricks.com"><img src="https://images.opencollective.com/fire-stick-tricks/b8fbe2c/logo.png" alt="Fire Stick Tricks" height="32"></a> <a href="https://www.practiceignition.com"><img src="https://avatars.githubusercontent.com/u/5753491?v=4" alt="Practice Ignition" height="32"></a></p>
 <!--sponsorsend-->
 
 ## <a name="technology-sponsors"></a>Technology Sponsors
diff --git a/docs/developer-guide/nodejs-api.md b/docs/developer-guide/nodejs-api.md
index f1e21584e..8d25c6e98 100644
--- a/docs/developer-guide/nodejs-api.md
+++ b/docs/developer-guide/nodejs-api.md
@@ -10,6 +10,7 @@ While ESLint is designed to be run on the command line, it's possible to use ESL
     * [constructor()][eslint-constructor]
     * [lintFiles()][eslint-lintfiles]
     * [lintText()][eslint-linttext]
+    * [getRulesMetaForResults()](eslint-getrulesmetaforresults)
     * [calculateConfigForFile()][eslint-calculateconfigforfile]
     * [isPathIgnored()][eslint-ispathignored]
     * [loadFormatter()][eslint-loadformatter]
@@ -205,6 +206,25 @@ The second parameter `options` is omittable.
 * (`Promise<LintResult[]>`)<br>
   The promise that will be fulfilled with an array of [LintResult] objects. This is an array (despite there being only one lint result) in order to keep the interfaces between this and the [`eslint.lintFiles()`][eslint-lintfiles] method similar.
 
+### ◆ eslint.getRulesMetaForResults(results)
+
+```js
+const results = await eslint.lintFiles(patterns);
+const rulesMeta = eslint.getRulesMetaForResults(results);
+```
+
+This method returns an object containing meta information for each rule that triggered a lint error in the given `results`.
+
+#### Parameters
+
+* `results` (`LintResult[]`)<br>
+  An array of [LintResult] objects returned from a call to `ESLint#lintFiles()` or `ESLint#lintText()`.
+
+#### Return Value
+
+* (`Object`)<br>
+  An object whose property names are the rule IDs from the `results` and whose property values are the rule's meta information (if available).
+
 ### ◆ eslint.calculateConfigForFile(filePath)
 
 ```js
@@ -1236,6 +1256,7 @@ A test case is an object with the following properties:
 * `code` (string, required): The source code that the rule should be run on
 * `options` (array, optional): The options passed to the rule. The rule severity should not be included in this list.
 * `filename` (string, optional): The filename for the given case (useful for rules that make assertions about filenames).
+* `only` (boolean, optional): Run this case exclusively for debugging in supported test frameworks.
 
 In addition to the properties above, invalid test cases can also have the following properties:
 
@@ -1335,10 +1356,13 @@ ruleTester.run("my-rule-for-no-foo", rule, {
 `RuleTester` depends on two functions to run tests: `describe` and `it`. These functions can come from various places:
 
 1. If `RuleTester.describe` and `RuleTester.it` have been set to function values, `RuleTester` will use `RuleTester.describe` and `RuleTester.it` to run tests. You can use this to customize the behavior of `RuleTester` to match a test framework that you're using.
-1. Otherwise, if `describe` and `it` are present as globals, `RuleTester` will use `global.describe` and `global.it` to run tests. This allows `RuleTester` to work when using frameworks like [Mocha](https://mochajs.org/) without any additional configuration.
-1. Otherwise, `RuleTester#run` will simply execute all of the tests in sequence, and will throw an error if one of them fails. This means you can simply execute a test file that calls `RuleTester.run` using `node`, without needing a testing framework.
 
-`RuleTester#run` calls the `describe` function with two arguments: a string describing the rule, and a callback function. The callback calls the `it` function with a string describing the test case, and a test function. The test function will return successfully if the test passes, and throw an error if the test fails. (Note that this is the standard behavior for test suites when using frameworks like [Mocha](https://mochajs.org/); this information is only relevant if you plan to customize `RuleTester.it` and `RuleTester.describe`.)
+    If `RuleTester.itOnly` has been set to a function value, `RuleTester` will call `RuleTester.itOnly` instead of `RuleTester.it` to run cases with `only: true`. If `RuleTester.itOnly` is not set but `RuleTester.it` has an `only` function property, `RuleTester` will fall back to `RuleTester.it.only`.
+
+2. Otherwise, if `describe` and `it` are present as globals, `RuleTester` will use `global.describe` and `global.it` to run tests and `global.it.only` to run cases with `only: true`. This allows `RuleTester` to work when using frameworks like [Mocha](https://mochajs.org/) without any additional configuration.
+3. Otherwise, `RuleTester#run` will simply execute all of the tests in sequence, and will throw an error if one of them fails. This means you can simply execute a test file that calls `RuleTester.run` using `Node.js`, without needing a testing framework.
+
+`RuleTester#run` calls the `describe` function with two arguments: a string describing the rule, and a callback function. The callback calls the `it` function with a string describing the test case, and a test function. The test function will return successfully if the test passes, and throw an error if the test fails. The signature for `only` is the same as `it`. `RuleTester` calls either `it` or `only` for every case even when some cases have `only: true`, and the test framework is responsible for implementing test case exclusivity. (Note that this is the standard behavior for test suites when using frameworks like [Mocha](https://mochajs.org/); this information is only relevant if you plan to customize `RuleTester.describe`, `RuleTester.it`, or `RuleTester.itOnly`.)
 
 Example of customizing `RuleTester`:
 
@@ -1389,6 +1413,7 @@ ruleTester.run("my-rule", myRule, {
 [eslint-constructor]: #-new-eslintoptions
 [eslint-lintfiles]: #-eslintlintfilespatterns
 [eslint-linttext]: #-eslintlinttextcode-options
+[eslint-getrulesmetaforresults]: #-eslintgetrulesmetaforresultsresults
 [eslint-calculateconfigforfile]: #-eslintcalculateconfigforfilefilepath
 [eslint-ispathignored]: #-eslintispathignoredfilepath
 [eslint-loadformatter]: #-eslintloadformatternameorpath
diff --git a/docs/developer-guide/working-with-rules.md b/docs/developer-guide/working-with-rules.md
index 9dadae00e..6a019aff1 100644
--- a/docs/developer-guide/working-with-rules.md
+++ b/docs/developer-guide/working-with-rules.md
@@ -66,7 +66,7 @@ The source file for a rule exports an object with the following properties.
 
     In a custom rule or plugin, you can omit `docs` or include any properties that you need in it.
 
-* `fixable` (string) is either `"code"` or `"whitespace"` if the `--fix` option on the [command line](../user-guide/command-line-interface.md#fix) automatically fixes problems reported by the rule
+* `fixable` (string) is either `"code"` or `"whitespace"` if the `--fix` option on the [command line](../user-guide/command-line-interface.md#-fix) automatically fixes problems reported by the rule
 
     **Important:** the `fixable` property is mandatory for fixable rules. If this property isn't specified, ESLint will throw an error whenever the rule attempts to produce a fix. Omit the `fixable` property if the rule is not fixable.
 
diff --git a/docs/rules/no-fallthrough.md b/docs/rules/no-fallthrough.md
index 15ca77f66..81b428cf0 100644
--- a/docs/rules/no-fallthrough.md
+++ b/docs/rules/no-fallthrough.md
@@ -54,6 +54,17 @@ switch(foo) {
     case 2:
         doSomethingElse();
 }
+
+switch(foo) {
+    case 1: {
+        doSomething();
+        // falls through
+    }
+
+    case 2: {
+        doSomethingElse();
+    }
+}
 ```
 
 In this example, there is no confusion as to the expected behavior. It is clear that the first case is meant to fall through to the second case.
@@ -124,6 +135,17 @@ switch(foo) {
     case 2:
         doSomething();
 }
+
+switch(foo) {
+    case 1: {
+        doSomething();
+        // falls through
+    }
+
+    case 2: {
+        doSomethingElse();
+    }
+}
 ```
 
 Note that the last `case` statement in these examples does not cause a warning because there is nothing to fall through into.
diff --git a/docs/user-guide/integrations.md b/docs/user-guide/integrations.md
index c7130bb9f..34ad2e25b 100644
--- a/docs/user-guide/integrations.md
+++ b/docs/user-guide/integrations.md
@@ -44,6 +44,7 @@
 * [Git Precommit Hook](https://coderwall.com/p/zq8jlq/eslint-pre-commit-hook)
 * [Git pre-commit hook that only lints staged changes](https://gist.github.com/dahjelle/8ddedf0aebd488208a9a7c829f19b9e8)
 * [overcommit Git hook manager](https://github.com/brigade/overcommit)
+* [Mega-Linter](https://nvuillam.github.io/mega-linter): Linters aggregator for CI, [embedding eslint](https://nvuillam.github.io/mega-linter/descriptors/javascript_eslint/)
 
 ## Testing
 
diff --git a/lib/eslint/eslint.js b/lib/eslint/eslint.js
index ae2d21008..056e04b59 100644
--- a/lib/eslint/eslint.js
+++ b/lib/eslint/eslint.js
@@ -514,6 +514,39 @@ class ESLint {
         return CLIEngine.getErrorResults(results);
     }
 
+    /**
+     * Returns meta objects for each rule represented in the lint results.
+     * @param {LintResult[]} results The results to fetch rules meta for.
+     * @returns {Object} A mapping of ruleIds to rule meta objects.
+     */
+    getRulesMetaForResults(results) {
+
+        const resultRuleIds = new Set();
+
+        // first gather all ruleIds from all results
+
+        for (const result of results) {
+            for (const { ruleId } of result.messages) {
+                resultRuleIds.add(ruleId);
+            }
+        }
+
+        // create a map of all rules in the results
+
+        const { cliEngine } = privateMembersMap.get(this);
+        const rules = cliEngine.getRules();
+        const resultRules = new Map();
+
+        for (const [ruleId, rule] of rules) {
+            if (resultRuleIds.has(ruleId)) {
+                resultRules.set(ruleId, rule);
+            }
+        }
+
+        return createRulesMeta(resultRules);
+
+    }
+
     /**
      * Executes the current configuration on an array of file and directory names.
      * @param {string[]} patterns An array of file and directory names.
@@ -552,9 +585,12 @@ class ESLint {
             ...unknownOptions
         } = options || {};
 
-        for (const key of Object.keys(unknownOptions)) {
-            throw new Error(`'options' must not include the unknown option '${key}'`);
+        const unknownOptionKeys = Object.keys(unknownOptions);
+
+        if (unknownOptionKeys.length > 0) {
+            throw new Error(`'options' must not include the unknown option(s): ${unknownOptionKeys.join(", ")}`);
         }
+
         if (filePath !== void 0 && !isNonEmptyString(filePath)) {
             throw new Error("'options.filePath' must be a non-empty string or undefined");
         }
diff --git a/lib/init/npm-utils.js b/lib/init/npm-utils.js
index 35191cc08..b91a824b1 100644
--- a/lib/init/npm-utils.js
+++ b/lib/init/npm-utils.js
@@ -50,8 +50,7 @@ function findPackageJson(startDir) {
  */
 function installSyncSaveDev(packages) {
     const packageList = Array.isArray(packages) ? packages : [packages];
-    const npmProcess = spawn.sync("npm", ["i", "--save-dev"].concat(packageList),
-        { stdio: "inherit" });
+    const npmProcess = spawn.sync("npm", ["i", "--save-dev"].concat(packageList), { stdio: "inherit" });
     const error = npmProcess.error;
 
     if (error && error.code === "ENOENT") {
diff --git a/lib/rules/comma-style.js b/lib/rules/comma-style.js
index f1a23d63b..fbdecccaa 100644
--- a/lib/rules/comma-style.js
+++ b/lib/rules/comma-style.js
@@ -207,8 +207,7 @@ module.exports = {
                      * they are always valid regardless of an undefined item.
                      */
                     if (astUtils.isCommaToken(commaToken)) {
-                        validateCommaItemSpacing(previousItemToken, commaToken,
-                            currentItemToken, reportItem);
+                        validateCommaItemSpacing(previousItemToken, commaToken, currentItemToken, reportItem);
                     }
 
                     if (item) {
diff --git a/lib/rules/indent.js b/lib/rules/indent.js
index b1af2a73b..04f41db9e 100644
--- a/lib/rules/indent.js
+++ b/lib/rules/indent.js
@@ -1177,8 +1177,7 @@ module.exports = {
                     offsets.setDesiredOffset(questionMarkToken, firstToken, 1);
                     offsets.setDesiredOffset(colonToken, firstToken, 1);
 
-                    offsets.setDesiredOffset(firstConsequentToken, firstToken,
-                        firstConsequentToken.type === "Punctuator" &&
+                    offsets.setDesiredOffset(firstConsequentToken, firstToken, firstConsequentToken.type === "Punctuator" &&
                         options.offsetTernaryExpressions ? 2 : 1);
 
                     /*
@@ -1204,8 +1203,7 @@ module.exports = {
                          * If `baz` were aligned with `bar` rather than being offset by 1 from `foo`, `baz` would end up
                          * having no expected indentation.
                          */
-                        offsets.setDesiredOffset(firstAlternateToken, firstToken,
-                            firstAlternateToken.type === "Punctuator" &&
+                        offsets.setDesiredOffset(firstAlternateToken, firstToken, firstAlternateToken.type === "Punctuator" &&
                             options.offsetTernaryExpressions ? 2 : 1);
                     }
                 }
diff --git a/lib/rules/no-fallthrough.js b/lib/rules/no-fallthrough.js
index e8016e93e..3b949acd1 100644
--- a/lib/rules/no-fallthrough.js
+++ b/lib/rules/no-fallthrough.js
@@ -11,15 +11,26 @@
 const DEFAULT_FALLTHROUGH_COMMENT = /falls?\s?through/iu;
 
 /**
- * Checks whether or not a given node has a fallthrough comment.
- * @param {ASTNode} node A SwitchCase node to get comments.
+ * Checks whether or not a given case has a fallthrough comment.
+ * @param {ASTNode} caseWhichFallsThrough SwitchCase node which falls through.
+ * @param {ASTNode} subsequentCase The case after caseWhichFallsThrough.
  * @param {RuleContext} context A rule context which stores comments.
  * @param {RegExp} fallthroughCommentPattern A pattern to match comment to.
- * @returns {boolean} `true` if the node has a valid fallthrough comment.
+ * @returns {boolean} `true` if the case has a valid fallthrough comment.
  */
-function hasFallthroughComment(node, context, fallthroughCommentPattern) {
+function hasFallthroughComment(caseWhichFallsThrough, subsequentCase, context, fallthroughCommentPattern) {
     const sourceCode = context.getSourceCode();
-    const comment = sourceCode.getCommentsBefore(node).pop();
+
+    if (caseWhichFallsThrough.consequent.length === 1 && caseWhichFallsThrough.consequent[0].type === "BlockStatement") {
+        const trailingCloseBrace = sourceCode.getLastToken(caseWhichFallsThrough.consequent[0]);
+        const commentInBlock = sourceCode.getCommentsBefore(trailingCloseBrace).pop();
+
+        if (commentInBlock && fallthroughCommentPattern.test(commentInBlock.value)) {
+            return true;
+        }
+    }
+
+    const comment = sourceCode.getCommentsBefore(subsequentCase).pop();
 
     return Boolean(comment && fallthroughCommentPattern.test(comment.value));
 }
@@ -108,7 +119,7 @@ module.exports = {
                  * Checks whether or not there is a fallthrough comment.
                  * And reports the previous fallthrough node if that does not exist.
                  */
-                if (fallthroughCase && !hasFallthroughComment(node, context, fallthroughCommentPattern)) {
+                if (fallthroughCase && !hasFallthroughComment(fallthroughCase, node, context, fallthroughCommentPattern)) {
                     context.report({
                         messageId: node.test ? "case" : "default",
                         node
diff --git a/packages/eslint-config-eslint/default.yml b/packages/eslint-config-eslint/default.yml
index 8a8531304..38061e801 100644
--- a/packages/eslint-config-eslint/default.yml
+++ b/packages/eslint-config-eslint/default.yml
@@ -1,3 +1,4 @@
+reportUnusedDisableDirectives: true
 extends:
     - "eslint:recommended"
     - "plugin:node/recommended"
@@ -29,6 +30,7 @@ rules:
     consistent-return: "error"
     curly: ["error", "all"]
     default-case: "error"
+    default-case-last: "error"
     default-param-last: "error"
     dot-location: ["error", "property"]
     dot-notation: ["error", { allowKeywords: true }]
@@ -36,8 +38,10 @@ rules:
     eqeqeq: "error"
     func-call-spacing: "error"
     func-style: ["error", "declaration"]
+    function-call-argument-newline: ["error", "consistent"]
     function-paren-newline: ["error", "consistent"]
     generator-star-spacing: "error"
+    grouped-accessor-pairs: "error"
     guard-for-in: "error"
     jsdoc/check-alignment: "error"
     jsdoc/check-param-names: "error"
@@ -79,6 +83,7 @@ rules:
     no-caller: "error"
     no-confusing-arrow: "error"
     no-console: "error"
+    no-constructor-return: "error"
     no-delete-var: "error"
     no-else-return: ["error", { allowElseIf: false }]
     no-eval: "error"
@@ -133,6 +138,7 @@ rules:
     no-underscore-dangle: ["error", {allowAfterThis: true}]
     no-unmodified-loop-condition: "error"
     no-unneeded-ternary: "error"
+    no-unreachable-loop: "error"
     no-unused-expressions: "error"
     no-unused-vars: ["error", {vars: "all", args: "after-used", caughtErrors: "all"}]
     no-use-before-define: "error"
@@ -174,6 +180,7 @@ rules:
     ]
     prefer-arrow-callback: "error"
     prefer-const: "error"
+    prefer-exponentiation-operator: "error"
     prefer-numeric-literals: "error"
     prefer-promise-reject-errors: "error"
     prefer-regex-literals: "error"


Test patch:
diff --git a/docs/developer-guide/unit-tests.md b/docs/developer-guide/unit-tests.md
index 281c39674..46d77b590 100644
--- a/docs/developer-guide/unit-tests.md
+++ b/docs/developer-guide/unit-tests.md
@@ -10,11 +10,29 @@ This automatically starts Mocha and runs all tests in the `tests` directory. You
 
 ## Running Individual Tests
 
-If you want to quickly run just one test, you can do so by running Mocha directly and passing in the filename. For example:
+If you want to quickly run just one test file, you can do so by running Mocha directly and passing in the filename. For example:
 
     npm run test:cli tests/lib/rules/no-wrap-func.js
 
-Running individual tests is useful when you're working on a specific bug and iterating on the solution. You should be sure to run `npm test` before submitting a pull request.
+If you want to run just one or a subset of `RuleTester` test cases, add `only: true` to each test case or wrap the test case in `RuleTester.only(...)` to add it automatically:
+
+```js
+ruleTester.run("my-rule", myRule, {
+    valid: [
+        RuleTester.only("const valid = 42;"),
+        // Other valid cases
+    ],
+    invalid: [
+        {
+            code: "const invalid = 42;",
+            only: true,
+        },
+        // Other invalid cases
+    ]
+})
+```
+
+Running individual tests is useful when you're working on a specific bug and iterating on the solution. You should be sure to run `npm test` before submitting a pull request. `npm test` uses Mocha's `--forbid-only` option to prevent `only` tests from passing full test runs.
 
 ## More Control on Unit Testing
 
diff --git a/lib/rule-tester/rule-tester.js b/lib/rule-tester/rule-tester.js
index b08303c62..cac81bc71 100644
--- a/lib/rule-tester/rule-tester.js
+++ b/lib/rule-tester/rule-tester.js
@@ -71,6 +71,7 @@ const espreePath = require.resolve("espree");
  * @property {{ [name: string]: any }} [parserOptions] Options for the parser.
  * @property {{ [name: string]: "readonly" | "writable" | "off" }} [globals] The additional global variables.
  * @property {{ [name: string]: boolean }} [env] Environments for the test case.
+ * @property {boolean} [only] Run only this test case or the subset of test cases with this property.
  */
 
 /**
@@ -86,6 +87,7 @@ const espreePath = require.resolve("espree");
  * @property {{ [name: string]: any }} [parserOptions] Options for the parser.
  * @property {{ [name: string]: "readonly" | "writable" | "off" }} [globals] The additional global variables.
  * @property {{ [name: string]: boolean }} [env] Environments for the test case.
+ * @property {boolean} [only] Run only this test case or the subset of test cases with this property.
  */
 
 /**
@@ -121,7 +123,8 @@ const RuleTesterParameters = [
     "filename",
     "options",
     "errors",
-    "output"
+    "output",
+    "only"
 ];
 
 /*
@@ -282,6 +285,7 @@ function wrapParser(parser) {
 // default separators for testing
 const DESCRIBE = Symbol("describe");
 const IT = Symbol("it");
+const IT_ONLY = Symbol("itOnly");
 
 /**
  * This is `it` default handler if `it` don't exist.
@@ -400,6 +404,46 @@ class RuleTester {
         this[IT] = value;
     }
 
+    /**
+     * Adds the `only` property to a test to run it in isolation.
+     * @param {string | ValidTestCase | InvalidTestCase} item A single test to run by itself.
+     * @returns {ValidTestCase | InvalidTestCase} The test with `only` set.
+     */
+    static only(item) {
+        if (typeof item === "string") {
+            return { code: item, only: true };
+        }
+
+        return { ...item, only: true };
+    }
+
+    static get itOnly() {
+        if (typeof this[IT_ONLY] === "function") {
+            return this[IT_ONLY];
+        }
+        if (typeof this[IT] === "function" && typeof this[IT].only === "function") {
+            return Function.bind.call(this[IT].only, this[IT]);
+        }
+        if (typeof it === "function" && typeof it.only === "function") {
+            return Function.bind.call(it.only, it);
+        }
+
+        if (typeof this[DESCRIBE] === "function" || typeof this[IT] === "function") {
+            throw new Error(
+                "Set `RuleTester.itOnly` to use `only` with a custom test framework.\n" +
+                "See https://eslint.org/docs/developer-guide/nodejs-api#customizing-ruletester for more."
+            );
+        }
+        if (typeof it === "function") {
+            throw new Error("The current test framework does not support exclusive tests with `only`.");
+        }
+        throw new Error("To use `only`, use RuleTester with a test framework that provides `it.only()` like Mocha.");
+    }
+
+    static set itOnly(value) {
+        this[IT_ONLY] = value;
+    }
+
     /**
      * Define a rule for one particular run of tests.
      * @param {string} name The name of the rule to define.
@@ -610,7 +654,8 @@ class RuleTester {
             const messages = result.messages;
 
             assert.strictEqual(messages.length, 0, util.format("Should have no errors but had %d: %s",
-                messages.length, util.inspect(messages)));
+                messages.length,
+                util.inspect(messages)));
 
             assertASTDidntChange(result.beforeAST, result.afterAST);
         }
@@ -665,13 +710,18 @@ class RuleTester {
                 }
 
                 assert.strictEqual(messages.length, item.errors, util.format("Should have %d error%s but had %d: %s",
-                    item.errors, item.errors === 1 ? "" : "s", messages.length, util.inspect(messages)));
+                    item.errors,
+                    item.errors === 1 ? "" : "s",
+                    messages.length,
+                    util.inspect(messages)));
             } else {
                 assert.strictEqual(
-                    messages.length, item.errors.length,
-                    util.format(
+                    messages.length, item.errors.length, util.format(
                         "Should have %d error%s but had %d: %s",
-                        item.errors.length, item.errors.length === 1 ? "" : "s", messages.length, util.inspect(messages)
+                        item.errors.length,
+                        item.errors.length === 1 ? "" : "s",
+                        messages.length,
+                        util.inspect(messages)
                     )
                 );
 
@@ -885,23 +935,29 @@ class RuleTester {
         RuleTester.describe(ruleName, () => {
             RuleTester.describe("valid", () => {
                 test.valid.forEach(valid => {
-                    RuleTester.it(sanitize(typeof valid === "object" ? valid.code : valid), () => {
-                        testValidTemplate(valid);
-                    });
+                    RuleTester[valid.only ? "itOnly" : "it"](
+                        sanitize(typeof valid === "object" ? valid.code : valid),
+                        () => {
+                            testValidTemplate(valid);
+                        }
+                    );
                 });
             });
 
             RuleTester.describe("invalid", () => {
                 test.invalid.forEach(invalid => {
-                    RuleTester.it(sanitize(invalid.code), () => {
-                        testInvalidTemplate(invalid);
-                    });
+                    RuleTester[invalid.only ? "itOnly" : "it"](
+                        sanitize(invalid.code),
+                        () => {
+                            testInvalidTemplate(invalid);
+                        }
+                    );
                 });
             });
         });
     }
 }
 
-RuleTester[DESCRIBE] = RuleTester[IT] = null;
+RuleTester[DESCRIBE] = RuleTester[IT] = RuleTester[IT_ONLY] = null;
 
 module.exports = RuleTester;
diff --git a/tests/lib/eslint/eslint.js b/tests/lib/eslint/eslint.js
index c7783a6c0..c5a1b7360 100644
--- a/tests/lib/eslint/eslint.js
+++ b/tests/lib/eslint/eslint.js
@@ -22,6 +22,7 @@ const shell = require("shelljs");
 const { CascadingConfigArrayFactory } = require("@eslint/eslintrc/lib/cascading-config-array-factory");
 const hash = require("../../../lib/cli-engine/hash");
 const { unIndent, createCustomTeardown } = require("../../_utils");
+const coreRules = require("../../../lib/rules");
 
 //------------------------------------------------------------------------------
 // Tests
@@ -846,7 +847,7 @@ describe("ESLint", () => {
 
         it("should throw if 'options' argument contains unknown key", async () => {
             eslint = new ESLint();
-            await assert.rejects(() => eslint.lintText("var a = 0", { filename: "foo.js" }), /'options' must not include the unknown option 'filename'/u);
+            await assert.rejects(() => eslint.lintText("var a = 0", { filename: "foo.js" }), /'options' must not include the unknown option\(s\): filename/u);
         });
 
         it("should throw if non-string value is given to 'options.filePath' option", async () => {
@@ -4790,6 +4791,80 @@ describe("ESLint", () => {
         });
     });
 
+    describe("getRulesMetaForResults()", () => {
+        it("should return empty object when there are no linting errors", async () => {
+            const engine = new ESLint({
+                useEslintrc: false
+            });
+
+            const rulesMeta = engine.getRulesMetaForResults([]);
+
+            assert.strictEqual(Object.keys(rulesMeta).length, 0);
+        });
+
+        it("should return one rule meta when there is a linting error", async () => {
+            const engine = new ESLint({
+                useEslintrc: false,
+                overrideConfig: {
+                    rules: {
+                        semi: 2
+                    }
+                }
+            });
+
+            const results = await engine.lintText("a");
+            const rulesMeta = engine.getRulesMetaForResults(results);
+
+            assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta);
+        });
+
+        it("should return multiple rule meta when there are multiple linting errors", async () => {
+            const engine = new ESLint({
+                useEslintrc: false,
+                overrideConfig: {
+                    rules: {
+                        semi: 2,
+                        quotes: [2, "double"]
+                    }
+                }
+            });
+
+            const results = await engine.lintText("'a'");
+            const rulesMeta = engine.getRulesMetaForResults(results);
+
+            assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta);
+            assert.strictEqual(rulesMeta.quotes, coreRules.get("quotes").meta);
+        });
+
+        it("should return multiple rule meta when there are multiple linting errors from a plugin", async () => {
+            const nodePlugin = require("eslint-plugin-node");
+            const engine = new ESLint({
+                useEslintrc: false,
+                plugins: {
+                    node: nodePlugin
+                },
+                overrideConfig: {
+                    plugins: ["node"],
+                    rules: {
+                        "node/no-new-require": 2,
+                        semi: 2,
+                        quotes: [2, "double"]
+                    }
+                }
+            });
+
+            const results = await engine.lintText("new require('hi')");
+            const rulesMeta = engine.getRulesMetaForResults(results);
+
+            assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta);
+            assert.strictEqual(rulesMeta.quotes, coreRules.get("quotes").meta);
+            assert.strictEqual(
+                rulesMeta["node/no-new-require"],
+                nodePlugin.rules["no-new-require"].meta
+            );
+        });
+    });
+
     describe("outputFixes()", () => {
         afterEach(() => {
             sinon.verifyAndRestore();
diff --git a/tests/lib/linter/linter.js b/tests/lib/linter/linter.js
index e61804d3a..adce5a39d 100644
--- a/tests/lib/linter/linter.js
+++ b/tests/lib/linter/linter.js
@@ -3401,7 +3401,6 @@ var a = "test2";
                     return {};
                 });
 
-                linter.defineRule("checker", filenameChecker);
                 linter.defineRule("checker", filenameChecker);
                 linter.verify("foo;", { rules: { checker: "error" } }, { filename: "foo.js" });
                 assert(filenameChecker.calledOnce);
diff --git a/tests/lib/rule-tester/rule-tester.js b/tests/lib/rule-tester/rule-tester.js
index 3aaf6f91f..70647b186 100644
--- a/tests/lib/rule-tester/rule-tester.js
+++ b/tests/lib/rule-tester/rule-tester.js
@@ -97,6 +97,266 @@ describe("RuleTester", () => {
         ruleTester = new RuleTester();
     });
 
+    describe("only", () => {
+        describe("`itOnly` accessor", () => {
+            describe("when `itOnly` is set", () => {
+                before(() => {
+                    RuleTester.itOnly = sinon.spy();
+                });
+                after(() => {
+                    RuleTester.itOnly = void 0;
+                });
+                beforeEach(() => {
+                    RuleTester.itOnly.resetHistory();
+                    ruleTester = new RuleTester();
+                });
+
+                it("is called by exclusive tests", () => {
+                    ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
+                        valid: [{
+                            code: "const notVar = 42;",
+                            only: true
+                        }],
+                        invalid: []
+                    });
+
+                    sinon.assert.calledWith(RuleTester.itOnly, "const notVar = 42;");
+                });
+            });
+
+            describe("when `it` is set and has an `only()` method", () => {
+                before(() => {
+                    RuleTester.it.only = () => {};
+                    sinon.spy(RuleTester.it, "only");
+                });
+                after(() => {
+                    RuleTester.it.only = void 0;
+                });
+                beforeEach(() => {
+                    RuleTester.it.only.resetHistory();
+                    ruleTester = new RuleTester();
+                });
+
+                it("is called by tests with `only` set", () => {
+                    ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
+                        valid: [{
+                            code: "const notVar = 42;",
+                            only: true
+                        }],
+                        invalid: []
+                    });
+
+                    sinon.assert.calledWith(RuleTester.it.only, "const notVar = 42;");
+                });
+            });
+
+            describe("when global `it` is a function that has an `only()` method", () => {
+                let originalGlobalItOnly;
+
+                before(() => {
+
+                    /*
+                     * We run tests with `--forbid-only`, so we have to override
+                     * `it.only` to prevent the real one from being called.
+                     */
+                    originalGlobalItOnly = it.only;
+                    it.only = () => {};
+                    sinon.spy(it, "only");
+                });
+                after(() => {
+                    it.only = originalGlobalItOnly;
+                });
+                beforeEach(() => {
+                    it.only.resetHistory();
+                    ruleTester = new RuleTester();
+                });
+
+                it("is called by tests with `only` set", () => {
+                    ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
+                        valid: [{
+                            code: "const notVar = 42;",
+                            only: true
+                        }],
+                        invalid: []
+                    });
+
+                    sinon.assert.calledWith(it.only, "const notVar = 42;");
+                });
+            });
+
+            describe("when `describe` and `it` are overridden without `itOnly`", () => {
+                let originalGlobalItOnly;
+
+                before(() => {
+
+                    /*
+                     * These tests override `describe` and `it` already, so we
+                     * don't need to override them here. We do, however, need to
+                     * remove `only` from the global `it` to prevent it from
+                     * being used instead.
+                     */
+                    originalGlobalItOnly = it.only;
+                    it.only = void 0;
+                });
+                after(() => {
+                    it.only = originalGlobalItOnly;
+                });
+                beforeEach(() => {
+                    ruleTester = new RuleTester();
+                });
+
+                it("throws an error recommending overriding `itOnly`", () => {
+                    assert.throws(() => {
+                        ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
+                            valid: [{
+                                code: "const notVar = 42;",
+                                only: true
+                            }],
+                            invalid: []
+                        });
+                    }, "Set `RuleTester.itOnly` to use `only` with a custom test framework.");
+                });
+            });
+
+            describe("when global `it` is a function that does not have an `only()` method", () => {
+                let originalGlobalIt;
+                let originalRuleTesterDescribe;
+                let originalRuleTesterIt;
+
+                before(() => {
+                    originalGlobalIt = global.it;
+
+                    // eslint-disable-next-line no-global-assign
+                    it = () => {};
+
+                    /*
+                     * These tests override `describe` and `it`, so we need to
+                     * un-override them here so they won't interfere.
+                     */
+                    originalRuleTesterDescribe = RuleTester.describe;
+                    RuleTester.describe = void 0;
+                    originalRuleTesterIt = RuleTester.it;
+                    RuleTester.it = void 0;
+                });
+                after(() => {
+
+                    // eslint-disable-next-line no-global-assign
+                    it = originalGlobalIt;
+                    RuleTester.describe = originalRuleTesterDescribe;
+                    RuleTester.it = originalRuleTesterIt;
+                });
+                beforeEach(() => {
+                    ruleTester = new RuleTester();
+                });
+
+                it("throws an error explaining that the current test framework does not support `only`", () => {
+                    assert.throws(() => {
+                        ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
+                            valid: [{
+                                code: "const notVar = 42;",
+                                only: true
+                            }],
+                            invalid: []
+                        });
+                    }, "The current test framework does not support exclusive tests with `only`.");
+                });
+            });
+        });
+
+        describe("test cases", () => {
+            const ruleName = "no-var";
+            const rule = require("../../fixtures/testers/rule-tester/no-var");
+
+            let originalRuleTesterIt;
+            let spyRuleTesterIt;
+            let originalRuleTesterItOnly;
+            let spyRuleTesterItOnly;
+
+            before(() => {
+                originalRuleTesterIt = RuleTester.it;
+                spyRuleTesterIt = sinon.spy();
+                RuleTester.it = spyRuleTesterIt;
+                originalRuleTesterItOnly = RuleTester.itOnly;
+                spyRuleTesterItOnly = sinon.spy();
+                RuleTester.itOnly = spyRuleTesterItOnly;
+            });
+            after(() => {
+                RuleTester.it = originalRuleTesterIt;
+                RuleTester.itOnly = originalRuleTesterItOnly;
+            });
+            beforeEach(() => {
+                spyRuleTesterIt.resetHistory();
+                spyRuleTesterItOnly.resetHistory();
+                ruleTester = new RuleTester();
+            });
+
+            it("isn't called for normal tests", () => {
+                ruleTester.run(ruleName, rule, {
+                    valid: ["const notVar = 42;"],
+                    invalid: []
+                });
+                sinon.assert.calledWith(spyRuleTesterIt, "const notVar = 42;");
+                sinon.assert.notCalled(spyRuleTesterItOnly);
+            });
+
+            it("calls it or itOnly for every test case", () => {
+
+                /*
+                 * `RuleTester` doesn't implement test case exclusivity itself.
+                 * Setting `only: true` just causes `RuleTester` to call
+                 * whatever `only()` function is provided by the test framework
+                 * instead of the regular `it()` function.
+                 */
+
+                ruleTester.run(ruleName, rule, {
+                    valid: [
+                        "const valid = 42;",
+                        {
+                            code: "const onlyValid = 42;",
+                            only: true
+                        }
+                    ],
+                    invalid: [
+                        {
+                            code: "var invalid = 42;",
+                            errors: [/^Bad var/u]
+                        },
+                        {
+                            code: "var onlyInvalid = 42;",
+                            errors: [/^Bad var/u],
+                            only: true
+                        }
+                    ]
+                });
+
+                sinon.assert.calledWith(spyRuleTesterIt, "const valid = 42;");
+                sinon.assert.calledWith(spyRuleTesterItOnly, "const onlyValid = 42;");
+                sinon.assert.calledWith(spyRuleTesterIt, "var invalid = 42;");
+                sinon.assert.calledWith(spyRuleTesterItOnly, "var onlyInvalid = 42;");
+            });
+        });
+
+        describe("static helper wrapper", () => {
+            it("adds `only` to string test cases", () => {
+                const test = RuleTester.only("const valid = 42;");
+
+                assert.deepStrictEqual(test, {
+                    code: "const valid = 42;",
+                    only: true
+                });
+            });
+
+            it("adds `only` to object test cases", () => {
+                const test = RuleTester.only({ code: "const valid = 42;" });
+
+                assert.deepStrictEqual(test, {
+                    code: "const valid = 42;",
+                    only: true
+                });
+            });
+        });
+    });
+
     it("should not throw an error when everything passes", () => {
         ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
             valid: [
diff --git a/tests/lib/rules/no-fallthrough.js b/tests/lib/rules/no-fallthrough.js
index e154c31d5..0b98d3293 100644
--- a/tests/lib/rules/no-fallthrough.js
+++ b/tests/lib/rules/no-fallthrough.js
@@ -30,6 +30,14 @@ ruleTester.run("no-fallthrough", rule, {
         "switch(foo) { case 0: a(); /* fall through */ case 1: b(); }",
         "switch(foo) { case 0: a(); /* fallthrough */ case 1: b(); }",
         "switch(foo) { case 0: a(); /* FALLS THROUGH */ case 1: b(); }",
+        "switch(foo) { case 0: { a(); /* falls through */ } case 1: b(); }",
+        "switch(foo) { case 0: { a()\n /* falls through */ } case 1: b(); }",
+        "switch(foo) { case 0: { a(); /* fall through */ } case 1: b(); }",
+        "switch(foo) { case 0: { a(); /* fallthrough */ } case 1: b(); }",
+        "switch(foo) { case 0: { a(); /* FALLS THROUGH */ } case 1: b(); }",
+        "switch(foo) { case 0: { a(); } /* falls through */ case 1: b(); }",
+        "switch(foo) { case 0: { a(); /* falls through */ } /* comment */ case 1: b(); }",
+        "switch(foo) { case 0: { /* falls through */ } case 1: b(); }",
         "function foo() { switch(foo) { case 0: a(); return; case 1: b(); }; }",
         "switch(foo) { case 0: a(); throw 'foo'; case 1: b(); }",
         "while (a) { switch(foo) { case 0: a(); continue; case 1: b(); } }",
@@ -133,6 +141,30 @@ ruleTester.run("no-fallthrough", rule, {
             code: "switch(foo) { case 0:\n\n default: b() }",
             errors: errorsDefault
         },
+        {
+            code: "switch(foo) { case 0: {} default: b() }",
+            errors: errorsDefault
+        },
+        {
+            code: "switch(foo) { case 0: a(); { /* falls through */ } default: b() }",
+            errors: errorsDefault
+        },
+        {
+            code: "switch(foo) { case 0: { /* falls through */ } a(); default: b() }",
+            errors: errorsDefault
+        },
+        {
+            code: "switch(foo) { case 0: if (a) { /* falls through */ } default: b() }",
+            errors: errorsDefault
+        },
+        {
+            code: "switch(foo) { case 0: { { /* falls through */ } } default: b() }",
+            errors: errorsDefault
+        },
+        {
+            code: "switch(foo) { case 0: { /* comment */ } default: b() }",
+            errors: errorsDefault
+        },
         {
             code: "switch(foo) { case 0:\n // comment\n default: b() }",
             errors: errorsDefault
@@ -168,6 +200,20 @@ ruleTester.run("no-fallthrough", rule, {
                     column: 1
                 }
             ]
+        },
+        {
+            code: "switch(foo) { case 0: { a();\n/* no break */\n/* todo: fix readability */ }\ndefault: b() }",
+            options: [{
+                commentPattern: "no break"
+            }],
+            errors: [
+                {
+                    messageId: "default",
+                    type: "SwitchCase",
+                    line: 4,
+                    column: 1
+                }
+            ]
         }
     ]
 });