Repository: eslint/eslint
Pull request #14716 (2021-06-16 17:18:43)

New: Add ESLint#getRulesMetaForResults() (refs #13654)
<!--
    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)

Added the `ESLint#getRulesMetaForResults()` method. This method was proposed as `ESLint#getRulesMetaForReport()` but that wording was incorrect in the [RFC](https://github.com/eslint/rfcs/tree/main/designs/2021-package-exports) because it's `CLIEngine` that return a report while `ESLint` returns an array of results, so I updated the name to match that reality., 

I chose to return an object so that the `rulesMeta` passed to formatters is the same format rather than returning a map.

This part of the RFC is non-breaking, which is why I chose to create a separate PR for it.

#### Is there anything you'd like reviewers to focus on?

Does this implementation match everyone's expectations?


Patch:
diff --git a/docs/developer-guide/nodejs-api.md b/docs/developer-guide/nodejs-api.md
index f1e21584e..28b0fff90 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
@@ -1389,6 +1409,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/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/configuring/language-options.md b/docs/user-guide/configuring/language-options.md
index 08b62aad5..eb3fe8a0a 100644
--- a/docs/user-guide/configuring/language-options.md
+++ b/docs/user-guide/configuring/language-options.md
@@ -187,7 +187,7 @@ For ES6 syntax, use `{ "parserOptions": { "ecmaVersion": 6 } }`; for new ES6 glo
 
 Parser options are set in your `.eslintrc.*` file by using the `parserOptions` property. The available options are:
 
-* `ecmaVersion` - set to 3, 5 (default), 6, 7, 8, 9, 10, 11, or 12 to specify the version of ECMAScript syntax you want to use. You can also set to 2015 (same as 6), 2016 (same as 7), 2017 (same as 8), 2018 (same as 9), 2019 (same as 10), 2020 (same as 11), or 2021 (same as 12) to use the year-based naming. You can also set "latest" to use the most recently supported version.
+* `ecmaVersion` - set to 3, 5 (default), 6, 7, 8, 9, 10, 11, or 12 to specify the version of ECMAScript syntax you want to use. You can also set to 2015 (same as 6), 2016 (same as 7), 2017 (same as 8), 2018 (same as 9), 2019 (same as 10), 2020 (same as 11), or 2021 (same as 12) to use the year-based naming.
 * `sourceType` - set to `"script"` (default) or `"module"` if your code is in ECMAScript modules.
 * `ecmaFeatures` - an object indicating which additional language features you'd like to use:
     * `globalReturn` - allow `return` statements in the global scope
@@ -199,7 +199,7 @@ Here's an example `.eslintrc.json` file:
 ```json
 {
     "parserOptions": {
-        "ecmaVersion": "latest",
+        "ecmaVersion": 6,
         "sourceType": "module",
         "ecmaFeatures": {
             "jsx": true
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 c387ca72c..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.
diff --git a/lib/linter/linter.js b/lib/linter/linter.js
index 77682ea95..e94b507b5 100644
--- a/lib/linter/linter.js
+++ b/lib/linter/linter.js
@@ -37,7 +37,6 @@ const
 const debug = require("debug")("eslint:linter");
 const MAX_AUTOFIX_PASSES = 10;
 const DEFAULT_PARSER_NAME = "espree";
-const DEFAULT_ECMA_VERSION = 5;
 const commentParser = new ConfigCommentParser();
 const DEFAULT_ERROR_LOC = { start: { line: 1, column: 0 }, end: { line: 1, column: 1 } };
 
@@ -433,20 +432,10 @@ function getDirectiveComments(filename, ast, ruleMapper, warnInlineConfig) {
 
 /**
  * Normalize ECMAScript version from the initial config
- * @param {Parser} parser The parser which uses this options.
- * @param {number} ecmaVersion ECMAScript version from the initial config
+ * @param  {number} ecmaVersion ECMAScript version from the initial config
  * @returns {number} normalized ECMAScript version
  */
-function normalizeEcmaVersion(parser, ecmaVersion) {
-    if (parser === espree) {
-        if (ecmaVersion === void 0) {
-            return DEFAULT_ECMA_VERSION;
-        }
-
-        if (ecmaVersion === "latest") {
-            return espree.latestEcmaVersion;
-        }
-    }
+function normalizeEcmaVersion(ecmaVersion) {
 
     /*
      * Calculate ECMAScript edition number from official year version starting with
@@ -532,13 +521,12 @@ function normalizeVerifyOptions(providedOptions, config) {
 
 /**
  * Combines the provided parserOptions with the options from environments
- * @param {Parser} parser The parser which uses this options.
+ * @param {string} parserName The parser name which uses this options.
  * @param {ParserOptions} providedOptions The provided 'parserOptions' key in a config
  * @param {Environment[]} enabledEnvironments The environments enabled in configuration and with inline comments
  * @returns {ParserOptions} Resulting parser options after merge
  */
-function resolveParserOptions(parser, providedOptions, enabledEnvironments) {
-
+function resolveParserOptions(parserName, providedOptions, enabledEnvironments) {
     const parserOptionsFromEnv = enabledEnvironments
         .filter(env => env.parserOptions)
         .reduce((parserOptions, env) => merge(parserOptions, env.parserOptions), {});
@@ -554,7 +542,12 @@ function resolveParserOptions(parser, providedOptions, enabledEnvironments) {
         mergedParserOptions.ecmaFeatures = Object.assign({}, mergedParserOptions.ecmaFeatures, { globalReturn: false });
     }
 
-    mergedParserOptions.ecmaVersion = normalizeEcmaVersion(parser, mergedParserOptions.ecmaVersion);
+    /*
+     * TODO: @aladdin-add
+     * 1. for a 3rd-party parser, do not normalize parserOptions
+     * 2. for espree, no need to do this (espree will do it)
+     */
+    mergedParserOptions.ecmaVersion = normalizeEcmaVersion(mergedParserOptions.ecmaVersion);
 
     return mergedParserOptions;
 }
@@ -613,7 +606,7 @@ function getRuleOptions(ruleConfig) {
  */
 function analyzeScope(ast, parserOptions, visitorKeys) {
     const ecmaFeatures = parserOptions.ecmaFeatures || {};
-    const ecmaVersion = parserOptions.ecmaVersion || DEFAULT_ECMA_VERSION;
+    const ecmaVersion = parserOptions.ecmaVersion || 5;
 
     return eslintScope.analyze(ast, {
         ignoreEval: true,
@@ -1130,7 +1123,7 @@ class Linter {
             .map(envName => getEnv(slots, envName))
             .filter(env => env);
 
-        const parserOptions = resolveParserOptions(parser, config.parserOptions || {}, enabledEnvs);
+        const parserOptions = resolveParserOptions(parserName, config.parserOptions || {}, enabledEnvs);
         const configuredGlobals = resolveGlobals(config.globals || {}, enabledEnvs);
         const settings = config.settings || {};
 
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


Test patch:
diff --git a/tests/lib/eslint/eslint.js b/tests/lib/eslint/eslint.js
index 0845d6970..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
@@ -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 15d310c9a..adce5a39d 100644
--- a/tests/lib/linter/linter.js
+++ b/tests/lib/linter/linter.js
@@ -11,7 +11,6 @@
 
 const assert = require("chai").assert,
     sinon = require("sinon"),
-    espree = require("espree"),
     esprima = require("esprima"),
     testParsers = require("../../fixtures/parsers/linter-test-parsers");
 
@@ -3402,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);
@@ -3494,70 +3492,6 @@ var a = "test2";
         });
 
         describe("ecmaVersion", () => {
-
-            it("should not support ES6 when no ecmaVersion provided", () => {
-                const messages = linter.verify("let x = 0;");
-
-                assert.strictEqual(messages.length, 1);
-            });
-
-            it("the default ECMAScript version is 5", () => {
-                let ecmaVersion = null;
-                const config = { rules: { "ecma-version": 2 } };
-
-                linter.defineRule("ecma-version", context => ({
-                    Program() {
-                        ecmaVersion = context.parserOptions.ecmaVersion;
-                    }
-                }));
-                linter.verify("", config);
-                assert.strictEqual(ecmaVersion, 5);
-            });
-
-            it("supports ECMAScript version 'latest'", () => {
-                const messages = linter.verify("let x = 5 ** 7;", {
-                    parserOptions: { ecmaVersion: "latest" }
-                });
-
-                assert.strictEqual(messages.length, 0);
-            });
-
-            it("the 'latest' is equal to espree.lastEcmaVersion", () => {
-                let ecmaVersion = null;
-                const config = { rules: { "ecma-version": 2 }, parserOptions: { ecmaVersion: "latest" } };
-
-                linter.defineRule("ecma-version", context => ({
-                    Program() {
-                        ecmaVersion = context.parserOptions.ecmaVersion;
-                    }
-                }));
-                linter.verify("", config);
-                assert.strictEqual(ecmaVersion, espree.latestEcmaVersion);
-            });
-
-            it("should pass normalized ecmaVersion to eslint-scope", () => {
-                let blockScope = null;
-
-                linter.defineRule("block-scope", context => ({
-                    BlockStatement() {
-                        blockScope = context.getScope();
-                    }
-                }));
-
-                linter.verify("{}", {
-                    rules: { "block-scope": 2 },
-                    parserOptions: { ecmaVersion: "latest" }
-                });
-
-                assert.strictEqual(blockScope.type, "block");
-
-                linter.verify("{}", {
-                    rules: { "block-scope": 2 },
-                    parserOptions: {} // ecmaVersion defaults to 5
-                });
-                assert.strictEqual(blockScope.type, "global");
-            });
-
             describe("it should properly parse let declaration when", () => {
                 it("the ECMAScript version number is 6", () => {
                     const messages = linter.verify("let x = 5;", {
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
+                }
+            ]
         }
     ]
 });