Repository: eslint/eslint
Pull request #8631 (2017-05-21 19:33:28)

Update: Add Fixer method to Linter API
**What is the purpose of this pull request? (put an "X" next to item)**

[ ] Documentation update
[ ] Bug fix ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/bug-report.md))
[ ] New rule ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/rule-proposal.md))
[ ] Changes an existing rule ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/rule-change-proposal.md))
[ ] Add autofixing to a rule
[ ] Add a CLI option
[ x ] Add something to the core
[ ] Other, please explain:

<!--
    If the item you've checked above has a template, please paste the template questions below and answer them. (If this pull request is addressing an issue, you can just paste a link to the issue here instead.)
-->

<!--
    Please ensure your pull request is ready:

    - Read the pull request guide (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)**

Moved fixer code from `CLIEngine` to `Linter` API. This way it can be consumed on the web also. 
Issue link: https://github.com/eslint/eslint.github.io/issues/322

**Is there anything you'd like reviewers to focus on?**

I named the function `verifyAndFix` based on `verify` method. Any other name suggestions?

If accepted:

- [x] Docs

eslintbot: LGTM
eslintbot: LGTM
kaicataldo: It sounds like @nzakas was against this change in [this comment](https://github.com/eslint/eslint.github.io/issues/322#issuecomment-271694641) - have things changed since that last discussion?
kaicataldo: Sorry! Clicked the wrong button!
gyandeeps: I think he was against moving it into `linter` API exposed on api.js. Now we have designed a `Linter` class and exposed on API, which essentially is kinda WebView that why i thought of moving this in here.

What this does is that Linter can verify the code and also fix the code and return the fixed code. Now writing out to files n all is still outside `Linter` (currently handled by `CLIEngine`.

Personally I dont see any downside of this (unless if i am missing something). Any suggestions?
kaicataldo: Makes sense :) Thanks for the explanation
not-an-aardvark: I think the functionality fits into `linter` better than it fits into `CLIEngine` anyway, given that it relates to linting text and not CLI-specific behavior.
kaicataldo: I agree!
ilyavolodin: @nzakas was originally against moving it into `linter`, but I never agreed with that position. In my mind CLI deals with user input and file system. Everything else should not be in CLI. So I'm very much 👍 for this change.
gyandeeps: @kaicataldo @not-an-aardvark @platinumazure @kaicataldo and me (total 5) are all in favor of this. Do we still need to talk in TSC or we can mark it accepted here.
I know we have done this in past and kept TSC meeting for issue which doesn't have a clear path.
platinumazure: I don't really count since I'm not on TSC. :-)

On May 26, 2017 10:05 AM, "Gyandeep Singh" <notifications@github.com> wrote:

> @kaicataldo <https://github.com/kaicataldo> @not-an-aardvark
> <https://github.com/not-an-aardvark> @platinumazure
> <https://github.com/platinumazure> @kaicataldo
> <https://github.com/kaicataldo> and me (total 5) are all in favor of
> this. Do we still need to talk in TSC or we can mark it accepted here.
> I know we have done this in past and kept TSC meeting for issue which
> doesn't have a clear path.
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/eslint/eslint/pull/8631#issuecomment-304306993>, or mute
> the thread
> <https://github.com/notifications/unsubscribe-auth/AARWejppuiPBWDVG3JzCYAtHQqmkNgQ2ks5r9uo6gaJpZM4Nhsk1>
> .
>

