Repository: eslint/eslint
Pull request #14719 (2021-06-18 02:35:40)

Chore: remove duplicate code
<!--
    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
[ ] Add something to the core
[X] Other, please explain: refactoring

<!--
    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)

Remove a duplicate line of code.

#### Is there anything you'd like reviewers to focus on?
No


Patch:
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/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/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
+                }
+            ]
         }
     ]
 });