Repository: eslint/eslint (js)

Issue:
Proposed Rule: Enforce lines between class methods
I would like to propose a rule that enforces empty lines between class functions. When enabled and set to "always" it should make sure that there is at least one empty line between every method in a class declaration. If set to "never" the opposite would be true, that is, there should not be any empty lines at all between class methods.

A line with a comment on should not count as an empty line.

This rule should not concern itself with the class padding (empty lines before the first method and after the last method) since that is already taken care of by `padded-blocks`.

It also shouldn't concern itself with how many empty lines that are present, since that is a job for `no-multiple-empty-lines`.

This is a stylistic rule.

The following patterns would be considered problems:

``` javascript
class Test1 {
  constructor () {
    // ...
  }
  otherFunc () {
    // ...
  }
}

class Test2 {
  constructor () {
    // ...
  }
  // comment
  otherFunc () {
    // ...
  }
}
```

The following pattern would be considered valid:

``` javascript
class Test3 {
  constructor () {
    // ...
  }

  otherFunc () {
    // ...
  }
}

class Test4 {
  constructor () {
    // ...
  }



  otherFunc () {
    // ...
  }
}

class Test5 {
  constructor () {
    // ...
  }

  // comment
  otherFunc () {
    // ...
  }
}

class Test6 {
  constructor () {
    // ...
  }

  // Comment

  otherFunc () {
    // ...
  }
}
```


Pull request description:
New: rule lines-between-class-members(fixes #5949).
<!--
    ESLint adheres to the [JS Foundation Code of Conduct](https://js.foundation/community/code-of-conduct).
-->

**What is the purpose of this pull request? (put an "X" next to item)**

[x] New rule ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/rule-proposal.md))


