Repository: eslint/eslint (js)

Issue:
no-console rule should document how to enforce calls only via no-restricted-syntax
**What rule do you want to change?**

The `no-console` rule.

**Does this change cause the rule to produce more or fewer warnings?**

Fewer warnings.

**How will the change be implemented? (New option, new default behavior, etc.)?**

I'm thinking it would be default behavior, but could also be configured.

**Please provide some example code that this change will affect:**

In our development and test environments we have this snippet to suss out warnings in our Javascript:

```js
console.error = function (message) {
  throw new Error(message);
};
```

**What does the rule currently do for this code?**

The rule currently reports `Unexpected console statement`.

**What will the rule do after it's changed?**

It will not report any errors for assignment to console.

Pull request description:
Docs: show console call with no-restricted-syntax (fixes #7806)

[X] Documentation update

**What changes did you make? (Give an overview)**
Add documentation on how to show warnings/error for console calls and not method overwrites with the use of no-restricted-syntax

**Is there anything you'd like reviewers to focus on?**
That I got all the details from the opened issue



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-console.md b/docs/rules/no-console.md
index ab58cb846..1c36a85f9 100644
--- a/docs/rules/no-console.md
+++ b/docs/rules/no-console.md
@@ -49,6 +49,46 @@ console.error("Log an error level message.");
 
 If you're using Node.js, however, `console` is used to output information to the user and so is not strictly used for debugging purposes. If you are developing for Node.js then you most likely do not want this rule enabled.
 
+Another case where you might not use this rule is if you want to enforce console calls and not console overwrites. For example:
+
+```js
+/*eslint no-console: ["error", { allow: ["warn"] }] */
+console.error = function (message) {
+  throw new Error(message);
+};
+```
+
+With the `no-console` rule in the above example, ESLint will report an error. For the above example, you can disable the rule:
+
+```js
+// eslint-disable-next-line no-console
+console.error = function (message) {
+  throw new Error(message);
+};
+
+// or
+
+console.error = function (message) {  // eslint-disable-line no-console
+  throw new Error(message);
+};
+```
+
+However, you might not want to manually add `eslint-disable-next-line` or `eslint-disable-line`. You can achieve the effect of only receiving errors for console calls with the `no-restricted-syntax` rule:
+
+```json
+{
+    "rules": {
+        "no-restricted-syntax": [
+            "error",
+            {
+                "selector": "CallExpression[callee.object.name='console'][callee.property.name=/^(log|warn|error|info|trace)$/]",
+                "message": "Unexpected property on console object was called"
+            }
+        ]
+    }
+}
+```
+
 ## Related Rules
 
 * [no-alert](no-alert.md)
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/ignored-paths.js b/lib/ignored-paths.js
index 3b7a00e9c..f43ce4642 100644
--- a/lib/ignored-paths.js
+++ b/lib/ignored-paths.js
@@ -184,7 +184,7 @@ class IgnoredPaths {
                                     addPattern(this.ig.default, pattern);
                                 });
                             } else {
-                                throw new Error("Package.json eslintIgnore property requires an array of paths");
+                                throw new TypeError("Package.json eslintIgnore property requires an array of paths");
                             }
                         }
                     }
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/array-bracket-newline.js b/lib/rules/array-bracket-newline.js
index 319ac60f2..adfce985f 100644
--- a/lib/rules/array-bracket-newline.js
+++ b/lib/rules/array-bracket-newline.js
@@ -194,6 +194,12 @@ module.exports = {
                     options.multiline &&
                     elements.length > 0 &&
                     firstIncComment.loc.start.line !== lastIncComment.loc.end.line
+                ) ||
+                (
+                    elements.length === 0 &&
+                    firstIncComment.type === "Block" &&
+                    firstIncComment.loc.start.line !== lastIncComment.loc.end.line &&
+                    firstIncComment === lastIncComment
                 )
             );
 
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/node-event-generator.js b/lib/util/node-event-generator.js
index 05b343ae9..34ee78b49 100644
--- a/lib/util/node-event-generator.js
+++ b/lib/util/node-event-generator.js
@@ -160,7 +160,7 @@ function tryParseSelector(rawSelector) {
         return esquery.parse(rawSelector.replace(/:exit$/, ""));
     } catch (err) {
         if (typeof err.offset === "number") {
-            throw new Error(`Syntax error in selector "${rawSelector}" at position ${err.offset}: ${err.message}`);
+            throw new SyntaxError(`Syntax error in selector "${rawSelector}" at position ${err.offset}: ${err.message}`);
         }
         throw err;
     }
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/lib/testers/rule-tester.js b/lib/testers/rule-tester.js
index d7e14878c..5ba18d70c 100644
--- a/lib/testers/rule-tester.js
+++ b/lib/testers/rule-tester.js
@@ -178,7 +178,7 @@ class RuleTester {
      */
     static setDefaultConfig(config) {
         if (typeof config !== "object") {
-            throw new Error("RuleTester.setDefaultConfig: config must be an object");
+            throw new TypeError("RuleTester.setDefaultConfig: config must be an object");
         }
         defaultConfig = config;
 
@@ -254,7 +254,7 @@ class RuleTester {
             linter = this.linter;
 
         if (lodash.isNil(test) || typeof test !== "object") {
-            throw new Error(`Test Scenarios for rule ${ruleName} : Could not find test scenario object`);
+            throw new TypeError(`Test Scenarios for rule ${ruleName} : Could not find test scenario object`);
         }
 
         requiredScenarios.forEach(scenarioType => {
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/array-bracket-newline.js b/tests/lib/rules/array-bracket-newline.js
index d4e76dfc6..cf02cbdf6 100644
--- a/tests/lib/rules/array-bracket-newline.js
+++ b/tests/lib/rules/array-bracket-newline.js
@@ -40,6 +40,8 @@ ruleTester.run("array-bracket-newline", rule, {
         "var foo = [\n1, 2\n// any comment\n];",
         "var foo = [\n1,\n2\n];",
         "var foo = [\nfunction foo() {\nreturn dosomething();\n}\n];",
+        "var foo = [/* \nany comment\n */];",
+        "var foo = [/* single line multiline comment for no real reason */];",
 
         // "always"
         { code: "var foo = [\n];", options: ["always"] },
@@ -72,6 +74,7 @@ ruleTester.run("array-bracket-newline", rule, {
         { code: "var foo = [\n1, 2\n// any comment\n];", options: [{ multiline: true }] },
         { code: "var foo = [\n1,\n2\n];", options: [{ multiline: true }] },
         { code: "var foo = [\nfunction foo() {\nreturn dosomething();\n}\n];", options: [{ multiline: true }] },
+        { code: "var foo = [/* \nany comment\n */];", options: [{ multiline: true }] },
 
         // { multiline: false }
         { code: "var foo = [];", options: [{ multiline: false }] },
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" }]
         }
     ]
 });