kaicataldo: I don't think I count as two votes :P I only added the `tsc agenda` label because it was a change to core. If we have a majority of TSC members who are :+1: for the change I don't think we need to necessarily discuss (though I can see the argument being made that we should always vote on it formally to ensure that everyone sees the issue).
btmills: This proposal was discussed and accepted in the 2017-06-01 TSC meeting :tada:
eslintbot: LGTM

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 73dde79d3..7626f8970 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,31 @@
+v4.0.0 - June 11, 2017
+
+* 4aefb49 Chore: avoid using deprecated rules on ESLint codebase (#8708) (Teddy Katz)
+* 389feba Chore: upgrade deps. (#8684) (薛定谔的猫)
+* 3da7b5e Fix: Semi-Style only check for comments when tokens exist (fixes #8696) (#8697) (Reyad Attiyat)
+* 3cfe9ee Fix: Add space between async and param on fix (fixes #8682) (#8693) (Reyad Attiyat)
+* c702858 Chore: enable no-multiple-empty-lines on ESLint codebase (#8694) (Teddy Katz)
+* 34c4020 Update: Add support for parens on left side for-loops (fixes: #8393) (#8679) (Victor Hom)
+* 735cd09 Docs: Correct the comment in an example for `no-mixed-requires` (#8686) (Fangzhou Li)
+* 026f048 Chore: remove dead code from prefer-const (#8683) (Teddy Katz)
+
+v4.0.0-rc.0 - June 2, 2017
+
+* 0058b0f8 Update: add --fix to no-debugger (#8660) (薛定谔的猫)
+* b4daa225 Docs: Note to --fix option for strict rule (#8680) (Vitaliy Potapov)
+* 4df33e7c Chore: check for root:true in project sooner (fixes #8561) (#8638) (Victor Hom)
+* c9b980ce Build: Add Node 8 on travis (#8669) (Gyandeep Singh)
+* 95248336 Fix: Don't check object destructing in integer property (fixes #8654) (#8657) (flowmemo)
+* c4ac969c Update: fix parenthesized ternary expression indentation (fixes #8637) (#8649) (Teddy Katz)
+* 4f2f9fcb Build: update license checker to allow LGPL (fixes #8647) (#8652) (Teddy Katz)
+* b0c83bd1 Docs: suggest pushing new commits to a PR instead of amending (#8632) (Teddy Katz)
+* d0e9fd2d Fix: Config merge to correctly account for extends (fixes #8193) (#8636) (Gyandeep Singh)
+* 705d88f7 Docs: Update CLA link on Pull Requests page (#8642) (Teddy Katz)
+* 794d4d6c Docs: missing paren on readme (#8640) (Dan Beam)
+* 7ebd9d6f New: array-element-newline rule (fixes #6075) (#8375) (Jan Peer Stöcklmair)
+* f62cff66 Chore: Remove dependency to user-home (fixes #8604) (#8629) (Pavol Madar)
+* 936bc174 Docs: Add missing documentation for scoped modules in sharable config developer-guide (#8610) (Jonathan Samines)
+
 v4.0.0-beta.0 - May 19, 2017
 
 * 2f7015b6 New: semi-style rule (fixes #8169) (#8542) (Toru Nagashima)
diff --git a/docs/developer-guide/nodejs-api.md b/docs/developer-guide/nodejs-api.md
index 03a1dd794..f48670896 100644
--- a/docs/developer-guide/nodejs-api.md
+++ b/docs/developer-guide/nodejs-api.md
@@ -147,6 +147,37 @@ console.log(code.text);     // "var foo = bar;"
 
 In this way, you can retrieve the text and AST used for the last run of `linter.verify()`.
 
+### verifyAndFix()
+
+This method is similar to verify except that it also runs autofixing logic, similar to the `--fix` flag on the command line. The result object will contain the autofixed code, along with any remaining linting messages for the code that were not autofixed.
+
+```js
+var Linter = require("eslint").Linter;
+var linter = new Linter();
+
+var messages = linter.verifyAndFix("var foo", {
+    rules: {
+        semi: 2
+    }
+}, { filename: "foo.js" });
+```
+
+Output object from this method:
+
+```js
+{
+    fixed: true,
+    text: "var foo;",
+    messages: []
+}
+```
+
+The information available is:
+
+* `fixed` - True, if the code was fixed.
+* `text` - Fixed code text (might be the same as input if no fixes were applied).
+* `messages` - Collection of all messages for the given code (It has the same information as explained above under `verify` block).
+
 ## linter
 
 The `eslint.linter` object (deprecated) is an instance of the `Linter` class as defined [above](#Linter). `eslint.linter` exists for backwards compatibility, but we do not recommend using it because any mutations to it are shared among every module that uses `eslint`. Instead, please create your own instance of `eslint.Linter`.
diff --git a/docs/rules/no-mixed-requires.md b/docs/rules/no-mixed-requires.md
index b507b6c5e..7ecf63770 100644
--- a/docs/rules/no-mixed-requires.md
+++ b/docs/rules/no-mixed-requires.md
@@ -76,7 +76,7 @@ Examples of **incorrect** code for this rule with the `{ "grouping": true }` opt
 ```js
 /*eslint no-mixed-requires: ["error", { "grouping": true }]*/
 
-// invalid because of mixed types "core" and "file"
+// invalid because of mixed types "core" and "module"
 var fs = require('fs'),
     async = require('async');
 
diff --git a/docs/rules/strict.md b/docs/rules/strict.md
index 03e2feea4..22e6565dc 100644
--- a/docs/rules/strict.md
+++ b/docs/rules/strict.md
@@ -49,6 +49,8 @@ This rule disallows strict mode directives, no matter which option is specified,
 
 This rule disallows strict mode directives, no matter which option is specified, in functions with non-simple parameter lists (for example, parameter lists with default parameter values) because that is a syntax error in **ECMAScript 2016** and later. See the examples of the [function](#function) option.
 
+The `--fix` option on the command line does not insert new `"use strict"` statements, but only removes unneeded statements.
+
 ## Options
 
 This rule has a string option:
diff --git a/lib/cli-engine.js b/lib/cli-engine.js
index 46628c1e4..f53c2e76a 100644
--- a/lib/cli-engine.js
+++ b/lib/cli-engine.js
@@ -23,11 +23,9 @@ const fs = require("fs"),
     Config = require("./config"),
     fileEntryCache = require("file-entry-cache"),
     globUtil = require("./util/glob-util"),
-    SourceCodeFixer = require("./util/source-code-fixer"),
     validator = require("./config/config-validator"),
     stringify = require("json-stable-stringify"),
     hash = require("./util/hash"),
-
     pkg = require("../package.json");
 
 const debug = require("debug")("eslint:cli-engine");
@@ -132,80 +130,6 @@ function calculateStatsPerRun(results) {
     });
 }
 
-/**
- * Performs multiple autofix passes over the text until as many fixes as possible
- * have been applied.
- * @param {string} text The source text to apply fixes to.
- * @param {Object} config The ESLint config object to use.
- * @param {Object} options The ESLint options object to use.
- * @param {string} options.filename The filename from which the text was read.
- * @param {boolean} options.allowInlineConfig Flag indicating if inline comments
- *      should be allowed.
- * @param {Linter} linter Linter context
- * @returns {Object} The result of the fix operation as returned from the
- *      SourceCodeFixer.
- * @private
- */
-function multipassFix(text, config, options, linter) {
-    const MAX_PASSES = 10;
-    let messages = [],
-        fixedResult,
-        fixed = false,
-        passNumber = 0;
-
-    /**
-     * This loop continues until one of the following is true:
-     *
-     * 1. No more fixes have been applied.
-     * 2. Ten passes have been made.
-     *
-     * That means anytime a fix is successfully applied, there will be another pass.
-     * Essentially, guaranteeing a minimum of two passes.
-     */
-    do {
-        passNumber++;
-
-        debug(`Linting code for ${options.filename} (pass ${passNumber})`);
-        messages = linter.verify(text, config, options);
-
-        debug(`Generating fixed text for ${options.filename} (pass ${passNumber})`);
-        fixedResult = SourceCodeFixer.applyFixes(linter.getSourceCode(), messages);
-
-        // stop if there are any syntax errors.
-        // 'fixedResult.output' is a empty string.
-        if (messages.length === 1 && messages[0].fatal) {
-            break;
-        }
-
-        // keep track if any fixes were ever applied - important for return value
-        fixed = fixed || fixedResult.fixed;
-
-        // update to use the fixed output instead of the original text
-        text = fixedResult.output;
-
-    } while (
-        fixedResult.fixed &&
-        passNumber < MAX_PASSES
-    );
-
-
-    /*
-     * If the last result had fixes, we need to lint again to be sure we have
-     * the most up-to-date information.
-     */
-    if (fixedResult.fixed) {
-        fixedResult.messages = linter.verify(text, config, options);
-    }
-
-
-    // ensure the last result properly reflects if fixes were done
-    fixedResult.fixed = fixed;
-    fixedResult.output = text;
-
-    return fixedResult;
-
-}
-
 /**
  * Processes an source code using ESLint.
  * @param {string} text The source code to check.
@@ -269,10 +193,10 @@ function processText(text, configHelper, filename, fix, allowInlineConfig, linte
     } else {
 
         if (fix) {
-            fixedResult = multipassFix(text, config, {
+            fixedResult = linter.verifyAndFix(text, config, {
                 filename,
                 allowInlineConfig
-            }, linter);
+            });
             messages = fixedResult.messages;
         } else {
             messages = linter.verify(text, config, {
@@ -685,7 +609,6 @@ class CLIEngine {
         const startTime = Date.now();
 
 
-
         patterns = this.resolveFileGlobPatterns(patterns);
         const fileList = globUtil.listFilesToProcess(patterns, options);
 
diff --git a/lib/config/config-file.js b/lib/config/config-file.js
index 71ef45d3a..c9fcc9dff 100644
--- a/lib/config/config-file.js
+++ b/lib/config/config-file.js
@@ -513,7 +513,6 @@ function resolve(filePath, relativeTo) {
     return { filePath };
 
 
-
 }
 
 /**
diff --git a/lib/ignored-paths.js b/lib/ignored-paths.js
index 0d9152495..d2d89bd98 100644
--- a/lib/ignored-paths.js
+++ b/lib/ignored-paths.js
@@ -37,7 +37,6 @@ const DEFAULT_OPTIONS = {
     cwd: process.cwd()
 };
 
-
 //------------------------------------------------------------------------------
 // Helpers
 //------------------------------------------------------------------------------
@@ -80,6 +79,7 @@ class IgnoredPaths {
      */
     constructor(options) {
         options = mergeDefaultOptions(options);
+        this.cache = {};
 
         /**
          * add pattern to node-ignore instance
@@ -91,17 +91,6 @@ class IgnoredPaths {
             return ig.addPattern(pattern);
         }
 
-        /**
-         * add ignore file to node-ignore instance
-         * @param {Object} ig, instance of node-ignore
-         * @param {string} filepath, file to add to ig
-         * @returns {array} raw ignore rules
-         */
-        function addIgnoreFile(ig, filepath) {
-            ig.ignoreFiles.push(filepath);
-            return ig.add(fs.readFileSync(filepath, "utf8"));
-        }
-
         this.defaultPatterns = [].concat(DEFAULT_IGNORE_DIRS, options.patterns || []);
         this.baseDir = options.cwd;
 
@@ -155,8 +144,8 @@ class IgnoredPaths {
             if (ignorePath) {
                 debug(`Adding ${ignorePath}`);
                 this.baseDir = path.dirname(path.resolve(options.cwd, ignorePath));
-                addIgnoreFile(this.ig.custom, ignorePath);
-                addIgnoreFile(this.ig.default, ignorePath);
+                this.addIgnoreFile(this.ig.custom, ignorePath);
+                this.addIgnoreFile(this.ig.default, ignorePath);
             }
 
             if (options.ignorePattern) {
@@ -168,6 +157,29 @@ class IgnoredPaths {
         this.options = options;
     }
 
+    /**
+     * read ignore filepath
+     * @param {string} filePath, file to add to ig
+     * @returns {array} raw ignore rules
+     */
+    readIgnoreFile(filePath) {
+        if (typeof this.cache[filePath] === "undefined") {
+            this.cache[filePath] = fs.readFileSync(filePath, "utf8");
+        }
+        return this.cache[filePath];
+    }
+
+    /**
+     * add ignore file to node-ignore instance
+     * @param {Object} ig, instance of node-ignore
+     * @param {string} filePath, file to add to ig
+     * @returns {array} raw ignore rules
+     */
+    addIgnoreFile(ig, filePath) {
+        ig.ignoreFiles.push(filePath);
+        return ig.add(this.readIgnoreFile(filePath));
+    }
+
     /**
      * Determine whether a file path is included in the default or custom ignore patterns
      * @param {string} filepath Path to check
diff --git a/lib/linter.js b/lib/linter.js
index 25af05223..0b0d06a86 100755
--- a/lib/linter.js
+++ b/lib/linter.js
@@ -27,9 +27,11 @@ const assert = require("assert"),
     Rules = require("./rules"),
     timing = require("./timing"),
     astUtils = require("./ast-utils"),
+    pkg = require("../package.json"),
+    SourceCodeFixer = require("./util/source-code-fixer");
 
-    pkg = require("../package.json");
-
+const debug = require("debug")("eslint:linter");
+const MAX_AUTOFIX_PASSES = 10;
 
 //------------------------------------------------------------------------------
 // Typedefs
@@ -1185,6 +1187,74 @@ class Linter extends EventEmitter {
     getDeclaredVariables(node) {
         return (this.scopeManager && this.scopeManager.getDeclaredVariables(node)) || [];
     }
+
+    /**
+     * Performs multiple autofix passes over the text until as many fixes as possible
+     * have been applied.
+     * @param {string} text The source text to apply fixes to.
+     * @param {Object} config The ESLint config object to use.
+     * @param {Object} options The ESLint options object to use.
+     * @param {string} options.filename The filename from which the text was read.
+     * @param {boolean} options.allowInlineConfig Flag indicating if inline comments
+     *      should be allowed.
+     * @returns {Object} The result of the fix operation as returned from the
+     *      SourceCodeFixer.
+     */
+    verifyAndFix(text, config, options) {
+        let messages = [],
+            fixedResult,
+            fixed = false,
+            passNumber = 0;
+
+        /**
+         * This loop continues until one of the following is true:
+         *
+         * 1. No more fixes have been applied.
+         * 2. Ten passes have been made.
+         *
+         * That means anytime a fix is successfully applied, there will be another pass.
+         * Essentially, guaranteeing a minimum of two passes.
+         */
+        do {
+            passNumber++;
+
+            debug(`Linting code for ${options.filename} (pass ${passNumber})`);
+            messages = this.verify(text, config, options);
+
+            debug(`Generating fixed text for ${options.filename} (pass ${passNumber})`);
+            fixedResult = SourceCodeFixer.applyFixes(this.getSourceCode(), messages);
+
+            // stop if there are any syntax errors.
+            // 'fixedResult.output' is a empty string.
+            if (messages.length === 1 && messages[0].fatal) {
+                break;
+            }
+
+            // keep track if any fixes were ever applied - important for return value
+            fixed = fixed || fixedResult.fixed;
+
+            // update to use the fixed output instead of the original text
+            text = fixedResult.output;
+
+        } while (
+            fixedResult.fixed &&
+            passNumber < MAX_AUTOFIX_PASSES
+        );
+
+        /*
+         * If the last result had fixes, we need to lint again to be sure we have
+         * the most up-to-date information.
+         */
+        if (fixedResult.fixed) {
+            fixedResult.messages = this.verify(text, config, options);
+        }
+
+        // ensure the last result properly reflects if fixes were done
+        fixedResult.fixed = fixed;
+        fixedResult.output = text;
+
+        return fixedResult;
+    }
 }
 
 // methods that exist on SourceCode object
diff --git a/lib/rules/arrow-parens.js b/lib/rules/arrow-parens.js
index c292d1b49..60a043bb3 100644
--- a/lib/rules/arrow-parens.js
+++ b/lib/rules/arrow-parens.js
@@ -50,14 +50,31 @@ module.exports = {
 
         const sourceCode = context.getSourceCode();
 
-
         /**
          * Determines whether a arrow function argument end with `)`
          * @param {ASTNode} node The arrow function node.
          * @returns {void}
          */
         function parens(node) {
-            const token = sourceCode.getFirstToken(node, node.async ? 1 : 0);
+            const isAsync = node.async;
+            const firstTokenOfParam = sourceCode.getFirstToken(node, isAsync ? 1 : 0);
+
+            /**
+             * Remove the parenthesis around a parameter
+             * @param {Fixer} fixer Fixer
+             * @returns {string} fixed parameter
+             */
+            function fixParamsWithParenthesis(fixer) {
+                const paramToken = sourceCode.getTokenAfter(firstTokenOfParam);
+                const closingParenToken = sourceCode.getTokenAfter(paramToken);
+                const asyncToken = isAsync ? sourceCode.getTokenBefore(firstTokenOfParam) : null;
+                const shouldAddSpaceForAsync = asyncToken && (asyncToken.end === firstTokenOfParam.start);
+
+                return fixer.replaceTextRange([
+                    firstTokenOfParam.range[0],
+                    closingParenToken.range[1]
+                ], `${shouldAddSpaceForAsync ? " " : ""}${paramToken.value}`);
+            }
 
             // "as-needed", { "requireForBlockBody": true }: x => x
             if (
@@ -68,19 +85,11 @@ module.exports = {
                 node.body.type !== "BlockStatement" &&
                 !node.returnType
             ) {
-                if (astUtils.isOpeningParenToken(token)) {
+                if (astUtils.isOpeningParenToken(firstTokenOfParam)) {
                     context.report({
                         node,
                         message: requireForBlockBodyMessage,
-                        fix(fixer) {
-                            const paramToken = context.getTokenAfter(token);
-                            const closingParenToken = context.getTokenAfter(paramToken);
-
-                            return fixer.replaceTextRange([
-                                token.range[0],
-                                closingParenToken.range[1]
-                            ], paramToken.value);
-                        }
+                        fix: fixParamsWithParenthesis
                     });
                 }
                 return;
@@ -90,12 +99,12 @@ module.exports = {
                 requireForBlockBody &&
                 node.body.type === "BlockStatement"
             ) {
-                if (!astUtils.isOpeningParenToken(token)) {
+                if (!astUtils.isOpeningParenToken(firstTokenOfParam)) {
                     context.report({
                         node,
                         message: requireForBlockBodyNoParensMessage,
                         fix(fixer) {
-                            return fixer.replaceText(token, `(${token.value})`);
+                            return fixer.replaceText(firstTokenOfParam, `(${firstTokenOfParam.value})`);
                         }
                     });
                 }
@@ -109,26 +118,18 @@ module.exports = {
                 !node.params[0].typeAnnotation &&
                 !node.returnType
             ) {
-                if (astUtils.isOpeningParenToken(token)) {
+                if (astUtils.isOpeningParenToken(firstTokenOfParam)) {
                     context.report({
                         node,
                         message: asNeededMessage,
-                        fix(fixer) {
-                            const paramToken = context.getTokenAfter(token);
-                            const closingParenToken = context.getTokenAfter(paramToken);
-
-                            return fixer.replaceTextRange([
-                                token.range[0],
-                                closingParenToken.range[1]
-                            ], paramToken.value);
-                        }
+                        fix: fixParamsWithParenthesis
                     });
                 }
                 return;
             }
 
-            if (token.type === "Identifier") {
-                const after = sourceCode.getTokenAfter(token);
+            if (firstTokenOfParam.type === "Identifier") {
+                const after = sourceCode.getTokenAfter(firstTokenOfParam);
 
                 // (x) => x
                 if (after.value !== ")") {
@@ -136,7 +137,7 @@ module.exports = {
                         node,
                         message,
                         fix(fixer) {
-                            return fixer.replaceText(token, `(${token.value})`);
+                            return fixer.replaceText(firstTokenOfParam, `(${firstTokenOfParam.value})`);
                         }
                     });
                 }
diff --git a/lib/rules/key-spacing.js b/lib/rules/key-spacing.js
index 409fb451f..03f45a6c6 100644
--- a/lib/rules/key-spacing.js
+++ b/lib/rules/key-spacing.js
@@ -634,6 +634,5 @@ module.exports = {
         };
 
 
-
     }
 };
diff --git a/lib/rules/no-debugger.js b/lib/rules/no-debugger.js
index 897b3dbb6..a158724fe 100644
--- a/lib/rules/no-debugger.js
+++ b/lib/rules/no-debugger.js
@@ -16,7 +16,7 @@ module.exports = {
             category: "Possible Errors",
             recommended: true
         },
-
+        fixable: "code",
         schema: []
     },
 
@@ -24,7 +24,13 @@ module.exports = {
 
         return {
             DebuggerStatement(node) {
-                context.report({ node, message: "Unexpected 'debugger' statement." });
+                context.report({
+                    node,
+                    message: "Unexpected 'debugger' statement.",
+                    fix(fixer) {
+                        return fixer.remove(node);
+                    }
+                });
             }
         };
 
diff --git a/lib/rules/no-extra-parens.js b/lib/rules/no-extra-parens.js
index 0a6a0d0f1..d0d79c6a3 100644
--- a/lib/rules/no-extra-parens.js
+++ b/lib/rules/no-extra-parens.js
@@ -255,6 +255,23 @@ module.exports = {
                 !astUtils.canTokensBeAdjacent(tokenBeforeLeftParen, firstToken);
         }
 
+        /**
+         * Determines whether a node should be followed by an additional space when removing parens
+         * @param {ASTNode} node node to evaluate; must be surrounded by parentheses
+         * @returns {boolean} `true` if a space should be inserted after the node
+         * @private
+         */
+        function requiresTrailingSpace(node) {
+            const nextTwoTokens = sourceCode.getTokensAfter(node, { count: 2 });
+            const rightParenToken = nextTwoTokens[0];
+            const tokenAfterRightParen = nextTwoTokens[1];
+            const tokenBeforeRightParen = sourceCode.getLastToken(node);
+
+            return rightParenToken && tokenAfterRightParen &&
+                !sourceCode.isSpaceBetweenTokens(rightParenToken, tokenAfterRightParen) &&
+                !astUtils.canTokensBeAdjacent(tokenBeforeRightParen, tokenAfterRightParen);
+        }
+
         /**
          * Report the node
          * @param {ASTNode} node node to evaluate
@@ -279,7 +296,7 @@ module.exports = {
                     return fixer.replaceTextRange([
                         leftParenToken.range[0],
                         rightParenToken.range[1]
-                    ], (requiresLeadingSpace(node) ? " " : "") + parenthesizedSource);
+                    ], (requiresLeadingSpace(node) ? " " : "") + parenthesizedSource + (requiresTrailingSpace(node) ? " " : ""));
                 }
             });
         }
@@ -488,12 +505,18 @@ module.exports = {
                 if (hasExcessParens(node.right)) {
                     report(node.right);
                 }
+                if (hasExcessParens(node.left)) {
+                    report(node.left);
+                }
             },
 
             ForOfStatement(node) {
                 if (hasExcessParens(node.right)) {
                     report(node.right);
                 }
+                if (hasExcessParens(node.left)) {
+                    report(node.left);
+                }
             },
 
             ForStatement(node) {
diff --git a/lib/rules/prefer-const.js b/lib/rules/prefer-const.js
index 595bb8a53..1395e0a8a 100644
--- a/lib/rules/prefer-const.js
+++ b/lib/rules/prefer-const.js
@@ -85,17 +85,6 @@ function getIdentifierIfShouldBeConst(variable, ignoreReadBeforeAssign) {
         return null;
     }
 
-    /*
-     * Due to a bug in acorn, code such as `let foo = 1; let foo = 2;` will not throw a syntax error. As a sanity
-     * check, make sure that the variable only has one declaration. After the parsing bug is fixed, this check
-     * will no longer be necessary, because variables declared with `let` or `const` should always have exactly one
-     * declaration.
-     * https://github.com/ternjs/acorn/issues/487
-     */
-    if (variable.defs.length > 1) {
-        return null;
-    }
-
     // Finds the unique WriteReference.
     let writer = null;
     let isReadBeforeInit = false;
diff --git a/lib/rules/semi-style.js b/lib/rules/semi-style.js
index fa258754d..97fcc3ac8 100644
--- a/lib/rules/semi-style.js
+++ b/lib/rules/semi-style.js
@@ -79,7 +79,7 @@ module.exports = {
                             : "the beginning of the next line"
                     },
                     fix(fixer) {
-                        if (commentsExistBetween(prevToken, nextToken)) {
+                        if (prevToken && nextToken && commentsExistBetween(prevToken, nextToken)) {
                             return null;
                         }
 
diff --git a/lib/util/patterns/letters.js b/lib/util/patterns/letters.js
index b212cfc9e..eb255d8f0 100644
--- a/lib/util/patterns/letters.js
+++ b/lib/util/patterns/letters.js
@@ -34,4 +34,3 @@
 "use strict";
 
 module.exports = /[A-Za-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0-\u08B2\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16F1-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA7AD\uA7B0\uA7B1\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB5F\uAB64\uAB65\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDE80-\uDE9C\uDEA0-\uDED0\uDF00-\uDF1F\uDF30-\uDF40\uDF42-\uDF49\uDF50-\uDF75\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF]|\uD801[\uDC00-\uDC9D\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00\uDE10-\uDE13\uDE15-\uDE17\uDE19-\uDE33\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE4\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48]|\uD804[\uDC03-\uDC37\uDC83-\uDCAF\uDCD0-\uDCE8\uDD03-\uDD26\uDD50-\uDD72\uDD76\uDD83-\uDDB2\uDDC1-\uDDC4\uDDDA\uDE00-\uDE11\uDE13-\uDE2B\uDEB0-\uDEDE\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D\uDF5D-\uDF61]|\uD805[\uDC80-\uDCAF\uDCC4\uDCC5\uDCC7\uDD80-\uDDAE\uDE00-\uDE2F\uDE44\uDE80-\uDEAA]|\uD806[\uDCA0-\uDCDF\uDCFF\uDEC0-\uDEF8]|\uD808[\uDC00-\uDF98]|[\uD80C\uD840-\uD868\uD86A-\uD86C][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDED0-\uDEED\uDF00-\uDF2F\uDF40-\uDF43\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDF00-\uDF44\uDF50\uDF93-\uDF9F]|\uD82C[\uDC00\uDC01]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB]|\uD83A[\uDC00-\uDCC4]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D]|\uD87E[\uDC00-\uDE1D]/;
-
diff --git a/package.json b/package.json
index 78a49ed5c..26dbdd8c8 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "eslint",
-  "version": "4.0.0-beta.0",
+  "version": "4.0.0",
   "author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
   "description": "An AST-based pattern checker for JavaScript.",
   "bin": {
@@ -37,22 +37,22 @@
     "babel-code-frame": "^6.22.0",
     "chalk": "^1.1.3",
     "concat-stream": "^1.6.0",
-    "debug": "^2.6.3",
+    "debug": "^2.6.8",
     "doctrine": "^2.0.0",
-    "eslint-scope": "^3.6.0",
-    "espree": "^3.4.2",
+    "eslint-scope": "^3.7.1",
+    "espree": "^3.4.3",
     "esquery": "^1.0.0",
     "estraverse": "^4.2.0",
     "esutils": "^2.0.2",
     "file-entry-cache": "^2.0.0",
-    "glob": "^7.1.1",
-    "globals": "^9.16.0",
-    "ignore": "^3.2.6",
+    "glob": "^7.1.2",
+    "globals": "^9.17.0",
+    "ignore": "^3.3.3",
     "imurmurhash": "^0.1.4",
     "inquirer": "^3.0.6",
     "is-my-json-valid": "^2.16.0",
     "is-resolvable": "^1.0.0",
-    "js-yaml": "^3.8.2",
+    "js-yaml": "^3.8.4",
     "json-stable-stringify": "^1.0.1",
     "levn": "^0.3.0",
     "lodash": "^4.17.4",
@@ -61,7 +61,7 @@
     "optionator": "^0.8.2",
     "path-is-inside": "^1.0.2",
     "pluralize": "^4.0.0",
-    "progress": "^1.1.8",
+    "progress": "^2.0.0",
     "require-uncached": "^1.0.3",
     "strip-json-comments": "~2.0.1",
     "table": "^4.0.1",
@@ -69,40 +69,40 @@
   },
   "devDependencies": {
     "babel-polyfill": "^6.23.0",
-    "babel-preset-es2015": "^6.24.0",
+    "babel-preset-es2015": "^6.24.1",
     "babelify": "^7.3.0",
     "beefy": "^2.1.8",
     "brfs": "1.4.3",
-    "browserify": "^14.1.0",
-    "chai": "^3.5.0",
+    "browserify": "^14.4.0",
+    "chai": "^4.0.1",
     "cheerio": "^0.22.0",
-    "coveralls": "^2.12.0",
+    "coveralls": "^2.13.1",
     "dateformat": "^2.0.0",
     "ejs": "^2.5.6",
-    "eslint-plugin-eslint-plugin": "^0.7.1",
-    "eslint-plugin-node": "^4.2.1",
+    "eslint-plugin-eslint-plugin": "^0.7.2",
+    "eslint-plugin-node": "^5.0.0",
     "eslint-release": "^0.10.1",
     "esprima": "^3.1.3",
     "esprima-fb": "^15001.1001.0-dev-harmony-fb",
     "istanbul": "^0.4.5",
     "jsdoc": "^3.4.3",
-    "karma": "^1.5.0",
+    "karma": "^1.7.0",
     "karma-babel-preprocessor": "^6.0.1",
     "karma-mocha": "^1.3.0",
-    "karma-mocha-reporter": "^2.2.2",
+    "karma-mocha-reporter": "^2.2.3",
     "karma-phantomjs-launcher": "^1.0.4",
     "leche": "^2.1.2",
     "load-perf": "^0.2.0",
-    "markdownlint": "^0.4.0",
-    "mocha": "^3.2.0",
-    "mock-fs": "^4.2.0",
+    "markdownlint": "^0.5.0",
+    "mocha": "^3.4.2",
+    "mock-fs": "^4.3.0",
     "npm-license": "^0.3.3",
     "phantomjs-prebuilt": "^2.1.14",
-    "proxyquire": "^1.7.11",
+    "proxyquire": "^1.8.0",
     "semver": "^5.3.0",
     "shelljs": "^0.7.7",
     "shelljs-nodecli": "~0.1.1",
-    "sinon": "^2.0.0",
+    "sinon": "^2.3.2",
     "temp": "^0.8.3",
     "through": "^2.3.8"
   },
diff --git a/packages/eslint-config-eslint/default.yml b/packages/eslint-config-eslint/default.yml
index 815a82bdd..9180f7525 100644
--- a/packages/eslint-config-eslint/default.yml
+++ b/packages/eslint-config-eslint/default.yml
@@ -36,7 +36,6 @@ rules:
     }]
     max-statements-per-line: "error"
     new-cap: "error"
-    newline-after-var: "error"
     new-parens: "error"
     no-alert: "error"
     no-array-constructor: "error"
@@ -61,7 +60,7 @@ rules:
     no-mixed-spaces-and-tabs: ["error", false]
     no-multi-spaces: "error"
     no-multi-str: "error"
-    no-native-reassign: "off"
+    no-multiple-empty-lines: ["error", {max: 2, maxBOF: 0, maxEOF: 0}]
     no-nested-ternary: "error"
     no-new: "error"
     no-new-func: "error"
@@ -100,6 +99,19 @@ rules:
     object-shorthand: "error"
     one-var-declaration-per-line: "error"
     operator-linebreak: "error"
+    padding-line-between-statements: [
+        "error",
+        {
+            blankLine: "always",
+            prev: ["const", "let", "var"],
+            next: "*"
+        },
+        {
+            blankLine: "any",
+            prev: ["const", "let", "var"],
+            next: ["const", "let", "var"]
+        }
+    ]
     prefer-arrow-callback: "error"
     prefer-const: "error"
     prefer-promise-reject-errors: "error"


Test patch:
diff --git a/lib/testers/rule-tester.js b/lib/testers/rule-tester.js
index a0df6e05b..d66cd175a 100644
--- a/lib/testers/rule-tester.js
+++ b/lib/testers/rule-tester.js
@@ -456,7 +456,6 @@ class RuleTester {
             const messages = result.messages;
 
 
-
             if (typeof item.errors === "number") {
                 assert.equal(messages.length, item.errors, util.format("Should have %d error%s but had %d: %s",
                     item.errors, item.errors === 1 ? "" : "s", messages.length, util.inspect(messages)));
diff --git a/tests/lib/config.js b/tests/lib/config.js
index a2acd0380..1942a1c08 100644
--- a/tests/lib/config.js
+++ b/tests/lib/config.js
@@ -725,7 +725,6 @@ describe("Config", () => {
         });
 
 
-
         it("should load user config globals", () => {
             const configPath = path.resolve(__dirname, "..", "fixtures", "globals", "conf.yaml"),
                 configHelper = new Config({ configFile: configPath, useEslintrc: false }, linter);
diff --git a/tests/lib/eslint.js b/tests/lib/eslint.js
index 7366484c5..b01069aac 100644
--- a/tests/lib/eslint.js
+++ b/tests/lib/eslint.js
@@ -469,7 +469,6 @@ describe("eslint", () => {
     });
 
 
-
     describe("when calling getScope", () => {
         const code = "function foo() { q: for(;;) { break q; } } function bar () { var q = t; } var baz = (() => { return 1; });";
 
diff --git a/tests/lib/ignored-paths.js b/tests/lib/ignored-paths.js
index 07d96672d..1631d37f4 100644
--- a/tests/lib/ignored-paths.js
+++ b/tests/lib/ignored-paths.js
@@ -164,6 +164,30 @@ describe("IgnoredPaths", () => {
                 )
             );
         });
+
+
+    });
+
+    describe("caching file reads", () => {
+
+        let readFileSyncCount;
+
+        before(() => {
+            readFileSyncCount = sinon.spy(fs, "readFileSync");
+        });
+
+        after(() => {
+            readFileSyncCount.restore();
+        });
+
+        it("should cache readFileSync on same file paths", () => {
+            const ignoreFilePath = getFixturePath(".eslintignore");
+            const ignoredPaths = new IgnoredPaths({ ignore: true, cwd: getFixturePath() });
+
+            ignoredPaths.readIgnoreFile(ignoreFilePath);
+            assert.isTrue(ignoredPaths.contains(ignoreFilePath));
+            sinon.assert.calledOnce(readFileSyncCount);
+        });
     });
 
     describe("initialization with ignorePattern", () => {
diff --git a/tests/lib/linter.js b/tests/lib/linter.js
index ed5fb2238..9c47fafe5 100644
--- a/tests/lib/linter.js
+++ b/tests/lib/linter.js
@@ -70,4 +70,18 @@ describe("Linter", () => {
             });
         });
     });
+
+    describe("verifyAndFix", () => {
+        it("Fixes the code", () => {
+            const linter = new Linter();
+            const messages = linter.verifyAndFix("var a", {
+                rules: {
+                    semi: 2
+                }
+            }, { filename: "test.js" });
+
+            assert.equal(messages.output, "var a;", "Fixes were applied correctly");
+            assert.isTrue(messages.fixed);
+        });
+    });
 });
diff --git a/tests/lib/rules/arrow-parens.js b/tests/lib/rules/arrow-parens.js
index 2bd370f35..a209d08c3 100644
--- a/tests/lib/rules/arrow-parens.js
+++ b/tests/lib/rules/arrow-parens.js
@@ -176,6 +176,18 @@ const invalid = [
             type
         }]
     },
+    {
+        code: "async(a) => a",
+        output: "async a => a",
+        options: ["as-needed"],
+        parserOptions: { ecmaVersion: 8 },
+        errors: [{
+            line: 1,
+            column: 1,
+            message: asNeededMessage,
+            type
+        }]
+    },
 
     // "as-needed", { "requireForBlockBody": true }
     {
@@ -223,6 +235,18 @@ const invalid = [
             message: requireForBlockBodyMessage,
             type
         }]
+    },
+    {
+        code: "async(a) => a",
+        output: "async a => a",
+        options: ["as-needed", { requireForBlockBody: true }],
+        parserOptions: { ecmaVersion: 8 },
+        errors: [{
+            line: 1,
+            column: 1,
+            message: requireForBlockBodyMessage,
+            type
+        }]
     }
 ];
 
diff --git a/tests/lib/rules/no-debugger.js b/tests/lib/rules/no-debugger.js
index 3c3b082f6..a907405c8 100644
--- a/tests/lib/rules/no-debugger.js
+++ b/tests/lib/rules/no-debugger.js
@@ -23,6 +23,6 @@ ruleTester.run("no-debugger", rule, {
         "var test = { debugger: 1 }; test.debugger;"
     ],
     invalid: [
-        { code: "debugger", errors: [{ message: "Unexpected 'debugger' statement.", type: "DebuggerStatement" }] }
+        { code: "debugger", errors: [{ message: "Unexpected 'debugger' statement.", type: "DebuggerStatement" }], output: "" }
     ]
 });
diff --git a/tests/lib/rules/no-extra-parens.js b/tests/lib/rules/no-extra-parens.js
index b30c42d86..fb69eb713 100644
--- a/tests/lib/rules/no-extra-parens.js
+++ b/tests/lib/rules/no-extra-parens.js
@@ -41,7 +41,6 @@ function invalid(code, output, type, line, config) {
     if (line) {
         result.errors[0].line = line;
     }
-
     return result;
 }
 
@@ -996,6 +995,27 @@ ruleTester.run("no-extra-parens", rule, {
             1,
             { parserOptions: { ecmaVersion: 2015 } }
         ),
+        invalid(
+            "for ((foo) of bar);",
+            "for (foo of bar);",
+            "Identifier",
+            1,
+            { parserOptions: { ecmaVersion: 2015 } }
+        ),
+        invalid(
+            "for ((foo)in bar);",
+            "for (foo in bar);",
+            "Identifier",
+            1,
+            { parserOptions: { ecmaVersion: 2015 } }
+        ),
+        invalid(
+            "for ((foo['bar'])of baz);",
+            "for (foo['bar']of baz);",
+            "MemberExpression",
+            1,
+            { parserOptions: { ecmaVersion: 2015 } }
+        ),
         invalid(
             "() => (({ foo: 1 }).foo)",
             "() => ({ foo: 1 }).foo",
diff --git a/tests/lib/rules/no-restricted-globals.js b/tests/lib/rules/no-restricted-globals.js
index 4041ebec0..df9d6e4ff 100644
--- a/tests/lib/rules/no-restricted-globals.js
+++ b/tests/lib/rules/no-restricted-globals.js
@@ -63,4 +63,3 @@ ruleTester.run("no-restricted-globals", rule, {
         }
     ]
 });
-
diff --git a/tests/lib/rules/semi-style.js b/tests/lib/rules/semi-style.js
index a0281bff7..43968facf 100644
--- a/tests/lib/rules/semi-style.js
+++ b/tests/lib/rules/semi-style.js
@@ -113,6 +113,12 @@ ruleTester.run("semi-style", rule, {
             options: ["last"],
             errors: ["Expected this semicolon to be at the end of the previous line."]
         },
+        {
+            code: "foo()\n;",
+            output: "foo();\n",
+            options: ["last"],
+            errors: ["Expected this semicolon to be at the end of the previous line."]
+        },
 
         {
             code: "foo;\nbar",