<!--
    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 (http://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)**
fixes ##5949

**Is there anything you'd like reviewers to focus on?**




Patch:
diff --git a/Makefile.js b/Makefile.js
index ded744d63..88672067e 100644
--- a/Makefile.js
+++ b/Makefile.js
@@ -73,7 +73,7 @@ const NODE = "node ", // intentional extra space
 
     // Utilities - intentional extra space at the end of each string
     MOCHA = `${NODE_MODULES}mocha/bin/_mocha `,
-    ESLINT = `${NODE} bin/eslint.js --rulesdir lib/internal-rules/ `,
+    ESLINT = `${NODE} bin/eslint.js --rulesdir lib/internal-rules/ --report-unused-disable-directives `,
 
     // Files
     MAKEFILE = "./Makefile.js",
diff --git a/conf/eslint-recommended.js b/conf/eslint-recommended.js
index a6fc9adf5..5d7f4d33c 100755
--- a/conf/eslint-recommended.js
+++ b/conf/eslint-recommended.js
@@ -63,6 +63,7 @@ module.exports = {
         "linebreak-style": "off",
         "lines-around-comment": "off",
         "lines-around-directive": "off",
+        "lines-between-class-members": "off",
         "max-depth": "off",
         "max-len": "off",
         "max-lines": "off",
diff --git a/docs/rules/lines-between-class-members.md b/docs/rules/lines-between-class-members.md
new file mode 100644
index 000000000..6beeae6ea
--- /dev/null
+++ b/docs/rules/lines-between-class-members.md
@@ -0,0 +1,107 @@
+# require or disallow an empty line between class members (lines-between-class-members)
+
+This rule improves readability by enforcing lines between class members. It will not check empty lines before the first member and after the last member, since that is already taken care of by padded-blocks.
+
+## Rule Details
+
+Examples of **incorrect** code for this rule:
+
+```js
+/* eslint lines-between-class-members: ["error", "always"]*/
+class MyClass {
+  foo() {
+    //...
+  }
+  bar() {
+    //...
+  }
+}
+```
+
+Examples of **correct** code for this rule:
+
+```js
+/* eslint lines-between-class-members: ["error", "always"]*/
+class MyClass {
+  foo() {
+    //...
+  }
+
+  bar() {
+    //...
+  }
+}
+```
+
+### Options
+
+This rule has a string option and an object option.
+
+String option:
+
+* `"always"`(default) require an empty line after after class members
+* `"never"` disallows an empty line after after class members
+
+Object option:
+
+* `"exceptAfterSingleLine": "false"`(default) **do not** skip checking empty lines after singleline class members
+* `"exceptAfterSingleLine": "true"` skip checking empty lines after singleline class members
+
+Examples of **incorrect** code for this rule with the string option:
+
+```js
+/* eslint lines-between-class-members: ["error", "always"]*/
+class Foo{
+  bar(){}
+  baz(){}
+}
+
+/* eslint lines-between-class-members: ["error", "never"]*/
+class Foo{
+  bar(){}
+
+  baz(){}
+}
+```
+
+Examples of **correct** code for this rule with the string option:
+
+```js
+/* eslint lines-between-class-members: ["error", "always"]*/
+class Foo{
+  bar(){}
+
+  baz(){}
+}
+
+/* eslint lines-between-class-members: ["error", "never"]*/
+class Foo{
+  bar(){}
+  baz(){}
+}
+```
+
+Examples of **correct** code for this rule with the object option:
+
+```js
+/* eslint lines-between-class-members: ["error", "always", { exceptAfterSingleLine: true }]*/
+class Foo{
+  bar(){} // single line class member
+  baz(){
+    // multi line class member
+  }
+
+  qux(){}
+}
+```
+
+## When Not To Use It
+
+If you don't want to enforce empty lines between class members, you can disable this rule.
+
+## Related Rules
+
+* [padded-blocks](padded-blocks.md)
+* [padding-line-between-statement](padding-line-between-statement.md)
+* [requirePaddingNewLinesAfterBlocks](http://jscs.info/rule/requirePaddingNewLinesAfterBlocks)
+* [disallowPaddingNewLinesAfterBlocks](http://jscs.info/rule/disallowPaddingNewLinesAfterBlocks)
diff --git a/docs/rules/no-else-return.md b/docs/rules/no-else-return.md
index 3127e0f51..eb6f8fab3 100644
--- a/docs/rules/no-else-return.md
+++ b/docs/rules/no-else-return.md
@@ -16,6 +16,23 @@ function foo() {
 
 This rule is aimed at highlighting an unnecessary block of code following an `if` containing a return statement. As such, it will warn when it encounters an `else` following a chain of `if`s, all of them containing a `return` statement.
 
+## Options
+
+This rule has an object option:
+
+```json
+{
+    "no-else-return": ["error", { "allowElseIf": true }],
+    // or
+    "no-else-return": ["error", { "allowElseIf": false }]
+}
+```
+
+* `allowElseIf: true` (default) allows `else if` blocks after a return
+* `allowElseIf: false` disallows `else if` blocks after a return
+
+### `allowElseIf: true`
+
 Examples of **incorrect** code for this rule:
 
 ```js
@@ -49,6 +66,16 @@ function foo() {
     return t;
 }
 
+function foo() {
+    if (error) {
+        return 'It failed';
+    } else {
+        if (loading) {
+            return "It's still loading";
+        }
+    }
+}
+
 // Two warnings for nested occurrences
 function foo() {
     if (x) {
@@ -95,4 +122,44 @@ function foo() {
         return z;
     }
 }
+
+function foo() {
+    if (error) {
+        return 'It failed';
+    } else if (loading) {
+        return "It's still loading";
+    }
+}
+```
+
+### `allowElseIf: false`
+
+Examples of **incorrect** code for this rule:
+
+```js
+/*eslint no-else-return: ["error", {allowElseIf: false}]*/
+
+function foo() {
+    if (error) {
+        return 'It failed';
+    } else if (loading) {
+        return "It's still loading";
+    }
+}
+```
+
+Examples of **correct** code for this rule:
+
+```js
+/*eslint no-else-return: ["error", {allowElseIf: false}]*/
+
+function foo() {
+    if (error) {
+        return 'It failed';
+    }
+
+    if (loading) {
+        return "It's still loading";
+    }
+}
 ```
diff --git a/docs/rules/padded-blocks.md b/docs/rules/padded-blocks.md
index 1ea3b1199..dd2c68ccb 100644
--- a/docs/rules/padded-blocks.md
+++ b/docs/rules/padded-blocks.md
@@ -354,3 +354,8 @@ if (a) {
 ## When Not To Use It
 
 You can turn this rule off if you are not concerned with the consistency of padding within blocks.
+
+## Related Rules
+
+* [lines-between-class-members](lines-between-class-members.md)
+* [padding-line-between-statement](padding-line-between-statement.md)
diff --git a/docs/user-guide/command-line-interface.md b/docs/user-guide/command-line-interface.md
index 18471375d..3a7e17650 100644
--- a/docs/user-guide/command-line-interface.md
+++ b/docs/user-guide/command-line-interface.md
@@ -69,6 +69,7 @@ Output:
 Miscellaneous:
   --init                         Run config initialization wizard - default: false
   --fix                          Automatically fix problems
+  --fix-dry-run                  Automatically fix problems without saving the changes to the file system
   --debug                        Output debugging information
   -h, --help                     Show help
   -v, --version                  Output the version number
@@ -362,6 +363,20 @@ This option instructs ESLint to try to fix as many issues as possible. The fixes
 1. This option throws an error when code is piped to ESLint.
 1. This option has no effect on code that uses a processor, unless the processor opts into allowing autofixes.
 
+If you want to fix code from `stdin` or otherwise want to get the fixes without actually writing them to the file, use the [`--fix-dry-run`](#--fix-dry-run) option.
+
+#### `--fix-dry-run`
+
+This option has the same effect as `--fix` with one difference: the fixes are not saved to the file system. This makes it possible to fix code from `stdin` (when used with the `--stdin` flag).
+
+Because the default formatter does not output the fixed code, you'll have to use another one (e.g. `json`) to get the fixes. Here's an example of this pattern:
+
+```
+getSomeText | eslint --stdin --fix-dry-run --format=json
+```
+
+This flag can be useful for integrations (e.g. editor plugins) which need to autofix text from the command line without saving it to the filesystem.
+
 #### `--debug`
 
 This option outputs debugging information to the console. This information is useful when you're seeing a problem and having a hard time pinpointing it. The ESLint team may ask for this debugging information to help solve bugs.
diff --git a/lib/ast-utils.js b/lib/ast-utils.js
index 47cc71990..fc0471e9a 100644
--- a/lib/ast-utils.js
+++ b/lib/ast-utils.js
@@ -1041,7 +1041,8 @@ module.exports = {
         } else if (parent.type === "Property" || parent.type === "MethodDefinition") {
             if (parent.kind === "constructor") {
                 return "constructor";
-            } else if (parent.kind === "get") {
+            }
+            if (parent.kind === "get") {
                 tokens.push("getter");
             } else if (parent.kind === "set") {
                 tokens.push("setter");
diff --git a/lib/cli.js b/lib/cli.js
index 0c8a7d62f..f04448097 100644
--- a/lib/cli.js
+++ b/lib/cli.js
@@ -63,7 +63,7 @@ function translateOptions(cliOptions) {
         cache: cliOptions.cache,
         cacheFile: cliOptions.cacheFile,
         cacheLocation: cliOptions.cacheLocation,
-        fix: cliOptions.fix && (cliOptions.quiet ? quietFixPredicate : true),
+        fix: (cliOptions.fix || cliOptions.fixDryRun) && (cliOptions.quiet ? quietFixPredicate : true),
         allowInlineConfig: cliOptions.inlineConfig,
         reportUnusedDisableDirectives: cliOptions.reportUnusedDisableDirectives
     };
@@ -152,7 +152,8 @@ const cli = {
             if (files.length) {
                 log.error("The --print-config option must be used with exactly one file name.");
                 return 1;
-            } else if (text) {
+            }
+            if (text) {
                 log.error("The --print-config option is not available for piped-in code.");
                 return 1;
             }
@@ -171,9 +172,13 @@ const cli = {
 
             debug(`Running on ${text ? "text" : "files"}`);
 
-            // disable --fix for piped-in code until we know how to do it correctly
+            if (currentOptions.fix && currentOptions.fixDryRun) {
+                log.error("The --fix option and the --fix-dry-run option cannot be used together.");
+                return 1;
+            }
+
             if (text && currentOptions.fix) {
-                log.error("The --fix option is not available for piped-in code.");
+                log.error("The --fix option is not available for piped-in code; use --fix-dry-run instead.");
                 return 1;
             }
 
diff --git a/lib/formatters/html.js b/lib/formatters/html.js
index e61fdea6a..d450f9dee 100644
--- a/lib/formatters/html.js
+++ b/lib/formatters/html.js
@@ -51,7 +51,8 @@ function renderSummary(totalErrors, totalWarnings) {
 function renderColor(totalErrors, totalWarnings) {
     if (totalErrors !== 0) {
         return 2;
-    } else if (totalWarnings !== 0) {
+    }
+    if (totalWarnings !== 0) {
         return 1;
     }
     return 0;
diff --git a/lib/options.js b/lib/options.js
index 3bc45b3ac..ee1d3369c 100644
--- a/lib/options.js
+++ b/lib/options.js
@@ -190,6 +190,12 @@ module.exports = optionator({
             default: false,
             description: "Automatically fix problems"
         },
+        {
+            option: "fix-dry-run",
+            type: "Boolean",
+            default: false,
+            description: "Automatically fix problems without saving the changes to the file system"
+        },
         {
             option: "debug",
             type: "Boolean",
diff --git a/lib/rules/callback-return.js b/lib/rules/callback-return.js
index 08600c01e..0fa7f278a 100644
--- a/lib/rules/callback-return.js
+++ b/lib/rules/callback-return.js
@@ -60,7 +60,8 @@ module.exports = {
             if (node.type === "MemberExpression") {
                 if (node.object.type === "Identifier") {
                     return true;
-                } else if (node.object.type === "MemberExpression") {
+                }
+                if (node.object.type === "MemberExpression") {
                     return containsOnlyIdentifiers(node.object);
                 }
             }
diff --git a/lib/rules/capitalized-comments.js b/lib/rules/capitalized-comments.js
index bb5e5825a..1a2760806 100644
--- a/lib/rules/capitalized-comments.js
+++ b/lib/rules/capitalized-comments.js
@@ -247,7 +247,8 @@ module.exports = {
 
             if (capitalize === "always" && isLowercase) {
                 return false;
-            } else if (capitalize === "never" && isUppercase) {
+            }
+            if (capitalize === "never" && isUppercase) {
                 return false;
             }
 
diff --git a/lib/rules/lines-between-class-members.js b/lib/rules/lines-between-class-members.js
new file mode 100644
index 000000000..85e8c6935
--- /dev/null
+++ b/lib/rules/lines-between-class-members.js
@@ -0,0 +1,91 @@
+/**
+ * @fileoverview Rule to check empty newline between class members
+ * @author 薛定谔的猫<hh_2013@foxmail.com>
+ */
+"use strict";
+
+const astUtils = require("../ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = {
+    meta: {
+        docs: {
+            description: "require or disallow an empty line between class members",
+            category: "Stylistic Issues",
+            recommended: false
+        },
+
+        fixable: "whitespace",
+
+        schema: [
+            {
+                enum: ["always", "never"]
+            },
+            {
+                type: "object",
+                properties: {
+                    exceptAfterSingleLine: {
+                        type: "boolean"
+                    }
+                },
+                additionalProperties: false
+            }
+        ]
+    },
+
+    create(context) {
+
+        const options = [];
+
+        options[0] = context.options[0] || "always";
+        options[1] = context.options[1] || { exceptAfterSingleLine: false };
+
+        const ALWAYS_MESSAGE = "Expected blank line between class members.";
+        const NEVER_MESSAGE = "Unexpected blank line between class members.";
+
+        const sourceCode = context.getSourceCode();
+
+        /**
+         * Checks if there is padding between two tokens
+         * @param {Token} first The first token
+         * @param {Token} second The second token
+         * @returns {boolean} True if there is at least a line between the tokens
+         */
+        function isPaddingBetweenTokens(first, second) {
+            return second.loc.start.line - first.loc.end.line >= 2;
+        }
+
+        return {
+            ClassBody(node) {
+                const body = node.body;
+
+                for (let i = 0; i < body.length - 1; i++) {
+                    const curFirst = sourceCode.getFirstToken(body[i]);
+                    const curLast = sourceCode.getLastToken(body[i]);
+                    const comments = sourceCode.getCommentsBefore(body[i + 1]);
+                    const nextFirst = comments.length ? comments[0] : sourceCode.getFirstToken(body[i + 1]);
+                    const isPadded = isPaddingBetweenTokens(curLast, nextFirst);
+                    const isMulti = !astUtils.isTokenOnSameLine(curFirst, curLast);
+                    const skip = !isMulti && options[1].exceptAfterSingleLine;
+
+
+                    if ((options[0] === "always" && !skip && !isPadded) ||
+                        (options[0] === "never" && isPadded)) {
+                        context.report({
+                            node: body[i + 1],
+                            message: isPadded ? NEVER_MESSAGE : ALWAYS_MESSAGE,
+                            fix(fixer) {
+                                return isPadded
+                                    ? fixer.replaceTextRange([curLast.range[1], nextFirst.range[0]], "\n")
+                                    : fixer.insertTextAfter(curLast, "\n");
+                            }
+                        });
+                    }
+                }
+            }
+        };
+    }
+};
diff --git a/lib/rules/new-cap.js b/lib/rules/new-cap.js
index 2f02c0924..f01e8f90a 100644
--- a/lib/rules/new-cap.js
+++ b/lib/rules/new-cap.js
@@ -178,7 +178,8 @@ module.exports = {
 
                 // char has no uppercase variant, so it's non-alphabetic
                 return "non-alpha";
-            } else if (firstChar === firstCharLower) {
+            }
+            if (firstChar === firstCharLower) {
                 return "lower";
             }
             return "upper";
diff --git a/lib/rules/newline-before-return.js b/lib/rules/newline-before-return.js
index 1b9b0c793..ef6c4a926 100644
--- a/lib/rules/newline-before-return.js
+++ b/lib/rules/newline-before-return.js
@@ -59,9 +59,11 @@ module.exports = {
 
             if (parentType === "IfStatement") {
                 return isPrecededByTokens(node, ["else", ")"]);
-            } else if (parentType === "DoWhileStatement") {
+            }
+            if (parentType === "DoWhileStatement") {
                 return isPrecededByTokens(node, ["do"]);
-            } else if (parentType === "SwitchCase") {
+            }
+            if (parentType === "SwitchCase") {
                 return isPrecededByTokens(node, [":"]);
             }
             return isPrecededByTokens(node, [")"]);
diff --git a/lib/rules/no-alert.js b/lib/rules/no-alert.js
index ecb52cedd..bc1087253 100644
--- a/lib/rules/no-alert.js
+++ b/lib/rules/no-alert.js
@@ -71,7 +71,8 @@ function isShadowed(scope, node) {
 function isGlobalThisReferenceOrGlobalWindow(scope, node) {
     if (scope.type === "global" && node.type === "ThisExpression") {
         return true;
-    } else if (node.name === "window") {
+    }
+    if (node.name === "window") {
         return !isShadowed(scope, node);
     }
 
diff --git a/lib/rules/no-control-regex.js b/lib/rules/no-control-regex.js
index 1ebf98000..14981f4ab 100644
--- a/lib/rules/no-control-regex.js
+++ b/lib/rules/no-control-regex.js
@@ -31,7 +31,8 @@ module.exports = {
         function getRegExp(node) {
             if (node.value instanceof RegExp) {
                 return node.value;
-            } else if (typeof node.value === "string") {
+            }
+            if (typeof node.value === "string") {
 
                 const parent = context.getAncestors().pop();
 
diff --git a/lib/rules/no-else-return.js b/lib/rules/no-else-return.js
index 36bfc26b3..6eb671749 100644
--- a/lib/rules/no-else-return.js
+++ b/lib/rules/no-else-return.js
@@ -24,8 +24,15 @@ module.exports = {
             recommended: false
         },
 
-        schema: [],
-
+        schema: [{
+            type: "object",
+            properties: {
+                allowElseIf: {
+                    type: "boolean"
+                }
+            },
+            additionalProperties: false
+        }],
         fixable: "code"
     },
 
@@ -134,13 +141,13 @@ module.exports = {
 
         /**
          * Check to see if the node is valid for evaluation,
-         * meaning it has an else and not an else-if
+         * meaning it has an else.
          *
          * @param {Node} node The node being evaluated
          * @returns {boolean} True if the node is valid
          */
         function hasElse(node) {
-            return node.alternate && node.consequent && node.alternate.type !== "IfStatement";
+            return node.alternate && node.consequent;
         }
 
         /**
@@ -189,14 +196,15 @@ module.exports = {
             return checkForReturnOrIf(node);
         }
 
+
         /**
-         * Check the if statement
+         * Check the if statement, but don't catch else-if blocks.
          * @returns {void}
          * @param {Node} node The node for the if statement to check
          * @private
          */
-        function IfStatement(node) {
-            const parent = context.getAncestors().pop();
+        function checkIfWithoutElse(node) {
+            const parent = node.parent;
             let consequents,
                 alternate;
 
@@ -221,13 +229,40 @@ module.exports = {
             }
         }
 
+        /**
+         * Check the if statement
+         * @returns {void}
+         * @param {Node} node The node for the if statement to check
+         * @private
+         */
+        function checkIfWithElse(node) {
+            const parent = node.parent;
+
+
+            /*
+             * Fixing this would require splitting one statement into two, so no error should
+             * be reported if this node is in a position where only one statement is allowed.
+             */
+            if (!astUtils.STATEMENT_LIST_PARENTS.has(parent.type)) {
+                return;
+            }
+
+            const alternate = node.alternate;
+
+            if (alternate && alwaysReturns(node.consequent)) {
+                displayReport(alternate);
+            }
+        }
+
+        const allowElseIf = !(context.options[0] && context.options[0].allowElseIf === false);
+
         //--------------------------------------------------------------------------
         // Public API
         //--------------------------------------------------------------------------
 
         return {
 
-            "IfStatement:exit": IfStatement
+            "IfStatement:exit": allowElseIf ? checkIfWithoutElse : checkIfWithElse
 
         };
 
diff --git a/lib/rules/no-extra-parens.js b/lib/rules/no-extra-parens.js
index 9c8fe70f0..020d2aeb1 100644
--- a/lib/rules/no-extra-parens.js
+++ b/lib/rules/no-extra-parens.js
@@ -195,10 +195,12 @@ module.exports = {
         function containsAssignment(node) {
             if (node.type === "AssignmentExpression") {
                 return true;
-            } else if (node.type === "ConditionalExpression" &&
+            }
+            if (node.type === "ConditionalExpression" &&
                     (node.consequent.type === "AssignmentExpression" || node.alternate.type === "AssignmentExpression")) {
                 return true;
-            } else if ((node.left && node.left.type === "AssignmentExpression") ||
+            }
+            if ((node.left && node.left.type === "AssignmentExpression") ||
                     (node.right && node.right.type === "AssignmentExpression")) {
                 return true;
             }
@@ -219,7 +221,8 @@ module.exports = {
 
             if (node.type === "ReturnStatement") {
                 return node.argument && containsAssignment(node.argument);
-            } else if (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement") {
+            }
+            if (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement") {
                 return containsAssignment(node.body);
             }
             return containsAssignment(node);
diff --git a/lib/rules/no-mixed-requires.js b/lib/rules/no-mixed-requires.js
index 55ad1e73e..171052a52 100644
--- a/lib/rules/no-mixed-requires.js
+++ b/lib/rules/no-mixed-requires.js
@@ -104,14 +104,16 @@ module.exports = {
 
                 // "var x = require('util');"
                 return DECL_REQUIRE;
-            } else if (allowCall &&
+            }
+            if (allowCall &&
                 initExpression.type === "CallExpression" &&
                 initExpression.callee.type === "CallExpression"
             ) {
 
                 // "var x = require('diagnose')('sub-module');"
                 return getDeclarationType(initExpression.callee);
-            } else if (initExpression.type === "MemberExpression") {
+            }
+            if (initExpression.type === "MemberExpression") {
 
                 // "var x = require('glob').Glob;"
                 return getDeclarationType(initExpression.object);
@@ -131,7 +133,8 @@ module.exports = {
 
                 // "var x = require('glob').Glob;"
                 return inferModuleType(initExpression.object);
-            } else if (initExpression.arguments.length === 0) {
+            }
+            if (initExpression.arguments.length === 0) {
 
                 // "var x = require();"
                 return REQ_COMPUTED;
@@ -149,7 +152,8 @@ module.exports = {
 
                 // "var fs = require('fs');"
                 return REQ_CORE;
-            } else if (/^\.{0,2}\//.test(arg.value)) {
+            }
+            if (/^\.{0,2}\//.test(arg.value)) {
 
                 // "var utils = require('./utils');"
                 return REQ_FILE;
diff --git a/lib/rules/sort-imports.js b/lib/rules/sort-imports.js
index 74db02ad3..9f82a025a 100644
--- a/lib/rules/sort-imports.js
+++ b/lib/rules/sort-imports.js
@@ -67,9 +67,11 @@ module.exports = {
         function usedMemberSyntax(node) {
             if (node.specifiers.length === 0) {
                 return "none";
-            } else if (node.specifiers[0].type === "ImportNamespaceSpecifier") {
+            }
+            if (node.specifiers[0].type === "ImportNamespaceSpecifier") {
                 return "all";
-            } else if (node.specifiers.length === 1) {
+            }
+            if (node.specifiers.length === 1) {
                 return "single";
             }
             return "multiple";
diff --git a/lib/util/source-code.js b/lib/util/source-code.js
index af01ff3fa..828eb85cd 100644
--- a/lib/util/source-code.js
+++ b/lib/util/source-code.js
@@ -332,7 +332,8 @@ class SourceCode extends TokenStore {
                     }
 
                     return parent && parent.type !== "FunctionDeclaration" && parent.type !== "Program" ? findJSDocComment(parentLeadingComments, parent.loc.start.line) : null;
-                } else if (leadingComments.length) {
+                }
+                if (leadingComments.length) {
                     return findJSDocComment(leadingComments, node.loc.start.line);
                 }
 
diff --git a/packages/eslint-config-eslint/default.yml b/packages/eslint-config-eslint/default.yml
index 2af0a614f..95ec500b8 100644
--- a/packages/eslint-config-eslint/default.yml
+++ b/packages/eslint-config-eslint/default.yml
@@ -51,7 +51,7 @@ rules:
     no-confusing-arrow: "error"
     no-console: "error"
     no-delete-var: "error"
-    no-else-return: "error"
+    no-else-return: ["error", { allowElseIf: false }]
     no-eval: "error"
     no-extend-native: "error"
     no-extra-bind: "error"


Test patch:
diff --git a/tests/bin/eslint.js b/tests/bin/eslint.js
index 39be27fb0..04ae01f3d 100644
--- a/tests/bin/eslint.js
+++ b/tests/bin/eslint.js
@@ -71,6 +71,41 @@ describe("bin/eslint.js", () => {
             return assertExitCode(child, 0);
         });
 
+        it("has exit code 0 if no linting errors are reported", () => {
+            const child = runESLint([
+                "--stdin",
+                "--no-eslintrc",
+                "--rule",
+                "{'no-extra-semi': 2}",
+                "--fix-dry-run",
+                "--format",
+                "json"
+            ]);
+
+            const expectedOutput = JSON.stringify([
+                {
+                    filePath: "<text>",
+                    messages: [],
+                    errorCount: 0,
+                    warningCount: 0,
+                    fixableErrorCount: 0,
+                    fixableWarningCount: 0,
+                    output: "var foo = bar;\n"
+                }
+            ]);
+
+            const exitCodePromise = assertExitCode(child, 0);
+            const stdoutPromise = getOutput(child).then(output => {
+                assert.strictEqual(output.stdout.trim(), expectedOutput);
+                assert.strictEqual(output.stderr, "");
+            });
+
+            child.stdin.write("var foo = bar;;\n");
+            child.stdin.end();
+
+            return Promise.all([exitCodePromise, stdoutPromise]);
+        });
+
         it("has exit code 1 if a syntax error is thrown", () => {
             const child = runESLint(["--stdin", "--no-eslintrc"]);
 
diff --git a/tests/lib/cli.js b/tests/lib/cli.js
index c994bac07..618a7c0fc 100644
--- a/tests/lib/cli.js
+++ b/tests/lib/cli.js
@@ -755,7 +755,7 @@ describe("cli", () => {
                 results: []
             });
             sandbox.stub(fakeCLIEngine.prototype, "getFormatter").returns(() => "done");
-            fakeCLIEngine.outputFixes = sandbox.stub();
+            fakeCLIEngine.outputFixes = sandbox.mock().once();
 
             localCLI = proxyquire("../../lib/cli", {
                 "./cli-engine": fakeCLIEngine,
@@ -858,6 +858,163 @@ describe("cli", () => {
 
     });
 
+    describe("when passed --fix-dry-run", () => {
+        const sandbox = sinon.sandbox.create();
+        let localCLI;
+
+        afterEach(() => {
+            sandbox.verifyAndRestore();
+        });
+
+        it("should pass fix:true to CLIEngine when executing on files", () => {
+
+            // create a fake CLIEngine to test with
+            const fakeCLIEngine = sandbox.mock().withExactArgs(sinon.match({ fix: true }));
+
+            fakeCLIEngine.prototype = leche.fake(CLIEngine.prototype);
+            sandbox.stub(fakeCLIEngine.prototype, "executeOnFiles").returns({
+                errorCount: 0,
+                warningCount: 0,
+                results: []
+            });
+            sandbox.stub(fakeCLIEngine.prototype, "getFormatter").returns(() => "done");
+            fakeCLIEngine.outputFixes = sandbox.mock().never();
+
+            localCLI = proxyquire("../../lib/cli", {
+                "./cli-engine": fakeCLIEngine,
+                "./logging": log
+            });
+
+            const exitCode = localCLI.execute("--fix-dry-run .");
+
+            assert.equal(exitCode, 0);
+
+        });
+
+        it("should not rewrite files when in fix-dry-run mode", () => {
+
+            const report = {
+                errorCount: 1,
+                warningCount: 0,
+                results: [{
+                    filePath: "./foo.js",
+                    output: "bar",
+                    messages: [
+                        {
+                            severity: 2,
+                            message: "Fake message"
+                        }
+                    ]
+                }]
+            };
+
+            // create a fake CLIEngine to test with
+            const fakeCLIEngine = sandbox.mock().withExactArgs(sinon.match({ fix: true }));
+
+            fakeCLIEngine.prototype = leche.fake(CLIEngine.prototype);
+            sandbox.stub(fakeCLIEngine.prototype, "executeOnFiles").returns(report);
+            sandbox.stub(fakeCLIEngine.prototype, "getFormatter").returns(() => "done");
+            fakeCLIEngine.outputFixes = sandbox.mock().never();
+
+            localCLI = proxyquire("../../lib/cli", {
+                "./cli-engine": fakeCLIEngine,
+                "./logging": log
+            });
+
+            const exitCode = localCLI.execute("--fix-dry-run .");
+
+            assert.equal(exitCode, 1);
+
+        });
+
+        it("should provide fix predicate when in fix-dry-run mode and quiet mode", () => {
+
+            const report = {
+                errorCount: 0,
+                warningCount: 1,
+                results: [{
+                    filePath: "./foo.js",
+                    output: "bar",
+                    messages: [
+                        {
+                            severity: 1,
+                            message: "Fake message"
+                        }
+                    ]
+                }]
+            };
+
+            // create a fake CLIEngine to test with
+            const fakeCLIEngine = sandbox.mock().withExactArgs(sinon.match({ fix: sinon.match.func }));
+
+            fakeCLIEngine.prototype = leche.fake(CLIEngine.prototype);
+            sandbox.stub(fakeCLIEngine.prototype, "executeOnFiles").returns(report);
+            sandbox.stub(fakeCLIEngine.prototype, "getFormatter").returns(() => "done");
+            fakeCLIEngine.getErrorResults = sandbox.stub().returns([]);
+            fakeCLIEngine.outputFixes = sandbox.mock().never();
+
+            localCLI = proxyquire("../../lib/cli", {
+                "./cli-engine": fakeCLIEngine,
+                "./logging": log
+            });
+
+            const exitCode = localCLI.execute("--fix-dry-run --quiet .");
+
+            assert.equal(exitCode, 0);
+
+        });
+
+        it("should allow executing on text", () => {
+
+            const report = {
+                errorCount: 1,
+                warningCount: 0,
+                results: [{
+                    filePath: "./foo.js",
+                    output: "bar",
+                    messages: [
+                        {
+                            severity: 2,
+                            message: "Fake message"
+                        }
+                    ]
+                }]
+            };
+
+            // create a fake CLIEngine to test with
+            const fakeCLIEngine = sandbox.mock().withExactArgs(sinon.match({ fix: true }));
+
+            fakeCLIEngine.prototype = leche.fake(CLIEngine.prototype);
+            sandbox.stub(fakeCLIEngine.prototype, "executeOnText").returns(report);
+            sandbox.stub(fakeCLIEngine.prototype, "getFormatter").returns(() => "done");
+            fakeCLIEngine.outputFixes = sandbox.mock().never();
+
+            localCLI = proxyquire("../../lib/cli", {
+                "./cli-engine": fakeCLIEngine,
+                "./logging": log
+            });
+
+            const exitCode = localCLI.execute("--fix-dry-run .", "foo = bar;");
+
+            assert.equal(exitCode, 1);
+        });
+
+        it("should not call CLIEngine and return 1 when used with --fix", () => {
+
+            // create a fake CLIEngine to test with
+            const fakeCLIEngine = sandbox.mock().never();
+
+            localCLI = proxyquire("../../lib/cli", {
+                "./cli-engine": fakeCLIEngine,
+                "./logging": log
+            });
+
+            const exitCode = localCLI.execute("--fix --fix-dry-run .", "foo = bar;");
+
+            assert.equal(exitCode, 1);
+        });
+    });
+
     describe("when passing --print-config", () => {
         it("should print out the configuration", () => {
             const filePath = getFixturePath("files");
diff --git a/tests/lib/linter.js b/tests/lib/linter.js
index b9d1c5d3a..3f3c75333 100644
--- a/tests/lib/linter.js
+++ b/tests/lib/linter.js
@@ -20,7 +20,8 @@
 function compatRequire(name, windowName) {
     if (typeof window === "object") {
         return window[windowName || name];
-    } else if (typeof require === "function") {
+    }
+    if (typeof require === "function") {
         return require(name);
     }
     throw new Error(`Cannot find object '${name}'.`);
diff --git a/tests/lib/rules/lines-between-class-members.js b/tests/lib/rules/lines-between-class-members.js
new file mode 100644
index 000000000..1304d719a
--- /dev/null
+++ b/tests/lib/rules/lines-between-class-members.js
@@ -0,0 +1,72 @@
+/**
+ * @fileoverview Tests for lines-between-class-members rule.
+ * @author 薛定谔的猫<hh_2013@foxmail.com>
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const rule = require("../../../lib/rules/lines-between-class-members");
+const RuleTester = require("../../../lib/testers/rule-tester");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const ALWAYS_MESSAGE = "Expected blank line between class members.";
+const NEVER_MESSAGE = "Unexpected blank line between class members.";
+
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
+
+ruleTester.run("lines-between-class-members", rule, {
+    valid: [
+        "class foo{}",
+        "class foo{;;}",
+        "class foo{\n\n}",
+        "class foo{constructor(){}\n}",
+        "class foo{\nconstructor(){}}",
+
+        "class foo{ bar(){}\n\nbaz(){}}",
+        "class foo{ bar(){}\n\n/*comments*/baz(){}}",
+        "class foo{ bar(){}\n\n//comments\nbaz(){}}",
+
+        "class foo{ bar(){}\n\n;;baz(){}}",
+        "class foo{ bar(){};\n\nbaz(){}}",
+
+        { code: "class foo{ bar(){}\nbaz(){}}", options: ["never"] },
+        { code: "class foo{ bar(){}\n/*comments*/baz(){}}", options: ["never"] },
+        { code: "class foo{ bar(){}\n//comments\nbaz(){}}", options: ["never"] },
+
+        { code: "class foo{ bar(){}\n\nbaz(){}}", options: ["always"] },
+        { code: "class foo{ bar(){}\n\n/*comments*/baz(){}}", options: ["always"] },
+        { code: "class foo{ bar(){}\n\n//comments\nbaz(){}}", options: ["always"] },
+
+        { code: "class foo{ bar(){}\nbaz(){}}", options: ["always", { exceptAfterSingleLine: true }] },
+        { code: "class foo{ bar(){\n}\n\nbaz(){}}", options: ["always", { exceptAfterSingleLine: true }] }
+    ],
+    invalid: [
+        {
+            code: "class foo{ bar(){}\nbaz(){}}",
+            output: "class foo{ bar(){}\n\nbaz(){}}",
+            options: ["always"],
+            errors: [{ message: ALWAYS_MESSAGE }]
+        }, {
+            code: "class foo{ bar(){}\n\nbaz(){}}",
+            output: "class foo{ bar(){}\nbaz(){}}",
+            options: ["never"],
+            errors: [{ message: NEVER_MESSAGE }]
+        }, {
+            code: "class foo{ bar(){\n}\nbaz(){}}",
+            output: "class foo{ bar(){\n}\n\nbaz(){}}",
+            options: ["always", { exceptAfterSingleLine: true }],
+            errors: [{ message: ALWAYS_MESSAGE }]
+        }
+    ]
+});
diff --git a/tests/lib/rules/no-else-return.js b/tests/lib/rules/no-else-return.js
index d081de88f..f88407151 100644
--- a/tests/lib/rules/no-else-return.js
+++ b/tests/lib/rules/no-else-return.js
@@ -42,7 +42,19 @@ ruleTester.run("no-else-return", rule, {
                     if (bar) return;
                     else baz;
             }
-        `
+        `,
+        {
+            code: "function foo19() { if (true) { return x; } else if (false) { return y; } }",
+            options: [{ allowElseIf: true }]
+        },
+        {
+            code: "function foo20() {if (x) { return true; } else if (y) { notAReturn() } else { notAReturn(); } }",
+            options: [{ allowElseIf: true }]
+        },
+        {
+            code: "function foo21() { var x = true; if (x) { return x; } else if (x === false) { return false; } }",
+            options: [{ allowElseIf: true }]
+        }
     ],
     invalid: [
         {
@@ -96,6 +108,18 @@ ruleTester.run("no-else-return", rule, {
             output: "function foo9() {if (x) { return true; } else if (y) { return true; }  notAReturn();  }",
             errors: [{ message: "Unnecessary 'else' after 'return'.", type: "BlockStatement" }]
         },
+        {
+            code: "function foo9a() {if (x) { return true; } else if (y) { return true; } else { notAReturn(); } }",
+            output: "function foo9a() {if (x) { return true; } if (y) { return true; } else { notAReturn(); } }",
+            options: [{ allowElseIf: false }],
+            errors: [{ message: "Unnecessary 'else' after 'return'.", type: "IfStatement" }]
+        },
+        {
+            code: "function foo9b() {if (x) { return true; } if (y) { return true; } else { notAReturn(); } }",
+            output: "function foo9b() {if (x) { return true; } if (y) { return true; }  notAReturn();  }",
+            options: [{ allowElseIf: false }],
+            errors: [{ message: "Unnecessary 'else' after 'return'.", type: "BlockStatement" }]
+        },
         {
             code: "function foo10() { if (foo) return bar; else (foo).bar(); }",
             output: "function foo10() { if (foo) return bar; (foo).bar(); }",
@@ -140,6 +164,24 @@ ruleTester.run("no-else-return", rule, {
             code: "function foo18() { if (foo) return function() {} \nelse [1, 2, 3].map(bar) }",
             output: null,
             errors: [{ message: "Unnecessary 'else' after 'return'.", type: "ExpressionStatement" }]
+        },
+        {
+            code: "function foo19() { if (true) { return x; } else if (false) { return y; } }",
+            output: "function foo19() { if (true) { return x; } if (false) { return y; } }",
+            options: [{ allowElseIf: false }],
+            errors: [{ message: "Unnecessary 'else' after 'return'.", type: "IfStatement" }]
+        },
+        {
+            code: "function foo20() {if (x) { return true; } else if (y) { notAReturn() } else { notAReturn(); } }",
+            output: "function foo20() {if (x) { return true; } if (y) { notAReturn() } else { notAReturn(); } }",
+            options: [{ allowElseIf: false }],
+            errors: [{ message: "Unnecessary 'else' after 'return'.", type: "IfStatement" }]
+        },
+        {
+            code: "function foo21() { var x = true; if (x) { return x; } else if (x === false) { return false; } }",
+            output: "function foo21() { var x = true; if (x) { return x; } if (x === false) { return false; } }",
+            options: [{ allowElseIf: false }],
+            errors: [{ message: "Unnecessary 'else' after 'return'.", type: "IfStatement" }]
         }
     ]
 });