Repository: eslint/eslint
Pull request #8422 (2017-04-07 03:34:23)

Chore: add a fuzzer to detect bugs in core rules
**What is the purpose of this pull request? (put an "X" next to item)**

[x] 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)**

This commit adds a fuzzer to detect bugs in core rules. The fuzzer can detect two types of problems: crashes (where a rule throws an error given a certain syntax) and autofix errors (where an autofix results in a syntax error).

The fuzzer works by running eslint on randomly-generated code with a random config. The code is generated with [eslump](https://github.com/lydell/eslump), and the config is generated with the existing autoconfig logic.

I've been using and developing this fuzzer locally for the past few weeks. It has been immensely helpful for finding bugs in rules. (It discovered the bugs fixed by https://github.com/eslint/eslint/pull/8245, https://github.com/eslint/eslint/pull/8246, https://github.com/eslint/eslint/pull/8247, https://github.com/eslint/eslint/pull/8327, https://github.com/eslint/eslint/pull/8328, https://github.com/eslint/eslint/pull/8330, https://github.com/eslint/eslint/pull/8331, https://github.com/eslint/eslint/pull/8332, https://github.com/eslint/eslint/pull/8335, https://github.com/eslint/eslint/pull/8337, https://github.com/eslint/eslint/pull/8338, https://github.com/eslint/eslint/pull/8339, https://github.com/eslint/eslint/pull/8340, https://github.com/eslint/eslint/pull/8358, https://github.com/eslint/eslint/pull/8359, https://github.com/eslint/eslint/pull/8391, https://github.com/eslint/eslint/pull/8394, https://github.com/eslint/eslint/pull/8412, https://github.com/eslint/eslint/pull/8806, https://github.com/eslint/eslint/pull/8807, and https://github.com/eslint/eslint/pull/8808.)

The fuzzer can be run with `npm run fuzz`. Eventually, I think we should add the fuzzer to the normal `npm test` build. Right now it's still reporting a few errors sometimes, which we should probably investigate, but we shouldn't let that break the regular build.

**Is there anything you'd like reviewers to focus on?**

* I wasn't sure where to put the fuzzer, so I created a new top-level directory called `tools/`. Let me know if there's a better place to put it.
* The fuzzer outputs debugging info when it fails. Right now, the info is output to a top-level `debug/` folder, which is in gitignore. Let me know if there's a better place to put it.

mention-bot: @not-an-aardvark, thanks for your PR! By analyzing the history of the files in this pull request, we identified @nzakas, @IanVS and @kaicataldo to be potential reviewers.
eslintbot: LGTM
not-an-aardvark: It looks like the tests are failing because `eslump` only supports Node 6+.

Given that this is just an internal debugging tool and isn't exposed anywhere, I think the easiest thing to do would be to skip the tests for it when the Node version is less than 6.
ilyavolodin: Would it better as a stand-alone tool, instead of embedding it into ESLint project?
platinumazure: :+1: to standalone tool-- we could still use it in ESLint by consuming it as a devDependency and adding an npm script.
not-an-aardvark: Could you explain the advantage of making it a standalone tool? To me, it seems like it better to add it to eslint's repo directly because:

* I think it should be easy to run it while developing eslint (and eventually it should be run as part of the tests). It has also been very useful for finding other eslint bugs, as discussed above.
* It uses a few private ESLint APIs
* If its main user is the eslint project, it would be useful for the eslint team to be able to make modifications to it.
lydell: Hi! I’m the creator of [eslump](https://github.com/lydell/eslump). I’m glad that eslump has helped you find and fix problems in ESLint. But I’m also a little worried.

eslump has only ever been intended to be a CLI tool. The fact that `index.js` exposes `generateRandomJS` is mostly by accident, and has stuck around since then.

eslump was created from the need of finding edge cases in [prettier](https://github.com/prettier/prettier/). It started out as a bare-bones little script in a branch on my fork of that repo. As I wanted more and more features, I extracted it and fleshed it out in its own repo. Then I realized that it might be useful to others, so I put it on github and made the CLI installable from npm.

Initially, eslump basically just strung together [shift-fuzzer-js](https://github.com/shapesecurity/shift-fuzzer-js) and [shift-codegen-js](https://github.com/shapesecurity/shift-codegen-js). Then, I realized that no random comments were generated, so I hacked that in (along with random whitespace) since comments are very difficult to get right in prettier. Then, @not-an-aardvark requested random parentheses and semicolons (lydell/eslump#1), so I hacked that in as well.

I have an ambitious plan in the back of my head to create a stand-alone library that generates random JS (including flow and JSX), but I’m not sure when (or if) I’ll get around doing that.

Anyway, since eslump only was intended to be a CLI tool (and its “public” API isn’t even documented), is a bit hacky and doesn’t even have tests (I’ve just gone meta and fuzz-tested it using itself basically), I feel worried about ESLint’s testing infrastructure depending on it. Therefore, I’d recommend one of these:

- Pin eslump to a fixed version in `package.json`.
- Copy [codegen.js](https://github.com/lydell/eslump/blob/master/codegen.js) from eslump to somewhere appropriate.
eslintbot: LGTM
eslintbot: LGTM
not-an-aardvark: @lydell Thanks for your comment -- I hadn't realized that `generateRandomJS` wasn't supposed to be exposed. For now, I've pinned the `eslump` version in this PR.
kaicataldo: FWIW, I'm fine with this being in ESLint core.
kaicataldo: How do we want to proceed with this? This seems like a very useful tool, and I'd love to see it implemented so we can all use it (though I don't personally feel strongly about it being in core vs a standalone tool).
not-an-aardvark: I think the main blocker at the moment is that eslump, the random code generator used by the fuzzer, doesn't support Node 4. It seems like our options are to either (a) require Node 6 for the fuzzer and skip the fuzzer tests in Node 4, or (b) make a PR/fork of eslump to support Node 4.
kaicataldo: @not-an-aardvark If we just made it a standalone tool that we use for development purposes, would that suffice? That way we don't necessarily need to worry about running it in Node 4, right?
not-an-aardvark: I think we could accomplish that even without needing to make it a standalone tool. Right now it's run as `npm run fuzz`, so we could just print an error message and exit if someone runs `npm run fuzz` on Node 4.

There are also a few tests for the fuzzer, which we could skip in Node 4.

In my mind, the advantages of having this in the ESLint repo are that (a) it's very useful for developing ESLint, and (b) it uses internal ESLint APIs, so we would know to update it if something changes internally. The disadvantage would be that we might have to conditionally run a test file, which seems slightly messy.
ilyavolodin: @not-an-aardvark another disadvantage is more code for users to download and for us to maintain. I still think that standalone tool that we would run nightly in jenkins would be a better choice.
not-an-aardvark: @ilyavolodin I disagree. This would not result in any more code for users to download -- it's excluded from the package when publishing. Also, if we were running it on jenkins then we would have to maintain it regardless of whether it was part of the repo or a separate tool, so I don't think putting it in the repo makes it harder for us to maintain.

Since it uses internal APIs, I think making it a separate tool would actually cause more maintenance work, because we would accidentally break it when changing the APIs. The fuzzer is clearly useful for finding bugs in rules, so to me, it seems like it's definitely worth adding.

Just to be clear (you might already be aware of these, but I'm restating them to make sure):

* The fuzzer is an internal-only tool -- this is just for ESLint and doesn't add anything to our API.
* The fuzzer currently does not get run as part of the unit tests -- it's just an opt-in check with `npm run fuzz`.
* In its current state, the fuzzer only makes sense for testing core ESLint rules -- it doesn't allow other rules to be tested.
* The fuzzer has been shown to be very useful for finding bugs in rules.
platinumazure: I would love to see it as a standalone tool so that it could be augmented
for testing plugin rules eventually. It would be awesome to be able to
offer it as a service for plugin or parser developers, and having it
bundled in ESLint as part of the public API feels like it bloats ESLint.
Just my two cents.

On Jun 21, 2017 8:58 PM, "Teddy Katz" <notifications@github.com> wrote:

> @ilyavolodin <https://github.com/ilyavolodin> I disagree. This would not
> result in any more code for users to download -- it's excluded from the
> package when publishing. Also, if we were running it on jenkins then we
> would have to maintain it regardless of whether it was part of the repo or
> a separate tool, so I don't think putting it in the repo makes it harder
> for us to maintain.
>
> Since it uses internal APIs, I think making it a separate tool would
> actually cause more maintenance work, because we would accidentally break
> it when changing the APIs. The fuzzer is clearly useful for finding bugs in
> rules, so to me, it seems like it's definitely worth adding.
>
> Just to be clear (you might already be aware of these, but I'm restating
> them to make sure):
>
>    - The fuzzer is an internal-only tool -- this is just for ESLint and
>    doesn't add anything to our API.
>    - The fuzzer currently does not get run as part of the unit tests --
>    it's just an opt-in check with npm run fuzz.
>    - In its current state, the fuzzer only makes sense for testing core
>    ESLint rules -- it doesn't allow other rules to be tested.
>    - The fuzzer has been shown to be very useful for finding bugs in
>    rules.
>
> —
> You are receiving this because you commented.
> Reply to this email directly, view it on GitHub
> <https://github.com/eslint/eslint/pull/8422#issuecomment-310254203>, or mute
> the thread
> <https://github.com/notifications/unsubscribe-auth/AARWesSyy52dCOFU8o4csQmPRQzgr52Sks5sGcpWgaJpZM4M2a5V>
> .
>

lydell: If it helps, I guess I can add Node.js 4 support to eslump. Tell me if you want me to take a look at it!
ilyavolodin: @not-an-aardvark Hmm.. Fair point. I forgot that we can exclude the code from the distribution. In that case, I guess I don't have a strong argument against merging this in (other then failing builds:-)
lydell: Thanks to @not-an-aardvark, eslump 1.6.0 now has Node.js 4 support! It now also explicitly supports `var generateRanomJS = require('eslump').generateRanomJS`. (I've moved all the CLI stuff out of index.js, so there shouldn't be any overhead from loading unnecessary CLI modules.) The disclaimer about no tests is still there, so it's up to you to choose if you want to pin the version number or not. But nothing's changed in eslump the past 2-3 months, and there are no changes planned either.
lydell: Thanks to @not-an-aardvark, eslump 1.6.0 now has Node.js 4 support! It now also explicitly supports `var generateRanomJS = require('eslump').generateRanomJS`. (I've moved all the CLI stuff out of index.js, so there shouldn't be any overhead from loading unnecessary CLI modules.) The disclaimer about no tests is still there, so it's up to you to choose if you want to pin the version number or not. But nothing's changed in eslump the past 2-3 months, and there are no changes planned either.
eslintbot: LGTM

Patch:
diff --git a/.eslintrc.yml b/.eslintrc.yml
index c857caa67..645e328d9 100644
--- a/.eslintrc.yml
+++ b/.eslintrc.yml
@@ -9,5 +9,6 @@ extends:
     - "plugin:eslint-plugin/recommended"
 rules:
     eslint-plugin/consistent-output: "error"
+    eslint-plugin/no-identical-tests: "error"
     eslint-plugin/prefer-placeholders: "error"
     eslint-plugin/report-message-format: ["error", '[^a-z].*\.$']
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
index 8675e4491..16822fd97 100644
--- a/.github/ISSUE_TEMPLATE.md
+++ b/.github/ISSUE_TEMPLATE.md
@@ -20,14 +20,18 @@
 
 **Please show your full configuration:**
 
-<!-- Paste your configuration below: -->
-```
+<details>
+<summary>Configuration</summary>
 
+<!-- Paste your configuration below: -->
+```js
 
 
 
 ```
 
+</details>
+
 **What did you do? Please include the actual source code causing the issue.**
 
 <!-- Paste the source code below: -->
diff --git a/.gitignore b/.gitignore
index 400e6646a..eae2b21d1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,7 @@ build/
 npm-debug.log
 .DS_Store
 tmp/
+debug/
 .idea
 jsdoc/
 versions.json
diff --git a/.travis.yml b/.travis.yml
index 6b75ef05e..497ae06ca 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -6,9 +6,15 @@ node_js:
     - "7"
     - "8"
 sudo: false
-script: "npm test && npm run docs"
+branches:
+    only:
+    - master
+
+# Run npm test always
+script:
+    - "npm test"
 after_success:
-    - npm run coveralls
+    - 'if [ "$node_js" = "8" ]; then npm run coveralls; fi'
 addons:
     code_climate:
         repo_token: 1945f7420d920a59f1ff8bf8d1a7b60ccd9e2838a692f73a5a74accd8df30146
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5e017bec8..3866a56b3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,42 @@
+v4.2.0 - July 8, 2017
+
+* e0f0101 Update: fix indentation of nested function parameters (fixes #8892) (#8900) (Teddy Katz)
+* 9f95a3e Chore: remove unused helper method from `indent` (#8901) (Teddy Katz)
+* 11ffe6b Fix: no-regex-spaces rule incorrectly fixes quantified spaces (#8773) (Keri Warr)
+* 975dacf Update: fix indentation of EmptyStatements (fixes #8882) (#8885) (Teddy Katz)
+* 88ed041 Build: Turnoff CI branch build (fixes #8804) (#8873) (Gyandeep Singh)
+* 72f22eb Chore: replace is-my-json-valid with Ajv (#8852) (Gajus Kuizinas)
+* 7c8de92 Docs: Clarified PR guidelines in maintainer guide (#8876) (Kevin Partington)
+* d1fc408 Docs: Update CLA link in Contributing docs (#8883) (Calvin Freitas)
+* 931a9f1 Fix: indent false positive with multi-line await expression (#8837) (薛定谔的猫)
+* 3767cda Update: add no-sync option to allow at root level (fixes #7985) (#8859) (Victor Hom)
+* 1ce553d Docs: Fix wording of minProperties in object-curly-newline (fixes #8874) (#8878) (solmsted)
+* f00854e Fix: --quiet no longer fixes warnings (fixes #8675) (#8858) (Kevin Partington)
+* b678535 Chore: Add collapsible block for config in ISSUE_TEMPLATE (#8872) (Gyandeep Singh)
+* 1f5bfc2 Update: Add always-multiline option to multiline-ternary (fixes #8770) (#8841) (Nathan Woltman)
+* 22116f2 Fix: correct comma-dangle JSON schema (#8864) (Evgeny Poberezkin)
+* 676af9e Update: fix indentation of JSXExpressionContainer contents (fixes #8832) (#8850) (Teddy Katz)
+* 330dd58 Chore: fix title of linter test suite (#8861) (Teddy Katz)
+* 60099ed Chore: enable for-direction rule on ESLint codebase (#8853) (薛定谔的猫)
+* e0d1a84 Chore: upgrade eslint-plugin-eslint-plugin & eslint-plugin-node (#8856) (薛定谔的猫)
+* 0780d86 Chore: remove identical tests (#8851) (Teddy Katz)
+* 5c3ac8e Fix: arrow-parens fixer gets tripped up with trailing comma in args (#8838) (薛定谔的猫)
+* c4f2e29 Build: fix race condition in demo (#8827) (Teddy Katz)
+* c693be5 New: Allow passing a function as `fix` option (fixes #8039) (#8730) (Ian VanSchooten)
+* 8796d55 Docs: add missing item to 4.0 migration guide table of contents (#8835) (薛定谔的猫)
+* 742998c doc md update: false -> `false` (#8825) (Erik Vold)
+* ce969f9 Docs: add guidelines for patch release communication (fixes #7277) (#8823) (Teddy Katz)
+* 5c83c99 Docs: Clarify arrow function parens in no-extra-parens (fixes #8741) (#8822) (Kevin Partington)
+* 84d921d Docs: Added note about Node/CJS scoping to no-redeclare (fixes #8814) (#8820) (Kevin Partington)
+* 85c9327 Update: fix parenthesized CallExpression indentation (fixes #8790) (#8802) (Teddy Katz)
+* be8d354 Update: simplify variable declarator indent handling (fixes #8785) (#8801) (Teddy Katz)
+* 9417818 Fix: no-debugger autofixer produced invalid syntax (#8806) (Teddy Katz)
+* 8698a92 New: getter-return rule (fixes #8449) (#8460) (薛定谔的猫)
+* eac06f2 Fix: no-extra-parens false positives for variables called "let" (#8808) (Teddy Katz)
+* 616587f Fix: dot-notation autofix produces syntax errors for object called "let" (#8807) (Teddy Katz)
+* a53ef7e Fix: don't require a third argument in linter.verifyAndFix (fixes #8805) (#8809) (Teddy Katz)
+* 5ad8b70 Docs: add minor formatting improvement to paragraph about parsers (#8816) (Teddy Katz)
+
 v4.1.1 - June 25, 2017
 
 * f307aa0 Fix: ensure configs from a plugin are cached separately (fixes #8792) (#8798) (Teddy Katz)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index ffe6b574c..3826c28e8 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -17,7 +17,7 @@ Before filing an issue, please be sure to read the guidelines for what you're re
 
 ## Contributing Code
 
-Please sign our [Contributor License Agreement](https://contribute.jquery.org/CLA/) and read over the [Pull Request Guidelines](http://eslint.org/docs/developer-guide/contributing/pull-requests).
+Please sign our [Contributor License Agreement](https://js.foundation/CLA) and read over the [Pull Request Guidelines](http://eslint.org/docs/developer-guide/contributing/pull-requests).
 
 ## Full Documentation
 
diff --git a/Makefile.js b/Makefile.js
index 87739b269..3e04619d1 100644
--- a/Makefile.js
+++ b/Makefile.js
@@ -51,6 +51,7 @@ const OPEN_SOURCE_LICENSES = [
 const NODE = "node ", // intentional extra space
     NODE_MODULES = "./node_modules/",
     TEMP_DIR = "./tmp/",
+    DEBUG_DIR = "./debug/",
     BUILD_DIR = "./build/",
     DOCS_DIR = "../eslint.github.io/docs",
     SITE_DIR = "../eslint.github.io/",
@@ -62,7 +63,7 @@ const NODE = "node ", // intentional extra space
 
     // Files
     MAKEFILE = "./Makefile.js",
-    JS_FILES = "\"lib/**/*.js\" \"conf/**/*.js\" \"bin/**/*.js\"",
+    JS_FILES = "\"lib/**/*.js\" \"conf/**/*.js\" \"bin/**/*.js\" \"tools/**/*.js\"",
     JSON_FILES = find("conf/").filter(fileType("json")),
     MARKDOWN_FILES_ARRAY = find("docs/").concat(ls(".")).filter(fileType("md")),
     TEST_FILES = getTestFilePatterns(),
@@ -86,16 +87,12 @@ const NODE = "node ", // intentional extra space
  * @private
  */
 function getTestFilePatterns() {
-    const testLibPath = "tests/lib/",
-        testTemplatesPath = "tests/templates/",
-        testBinPath = "tests/bin/";
-
-    return ls(testLibPath).filter(pathToCheck => test("-d", testLibPath + pathToCheck)).reduce((initialValue, currentValues) => {
+    return ls("tests/lib/").filter(pathToCheck => test("-d", `tests/lib/${pathToCheck}`)).reduce((initialValue, currentValues) => {
         if (currentValues !== "rules") {
-            initialValue.push(`"${testLibPath + currentValues}/**/*.js"`);
+            initialValue.push(`"tests/lib/${currentValues}/**/*.js"`);
         }
         return initialValue;
-    }, [`"${testLibPath}rules/**/*.js"`, `"${testLibPath}*.js"`, `"${testTemplatesPath}*.js"`, `"${testBinPath}**/*.js"`]).join(" ");
+    }, ["tests/lib/rules/**/*.js", "tests/lib/*.js", "tests/templates/*.js", "tests/bin/**/*.js", "tests/tools/*.js"]).join(" ");
 }
 
 /**
@@ -543,6 +540,42 @@ target.lint = function() {
     }
 };
 
+target.fuzz = function() {
+    const fuzzerRunner = require("./tools/fuzzer-runner");
+    const fuzzResults = fuzzerRunner.run({ amount: process.env.CI ? 1000 : 300 });
+
+    if (fuzzResults.length) {
+        echo(`The fuzzer reported ${fuzzResults.length} error${fuzzResults.length === 1 ? "" : "s"}.`);
+
+        const formattedResults = JSON.stringify({ results: fuzzResults }, null, 4);
+
+        if (process.env.CI) {
+            echo("More details can be found below.");
+            echo(formattedResults);
+        } else {
+            if (!test("-d", DEBUG_DIR)) {
+                mkdir(DEBUG_DIR);
+            }
+
+            let fuzzLogPath;
+            let fileSuffix = 0;
+
+            // To avoid overwriting any existing fuzzer log files, append a numeric suffix to the end of the filename.
+            do {
+                fuzzLogPath = path.join(DEBUG_DIR, `fuzzer-log-${fileSuffix}.json`);
+                fileSuffix++;
+            } while (test("-f", fuzzLogPath));
+
+            formattedResults.to(fuzzLogPath);
+
+            // TODO: (not-an-aardvark) Create a better way to isolate and test individual fuzzer errors from the log file
+            echo(`More details can be found in ${fuzzLogPath}.`);
+        }
+
+        exit(1);
+    }
+};
+
 target.test = function() {
     target.lint();
     target.checkRuleFiles();
@@ -758,13 +791,12 @@ target.browserify = function() {
 
     // 5. browserify the temp directory
     nodeCLI.exec("browserify", "-x espree", `${TEMP_DIR}linter.js`, "-o", `${BUILD_DIR}eslint.js`, "-s eslint", "--global-transform [ babelify --presets [ es2015 ] ]");
-    nodeCLI.exec("browserify", "-x espree", `${TEMP_DIR}rules.js`, "-o", `${TEMP_DIR}rules.js`, "-s rules", "--global-transform [ babelify --presets [ es2015 ] ]");
 
     // 6. Browserify espree
     nodeCLI.exec("browserify", "-r espree", "-o", `${TEMP_DIR}espree.js`);
 
     // 7. Concatenate Babel polyfill, Espree, and ESLint files together
-    cat("./node_modules/babel-polyfill/dist/polyfill.js", `${TEMP_DIR}espree.js`, `${BUILD_DIR}eslint.js`, `${TEMP_DIR}rules.js`).to(`${BUILD_DIR}eslint.js`);
+    cat("./node_modules/babel-polyfill/dist/polyfill.js", `${TEMP_DIR}espree.js`, `${BUILD_DIR}eslint.js`).to(`${BUILD_DIR}eslint.js`);
 
     // 8. remove temp directory
     rm("-r", TEMP_DIR);
diff --git a/appveyor.yml b/appveyor.yml
index e0e77131a..e424a345b 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -9,6 +9,10 @@ environment:
   matrix:
     - nodejs_version: 4
 
+branches:
+  only:
+    - master
+
 install:
   # Get the latest stable version of Node.js
   - ps: Install-Product node $env:nodejs_version
diff --git a/conf/eslint-recommended.js b/conf/eslint-recommended.js
index 0d8d19e44..1a2e2f8e7 100755
--- a/conf/eslint-recommended.js
+++ b/conf/eslint-recommended.js
@@ -48,6 +48,7 @@ module.exports = {
         "func-names": "off",
         "func-style": "off",
         "generator-star-spacing": "off",
+        "getter-return": "off",
         "global-require": "off",
         "guard-for-in": "off",
         "handle-callback-err": "off",
diff --git a/conf/json-schema-schema.json b/conf/json-schema-schema.json
deleted file mode 100644
index 85eb502a6..000000000
--- a/conf/json-schema-schema.json
+++ /dev/null
@@ -1,150 +0,0 @@
-{
-    "id": "http://json-schema.org/draft-04/schema#",
-    "$schema": "http://json-schema.org/draft-04/schema#",
-    "description": "Core schema meta-schema",
-    "definitions": {
-        "schemaArray": {
-            "type": "array",
-            "minItems": 1,
-            "items": { "$ref": "#" }
-        },
-        "positiveInteger": {
-            "type": "integer",
-            "minimum": 0
-        },
-        "positiveIntegerDefault0": {
-            "allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ]
-        },
-        "simpleTypes": {
-            "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ]
-        },
-        "stringArray": {
-            "type": "array",
-            "items": { "type": "string" },
-            "minItems": 1,
-            "uniqueItems": true
-        }
-    },
-    "type": "object",
-    "properties": {
-        "id": {
-            "type": "string",
-            "format": "uri"
-        },
-        "$schema": {
-            "type": "string",
-            "format": "uri"
-        },
-        "title": {
-            "type": "string"
-        },
-        "description": {
-            "type": "string"
-        },
-        "default": {},
-        "multipleOf": {
-            "type": "number",
-            "minimum": 0,
-            "exclusiveMinimum": true
-        },
-        "maximum": {
-            "type": "number"
-        },
-        "exclusiveMaximum": {
-            "type": "boolean",
-            "default": false
-        },
-        "minimum": {
-            "type": "number"
-        },
-        "exclusiveMinimum": {
-            "type": "boolean",
-            "default": false
-        },
-        "maxLength": { "$ref": "#/definitions/positiveInteger" },
-        "minLength": { "$ref": "#/definitions/positiveIntegerDefault0" },
-        "pattern": {
-            "type": "string",
-            "format": "regex"
-        },
-        "additionalItems": {
-            "anyOf": [
-                { "type": "boolean" },
-                { "$ref": "#" }
-            ],
-            "default": {}
-        },
-        "items": {
-            "anyOf": [
-                { "$ref": "#" },
-                { "$ref": "#/definitions/schemaArray" }
-            ],
-            "default": {}
-        },
-        "maxItems": { "$ref": "#/definitions/positiveInteger" },
-        "minItems": { "$ref": "#/definitions/positiveIntegerDefault0" },
-        "uniqueItems": {
-            "type": "boolean",
-            "default": false
-        },
-        "maxProperties": { "$ref": "#/definitions/positiveInteger" },
-        "minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" },
-        "required": { "$ref": "#/definitions/stringArray" },
-        "additionalProperties": {
-            "anyOf": [
-                { "type": "boolean" },
-                { "$ref": "#" }
-            ],
-            "default": {}
-        },
-        "definitions": {
-            "type": "object",
-            "additionalProperties": { "$ref": "#" },
-            "default": {}
-        },
-        "properties": {
-            "type": "object",
-            "additionalProperties": { "$ref": "#" },
-            "default": {}
-        },
-        "patternProperties": {
-            "type": "object",
-            "additionalProperties": { "$ref": "#" },
-            "default": {}
-        },
-        "dependencies": {
-            "type": "object",
-            "additionalProperties": {
-                "anyOf": [
-                    { "$ref": "#" },
-                    { "$ref": "#/definitions/stringArray" }
-                ]
-            }
-        },
-        "enum": {
-            "type": "array",
-            "minItems": 1,
-            "uniqueItems": true
-        },
-        "type": {
-            "anyOf": [
-                { "$ref": "#/definitions/simpleTypes" },
-                {
-                    "type": "array",
-                    "items": { "$ref": "#/definitions/simpleTypes" },
-                    "minItems": 1,
-                    "uniqueItems": true
-                }
-            ]
-        },
-        "allOf": { "$ref": "#/definitions/schemaArray" },
-        "anyOf": { "$ref": "#/definitions/schemaArray" },
-        "oneOf": { "$ref": "#/definitions/schemaArray" },
-        "not": { "$ref": "#" }
-    },
-    "dependencies": {
-        "exclusiveMaximum": [ "maximum" ],
-        "exclusiveMinimum": [ "minimum" ]
-    },
-    "default": {}
-}
diff --git a/docs/developer-guide/nodejs-api.md b/docs/developer-guide/nodejs-api.md
index f48670896..431aaf049 100644
--- a/docs/developer-guide/nodejs-api.md
+++ b/docs/developer-guide/nodejs-api.md
@@ -159,7 +159,7 @@ var messages = linter.verifyAndFix("var foo", {
     rules: {
         semi: 2
     }
-}, { filename: "foo.js" });
+});
 ```
 
 Output object from this method:
@@ -167,7 +167,7 @@ Output object from this method:
 ```js
 {
     fixed: true,
-    text: "var foo;",
+    output: "var foo;",
     messages: []
 }
 ```
@@ -175,7 +175,7 @@ Output object from this method:
 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).
+* `output` - 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
@@ -206,7 +206,7 @@ var CLIEngine = require("eslint").CLIEngine;
 
 The `CLIEngine` is a constructor, and you can create a new instance by passing in the options you want to use. The available options are:
 
-* `allowInlineConfig` - Set to false to disable the use of configuration comments (such as `/*eslint-disable*/`). Corresponds to `--no-inline-config`.
+* `allowInlineConfig` - Set to `false` to disable the use of configuration comments (such as `/*eslint-disable*/`). Corresponds to `--no-inline-config`.
 * `baseConfig` - Set to false to disable use of base config. Could be set to an object to override default base config as well.
 * `cache` - Operate only on changed files (default: `false`). Corresponds to `--cache`.
 * `cacheFile` - Name of the file where the cache will be stored (default: `.eslintcache`). Corresponds to `--cache-file`. Deprecated: use `cacheLocation` instead.
@@ -215,7 +215,7 @@ The `CLIEngine` is a constructor, and you can create a new instance by passing i
 * `cwd` - Path to a directory that should be considered as the current working directory.
 * `envs` - An array of environments to load (default: empty array). Corresponds to `--env`.
 * `extensions` - An array of filename extensions that should be checked for code. The default is an array containing just `".js"`. Corresponds to `--ext`. It is only used in conjunction with directories, not with filenames or glob patterns.
-* `fix` - True indicates that fixes should be included with the output report, and that errors and warnings should not be listed if they can be fixed. However, the files on disk will not be changed. To persist changes to disk, call [`outputFixes()`](#outputfixes).
+* `fix` - This can be a boolean or a function which will be provided each linting message and should return a boolean. True indicates that fixes should be included with the output report, and that errors and warnings should not be listed if they can be fixed. However, the files on disk will not be changed. To persist changes to disk, call [`outputFixes()`](#outputfixes).
 * `globals` - An array of global variables to declare (default: empty array). Corresponds to `--global`.
 * `ignore` - False disables use of `.eslintignore`, `ignorePath` and `ignorePattern` (default: true). Corresponds to `--no-ignore`.
 * `ignorePath` - The ignore file to use instead of `.eslintignore` (default: null). Corresponds to `--ignore-path`.
diff --git a/docs/developer-guide/working-with-plugins.md b/docs/developer-guide/working-with-plugins.md
index c3bbccc48..0c8bfad22 100644
--- a/docs/developer-guide/working-with-plugins.md
+++ b/docs/developer-guide/working-with-plugins.md
@@ -221,9 +221,9 @@ Add these keywords into your `package.json` file to make it easy for others to f
 
 ### Working with Custom Parsers
 
-If you want to use your own parser and provide additional capabilities for your rules, you can specify your own custom parser. By default, the ESLint parser will use its parse method that takes in the source code as a first parameter and additional optional parameters as a second parameter to create an AST. You can specify a `parse` configuration to use your own custom parser. If a `parseForESLint` method is exposed, this method will be used to parse. Otherwise, the parser will use the parse method. `parseForESLint` behaves like `parse` and takes in the the source code and optional ESLint configurations. When `parseForESLint` is called, the method should return an object that contains the required property `ast` and an optional `services` property. `ast` should contain the AST. The `services` property contains the parser-dependent services. The value of the service property is available to rules as `context.parserServices`
+If you want to use your own parser and provide additional capabilities for your rules, you can specify your own custom parser. If a `parseForESLint` method is exposed on the parser, this method will be used to parse the code. Otherwise, the `parse` method will be used. Both methods should take in the the source code as the first argument, and an optional configuration object as the second argument (provided as `parserOptions` in a config file). The `parse` method should simply return the AST. The `parseForESLint` method should return an object that contains the required property `ast` and an optional `services` property. `ast` should contain the AST. The `services` property can contain any parser-dependent services (such as type checkers for nodes). The value of the `services` property is available to rules as `context.parserServices`.
 
-If no parseForESLint function is found, the parser will use the default parse method with the source code and the parser options. You can find a ESLint parser project [here](https://github.com/eslint/typescript-eslint-parser).
+You can find an ESLint parser project [here](https://github.com/eslint/typescript-eslint-parser).
 
     {
 
diff --git a/docs/developer-guide/working-with-rules.md b/docs/developer-guide/working-with-rules.md
index 67b5e7952..0c35b19fb 100644
--- a/docs/developer-guide/working-with-rules.md
+++ b/docs/developer-guide/working-with-rules.md
@@ -361,6 +361,8 @@ In the preceding example, the error level is assumed to be the first argument. I
 
 To learn more about JSON Schema, we recommend looking at some [examples](http://json-schema.org/examples.html) to start, and also reading [Understanding JSON Schema](http://spacetelescope.github.io/understanding-json-schema/) (a free ebook).
 
+**Note:** Currently you need to use full JSON Schema object rather than array in case your schema has references ($ref), because in case of array format eslint transforms this array into a single schema without updating references that makes them incorrect (they are ignored).
+
 ### Getting the Source
 
 If your rule needs to get the actual JavaScript source to work with, then use the `sourceCode.getText()` method. This method works as follows:
diff --git a/docs/maintainer-guide/pullrequests.md b/docs/maintainer-guide/pullrequests.md
index fffeb75b5..3b5618389 100644
--- a/docs/maintainer-guide/pullrequests.md
+++ b/docs/maintainer-guide/pullrequests.md
@@ -11,14 +11,14 @@ Anyone, both team members and the public, may leave comments on pull requests.
 When a pull request is opened, the bot will check the following:
 
 1. Has the submitter signed a CLA?
-1. Is the commit message summary in the correct format? Double-check that the tag ("Fix:", "New:", etc.) is correct based on the issue. Documentation-only pull requests do not require an issue.
-1. Does the commit summary reference an issue?
+1. Is the commit message summary in the correct format?
 1. Is the commit summary too long?
 
 The bot will add a comment specifying the problems that it finds. You do not need to look at the pull request any further until those problems have been addressed (there's no need to comment on the pull request to ask the submitter to do what the bot asked - that's why we have the bot!).
 
 Once the bot checks have been satisfied, you check the following:
 
+1. Double-check that the commit message tag ("Fix:", "New:", etc.) is correct based on the issue (or, if no issue is referenced, based on the stated problem).
 1. Does the code follow our conventions (including header comments, JSDoc comments, etc.)? If not, please leave that feedback and reference the conventions document.
 1. For code changes:
     * Are there tests that verify the change? If not, please ask for them.
@@ -38,6 +38,7 @@ Committers may merge a pull request if it is a non-breaking change and is:
 1. A bug fix (for either rules or core)
 1. A dependency upgrade
 1. Related to the build system
+1. A chore
 
 In addition, committers may merge any non-breaking pull request if it has been approved by at least one TSC member.
 
diff --git a/docs/maintainer-guide/releases.md b/docs/maintainer-guide/releases.md
index cf92d7f56..4d9f09587 100644
--- a/docs/maintainer-guide/releases.md
+++ b/docs/maintainer-guide/releases.md
@@ -16,6 +16,10 @@ A two-person release team is assigned to each scheduled release. This two-person
 
 The two-person team should seek input from the whole team on the Monday following a release to double-check if a patch release is necessary.
 
+## Release Communication
+
+Each scheduled release should be associated with a release issue ([example](https://github.com/eslint/eslint/issues/8138)). The release issue is the source of information for the team about the status of a release. Be sure the release issue has the "release" label so that it's easy to find.
+
 ## Process
 
 On the day of a scheduled release, the release team should follow these steps:
@@ -27,10 +31,10 @@ On the day of a scheduled release, the release team should follow these steps:
     * Small bug fixes written by a team member.
 1. Log into Jenkins and schedule a build for the "ESLint Release" job.
 1. Wait for the "ESLint Release" job to complete.
-1. Update the release blog post with a "Highlights", including new rules and anything else that's important.
-1. Make release announcement in the chatroom.
-1. Make release announcement on Twitter.
-1. Remind the team not to merge anything other than documentation changes and bug fixes.
+1. Update the release blog post with a "Highlights" section, including new rules and anything else that's important.
+1. Make a release announcement in the public chatroom.
+1. Make a release announcement on Twitter.
+1. Make a release announcement on the release issue. Document any problems that occurred during the release, and remind the team not to merge anything other than documentation changes and bug fixes. Leave the release issue open.
 
 On the Monday following the scheduled release, the release team needs to determine if a patch release is necessary. A patch release is considered necessary if any of the following occurred since the scheduled release:
 
@@ -39,7 +43,9 @@ On the Monday following the scheduled release, the release team needs to determi
 
 The patch release decision should be made as early on Monday as possible. If a patch release is necessary, then follow the same steps as the scheduled release process.
 
-After the patch release has been published (or no patch release is necessary), inform the team that they can start merging in changes again.
+In rare cases, a second patch release might be necessary if the release is known to have a severe regression that hasn't been fixed by Monday. If this occurs, the release team should announce the situation on the release issue, and leave the issue open until all patch releases are complete. However, it's usually better to fix bugs for the next release cycle rather than doing a second patch release.
+
+After the patch release has been published (or no patch release is necessary), close the release issue and inform the team that they can start merging in semver-minor changes again.
 
 ## Emergency Releases
 
diff --git a/docs/rules/getter-return.md b/docs/rules/getter-return.md
new file mode 100644
index 000000000..57f92065d
--- /dev/null
+++ b/docs/rules/getter-return.md
@@ -0,0 +1,97 @@
+# Enforces that a return statement is present in property getters (getter-return)
+
+The get syntax binds an object property to a function that will be called when that property is looked up. It was first introduced in ECMAScript 5:
+
+```js
+    var p = {
+        get name(){
+            return "nicholas";
+        }
+    };
+
+    Object.defineProperty(p, "age", {
+        get: function (){
+            return 17;
+        }
+    });
+```
+
+Note that every `getter` is expected to return a value.
+
+## Rule Details
+
+This rule enforces that a return statement is present in property getters.
+
+Examples of **incorrect** code for this rule:
+
+```js
+/*eslint getter-return: "error"*/
+
+p = {
+    get name(){
+        // no returns.
+    }
+};
+
+Object.defineProperty(p, "age", {
+    get: function (){
+        // no returns.
+    }
+});
+
+class P{
+    get name(){
+        // no returns.
+    }
+}
+```
+
+Examples of **correct** code for this rule:
+
+```js
+/*eslint getter-return: "error"*/
+
+p = {
+    get name(){
+        return "nicholas";
+    }
+};
+
+Object.defineProperty(p, "age", {
+    get: function (){
+        return 18;
+    }
+});
+
+class P{
+    get name(){
+        return "nicholas";
+    }
+}
+```
+
+## Options
+
+This rule has an object option:
+
+* `"allowImplicit": false` (default) disallows implicitly returning undefined with a return; statement.
+
+Examples of **correct** code for the `{ "allowImplicit": true }` option:
+
+```js
+/*eslint getter-return: ["error", { allowImplicit: true }]*/
+p = {
+    get name(){
+        return; // return undefined implicitly.
+    }
+};
+```
+
+## When Not To Use It
+
+If your project will not be using ES5 property getters you do not need this rule.
+
+## Further Reading
+
+* [MDN: Functions getter](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get)
+* [Understanding ES6: Accessor Properties](https://leanpub.com/understandinges6/read/#leanpub-auto-accessor-properties)
diff --git a/docs/rules/multiline-ternary.md b/docs/rules/multiline-ternary.md
index e331078f3..0cb94643a 100644
--- a/docs/rules/multiline-ternary.md
+++ b/docs/rules/multiline-ternary.md
@@ -26,6 +26,7 @@ Note: The location of the operators is not enforced by this rule. Please see the
 This rule has a string option:
 
 * `"always"` (default) enforces newlines between the operands of a ternary expression.
+* `"always-multiline"` enforces newlines between the operands of a ternary expression if the expression spans multiple lines.
 * `"never"` disallows newlines between the operands of a ternary expression (enforcing that the entire ternary expression is on one line).
 
 ### always
@@ -62,6 +63,50 @@ foo > bar ?
     value3;
 ```
 
+### always-multiline
+
+Examples of **incorrect** code for this rule with the `"always-multiline"` option:
+
+```js
+/*eslint multiline-ternary: ["error", "always-multiline"]*/
+
+foo > bar ? value1 :
+    value2;
+
+foo > bar ?
+    value1 : value2;
+
+foo > bar &&
+    bar > baz ? value1 : value2;
+```
+
+Examples of **correct** code for this rule with the `"always-multiline"` option:
+
+```js
+/*eslint multiline-ternary: ["error", "always-multiline"]*/
+
+foo > bar ? value1 : value2;
+
+foo > bar ?
+    value1 :
+    value2;
+
+foo > bar ?
+    (baz > qux ? value1 : value2) :
+    value3;
+
+foo > bar ?
+    (baz > qux ?
+        value1 :
+        value2) :
+    value3;
+
+foo > bar &&
+    bar > baz ?
+        value1 :
+        value2;
+```
+
 ### never
 
 Examples of **incorrect** code for this rule with the `"never"` option:
diff --git a/docs/rules/no-extra-parens.md b/docs/rules/no-extra-parens.md
index b30b217a0..0bc103ccb 100644
--- a/docs/rules/no-extra-parens.md
+++ b/docs/rules/no-extra-parens.md
@@ -9,6 +9,8 @@ This rule always ignores extra parentheses around the following:
 * RegExp literals such as `(/abc/).test(var)` to avoid conflicts with the [wrap-regex](wrap-regex.md) rule
 * immediately-invoked function expressions (also known as IIFEs) such as `var x = (function () {})();` and `((function foo() {return 1;})())` to avoid conflicts with the [wrap-iife](wrap-iife.md) rule
 
+In addition, this rule ignores parentheses around arrow function arguments to avoid conflicts with the [arrow-parens](arrow-parens.md) rule.
+
 ## Options
 
 This rule has a string option:
@@ -217,5 +219,6 @@ typeof (a);
 
 ## Related Rules
 
+* [arrow-parens](arrow-parens.md)
 * [no-cond-assign](no-cond-assign.md)
 * [no-return-assign](no-return-assign.md)
diff --git a/docs/rules/no-redeclare.md b/docs/rules/no-redeclare.md
index 8e07c058b..d0d71e5de 100644
--- a/docs/rules/no-redeclare.md
+++ b/docs/rules/no-redeclare.md
@@ -32,6 +32,8 @@ If set to `true`, this rule also checks redeclaration of built-in globals, such
 
 ### builtinGlobals
 
+The `"builtinGlobals"` option will check for redeclaration of built-in globals in global scope.
+
 Examples of **incorrect** code for the `{ "builtinGlobals": true }` option:
 
 ```js
@@ -50,3 +52,9 @@ var top = 0;
 ```
 
 The `browser` environment has many built-in global variables (for example, `top`). Some of built-in global variables cannot be redeclared.
+
+Note that when using the `node` or `commonjs` environments (or `ecmaFeatures.globalReturn`, if using the default parser), the top scope of a program is not actually the global scope, but rather a "module" scope. When this is the case, declaring a variable named after a builtin global is not a redeclaration, but rather a shadowing of the global variable. In that case, the [`no-shadow`](no-shadow.md) rule with the `"builtinGlobals"` option should be used.
+
+## Related Rules
+
+* [no-shadow](no-shadow.md)
diff --git a/docs/rules/object-curly-newline.md b/docs/rules/object-curly-newline.md
index 40f5368e5..a4907046f 100644
--- a/docs/rules/object-curly-newline.md
+++ b/docs/rules/object-curly-newline.md
@@ -16,7 +16,7 @@ This rule has either a string option:
 Or an object option:
 
 * `"multiline": true` (default) requires line breaks if there are line breaks inside properties or between properties
-* `"minProperties"` requires line breaks if the number of properties is more than the given integer. By default, an error will also be reported if an object contains linebreaks and has fewer properties than the given integer. However, the second behavior is disabled if the `consistent` option is set to `true`
+* `"minProperties"` requires line breaks if the number of properties is at least the given integer. By default, an error will also be reported if an object contains linebreaks and has fewer properties than the given integer. However, the second behavior is disabled if the `consistent` option is set to `true`
 * `"consistent": true` requires that either both curly braces, or neither, directly enclose newlines. Note that enabling this option will also change the behavior of the `minProperties` option. (See `minProperties` above for more information)
 
 You can specify different options for object literals and destructuring assignments:
diff --git a/docs/user-guide/migrating-to-4.0.0.md b/docs/user-guide/migrating-to-4.0.0.md
index 68e9a40bc..8d02b5420 100644
--- a/docs/user-guide/migrating-to-4.0.0.md
+++ b/docs/user-guide/migrating-to-4.0.0.md
@@ -19,6 +19,7 @@ The lists below are ordered roughly by the number of users each change is expect
 
 1. [`RuleTester` now validates properties of test cases](#rule-tester-validation)
 1. [AST nodes no longer have comment properties](#comment-attachment)
+1. [`LineComment` and `BlockComment` events will no longer be emitted during AST traversal](#event-comments)
 1. [Shebangs are now returned from comment APIs](#shebangs)
 
 ### Breaking changes for integration developers
diff --git a/lib/cli-engine.js b/lib/cli-engine.js
index 8d5ab065f..1abc1fd2c 100644
--- a/lib/cli-engine.js
+++ b/lib/cli-engine.js
@@ -45,7 +45,7 @@ const debug = require("debug")("eslint:cli-engine");
  * @property {string} cwd The value to use for the current working directory.
  * @property {string[]} envs An array of environments to load.
  * @property {string[]} extensions An array of file extensions to check.
- * @property {boolean} fix Execute in autofix mode.
+ * @property {boolean|Function} fix Execute in autofix mode. If a function, should return a boolean.
  * @property {string[]} globals An array of global variables to declare.
  * @property {boolean} ignore False disables use of .eslintignore.
  * @property {string} ignorePath The ignore file to use instead of .eslintignore.
@@ -135,7 +135,7 @@ function calculateStatsPerRun(results) {
  * @param {string} text The source code to check.
  * @param {Object} configHelper The configuration options for ESLint.
  * @param {string} filename An optional string representing the texts filename.
- * @param {boolean} fix Indicates if fixes should be processed.
+ * @param {boolean|Function} fix Indicates if fixes should be processed.
  * @param {boolean} allowInlineConfig Allow/ignore comments that change config.
  * @param {Linter} linter Linter context
  * @returns {LintResult} The results for linting on this text.
@@ -195,7 +195,8 @@ function processText(text, configHelper, filename, fix, allowInlineConfig, linte
         if (fix) {
             fixedResult = linter.verifyAndFix(text, config, {
                 filename,
-                allowInlineConfig
+                allowInlineConfig,
+                fix
             });
             messages = fixedResult.messages;
         } else {
diff --git a/lib/cli.js b/lib/cli.js
index 530bfbc42..d39847718 100644
--- a/lib/cli.js
+++ b/lib/cli.js
@@ -28,6 +28,17 @@ const debug = require("debug")("eslint:cli");
 // Helpers
 //------------------------------------------------------------------------------
 
+/**
+ * Predicate function for whether or not to apply fixes in quiet mode.
+ * If a message is a warning, do not apply a fix.
+ * @param {LintResult} lintResult The lint result.
+ * @returns {boolean} True if the lint message is an error (and thus should be
+ * autofixed), false otherwise.
+ */
+function quietFixPredicate(lintResult) {
+    return lintResult.severity === 2;
+}
+
 /**
  * Translates the CLI options into the options expected by the CLIEngine.
  * @param {Object} cliOptions The CLI options to translate.
@@ -52,7 +63,7 @@ function translateOptions(cliOptions) {
         cache: cliOptions.cache,
         cacheFile: cliOptions.cacheFile,
         cacheLocation: cliOptions.cacheLocation,
-        fix: cliOptions.fix,
+        fix: cliOptions.fix && (cliOptions.quiet ? quietFixPredicate : true),
         allowInlineConfig: cliOptions.inlineConfig
     };
 }
diff --git a/lib/config/config-validator.js b/lib/config/config-validator.js
index 8754485f4..fb5a96564 100644
--- a/lib/config/config-validator.js
+++ b/lib/config/config-validator.js
@@ -9,7 +9,7 @@
 // Requirements
 //------------------------------------------------------------------------------
 
-const schemaValidator = require("is-my-json-valid"),
+const ajv = require("../util/ajv"),
     configSchema = require("../../conf/config-schema.js"),
     util = require("util");
 
@@ -20,6 +20,7 @@ const validators = {
 //------------------------------------------------------------------------------
 // Private
 //------------------------------------------------------------------------------
+let validateSchema;
 
 /**
  * Gets a complete options schema for a rule.
@@ -79,7 +80,7 @@ function validateRuleSchema(id, localOptions, rulesContext) {
     const schema = getRuleOptionsSchema(id, rulesContext);
 
     if (!validators.rules[id] && schema) {
-        validators.rules[id] = schemaValidator(schema, { verbose: true });
+        validators.rules[id] = ajv.compile(schema);
     }
 
     const validateRule = validators.rules[id];
@@ -87,7 +88,7 @@ function validateRuleSchema(id, localOptions, rulesContext) {
     if (validateRule) {
         validateRule(localOptions);
         if (validateRule.errors) {
-            throw new Error(validateRule.errors.map(error => `\tValue "${error.value}" ${error.message}.\n`).join(""));
+            throw new Error(validateRule.errors.map(error => `\tValue "${error.data}" ${error.message}.\n`).join(""));
         }
     }
 }
@@ -158,19 +159,23 @@ function validateRules(rulesConfig, source, rulesContext) {
  * @returns {string} Formatted error message
  */
 function formatErrors(errors) {
-
     return errors.map(error => {
-        if (error.message === "has additional properties") {
-            return `Unexpected top-level property "${error.value.replace(/^data\./, "")}"`;
+        if (error.keyword === "additionalProperties") {
+            const formattedPropertyPath = error.dataPath.length ? `${error.dataPath.slice(1)}.${error.params.additionalProperty}` : error.params.additionalProperty;
+
+            return `Unexpected top-level property "${formattedPropertyPath}"`;
         }
-        if (error.message === "is the wrong type") {
-            const formattedField = error.field.replace(/^data\./, "");
-            const formattedExpectedType = typeof error.type === "string" ? error.type : error.type.join("/");
-            const formattedValue = JSON.stringify(error.value);
+        if (error.keyword === "type") {
+            const formattedField = error.dataPath.slice(1);
+            const formattedExpectedType = Array.isArray(error.schema) ? error.schema.join("/") : error.schema;
+            const formattedValue = JSON.stringify(error.data);
 
             return `Property "${formattedField}" is the wrong type (expected ${formattedExpectedType} but got \`${formattedValue}\`)`;
         }
-        return `"${error.field.replace(/^(data\.)/, "")}" ${error.message}. Value: ${JSON.stringify(error.value)}`;
+
+        const field = error.dataPath[0] === "." ? error.dataPath.slice(1) : error.dataPath;
+
+        return `"${field}" ${error.message}. Value: ${JSON.stringify(error.data)}`;
     }).map(message => `\t- ${message}.\n`).join("");
 }
 
@@ -181,10 +186,10 @@ function formatErrors(errors) {
  * @returns {void}
  */
 function validateConfigSchema(config, source) {
-    const validator = schemaValidator(configSchema, { verbose: true });
+    validateSchema = validateSchema || ajv.compile(configSchema);
 
-    if (!validator(config)) {
-        throw new Error(`${source}:\n\tESLint configuration is invalid:\n${formatErrors(validator.errors)}`);
+    if (!validateSchema(config)) {
+        throw new Error(`${source}:\n\tESLint configuration is invalid:\n${formatErrors(validateSchema.errors)}`);
     }
 }
 
diff --git a/lib/linter.js b/lib/linter.js
index d2f1f4657..ce759756e 100755
--- a/lib/linter.js
+++ b/lib/linter.js
@@ -1197,6 +1197,7 @@ class Linter extends EventEmitter {
      * @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 {boolean|Function} options.fix Determines whether fixes should be applied
      * @returns {Object} The result of the fix operation as returned from the
      *      SourceCodeFixer.
      */
@@ -1205,6 +1206,8 @@ class Linter extends EventEmitter {
             fixedResult,
             fixed = false,
             passNumber = 0;
+        const debugTextDescription = options && options.filename || `${text.slice(0, 10)}...`;
+        const shouldFix = options && options.fix || true;
 
         /**
          * This loop continues until one of the following is true:
@@ -1218,11 +1221,11 @@ class Linter extends EventEmitter {
         do {
             passNumber++;
 
-            debug(`Linting code for ${options.filename} (pass ${passNumber})`);
+            debug(`Linting code for ${debugTextDescription} (pass ${passNumber})`);
             messages = this.verify(text, config, options);
 
-            debug(`Generating fixed text for ${options.filename} (pass ${passNumber})`);
-            fixedResult = SourceCodeFixer.applyFixes(this.getSourceCode(), messages);
+            debug(`Generating fixed text for ${debugTextDescription} (pass ${passNumber})`);
+            fixedResult = SourceCodeFixer.applyFixes(this.getSourceCode(), messages, shouldFix);
 
             // stop if there are any syntax errors.
             // 'fixedResult.output' is a empty string.
diff --git a/lib/rules/arrow-parens.js b/lib/rules/arrow-parens.js
index 60a043bb3..d8ca0ecaf 100644
--- a/lib/rules/arrow-parens.js
+++ b/lib/rules/arrow-parens.js
@@ -66,7 +66,10 @@ module.exports = {
              */
             function fixParamsWithParenthesis(fixer) {
                 const paramToken = sourceCode.getTokenAfter(firstTokenOfParam);
-                const closingParenToken = sourceCode.getTokenAfter(paramToken);
+
+                // ES8 allows Trailing commas in function parameter lists and calls
+                // https://github.com/eslint/eslint/issues/8834
+                const closingParenToken = sourceCode.getTokenAfter(paramToken, astUtils.isClosingParenToken);
                 const asyncToken = isAsync ? sourceCode.getTokenBefore(firstTokenOfParam) : null;
                 const shouldAddSpaceForAsync = asyncToken && (asyncToken.end === firstTokenOfParam.start);
 
diff --git a/lib/rules/comma-dangle.js b/lib/rules/comma-dangle.js
index 17066d947..ddcc0cd22 100644
--- a/lib/rules/comma-dangle.js
+++ b/lib/rules/comma-dangle.js
@@ -81,49 +81,49 @@ module.exports = {
             category: "Stylistic Issues",
             recommended: false
         },
-
         fixable: "code",
-
-        schema: [
-            {
-                defs: {
-                    value: {
-                        enum: [
-                            "always",
-                            "always-multiline",
-                            "only-multiline",
-                            "never"
-                        ]
-                    },
-                    valueWithIgnore: {
-                        anyOf: [
-                            {
-                                $ref: "#/defs/value"
-                            },
-                            {
-                                enum: ["ignore"]
-                            }
-                        ]
-                    }
+        schema: {
+            definitions: {
+                value: {
+                    enum: [
+                        "always-multiline",
+                        "always",
+                        "never",
+                        "only-multiline"
+                    ]
                 },
-                anyOf: [
-                    {
-                        $ref: "#/defs/value"
-                    },
-                    {
-                        type: "object",
-                        properties: {
-                            arrays: { $refs: "#/defs/valueWithIgnore" },
-                            objects: { $refs: "#/defs/valueWithIgnore" },
-                            imports: { $refs: "#/defs/valueWithIgnore" },
-                            exports: { $refs: "#/defs/valueWithIgnore" },
-                            functions: { $refs: "#/defs/valueWithIgnore" }
+                valueWithIgnore: {
+                    enum: [
+                        "always-multiline",
+                        "always",
+                        "ignore",
+                        "never",
+                        "only-multiline"
+                    ]
+                }
+            },
+            type: "array",
+            items: [
+                {
+                    oneOf: [
+                        {
+                            $ref: "#/definitions/value"
                         },
-                        additionalProperties: false
-                    }
-                ]
-            }
-        ]
+                        {
+                            type: "object",
+                            properties: {
+                                arrays: { $ref: "#/definitions/valueWithIgnore" },
+                                objects: { $ref: "#/definitions/valueWithIgnore" },
+                                imports: { $ref: "#/definitions/valueWithIgnore" },
+                                exports: { $ref: "#/definitions/valueWithIgnore" },
+                                functions: { $ref: "#/definitions/valueWithIgnore" }
+                            },
+                            additionalProperties: false
+                        }
+                    ]
+                }
+            ]
+        }
     },
 
     create(context) {
diff --git a/lib/rules/dot-notation.js b/lib/rules/dot-notation.js
index 55225b8cc..142f0f24a 100644
--- a/lib/rules/dot-notation.js
+++ b/lib/rules/dot-notation.js
@@ -116,6 +116,15 @@ module.exports = {
                                 return null;
                             }
 
+                            if (node.object.type === "Identifier" && node.object.name === "let") {
+
+                                /*
+                                 * A statement that starts with `let[` is parsed as a destructuring variable declaration, not
+                                 * a MemberExpression.
+                                 */
+                                return null;
+                            }
+
                             return fixer.replaceTextRange(
                                 [dot.range[0], node.property.range[1]],
                                 `[${textAfterDot}"${node.property.name}"]`
diff --git a/lib/rules/getter-return.js b/lib/rules/getter-return.js
new file mode 100644
index 000000000..8a095ad29
--- /dev/null
+++ b/lib/rules/getter-return.js
@@ -0,0 +1,151 @@
+/**
+ * @fileoverview Enforces that a return statement is present in property getters.
+ * @author Aladdin-ADD(hh_2013@foxmail.com)
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("../ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Checks a given code path segment is reachable.
+ *
+ * @param {CodePathSegment} segment - A segment to check.
+ * @returns {boolean} `true` if the segment is reachable.
+ */
+function isReachable(segment) {
+    return segment.reachable;
+}
+
+/**
+ * Gets a readable location.
+ *
+ * - FunctionExpression -> the function name or `function` keyword.
+ *
+ * @param {ASTNode} node - A function node to get.
+ * @returns {ASTNode|Token} The node or the token of a location.
+ */
+function getId(node) {
+    return node.id || node;
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = {
+    meta: {
+        docs: {
+            description: "enforce `return` statements in getters",
+            category: "Possible Errors",
+            recommended: false
+        },
+        fixable: null,
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    allowImplicit: {
+                        type: "boolean"
+                    }
+                },
+                additionalProperties: false
+            }
+        ]
+    },
+
+    create(context) {
+
+        const options = context.options[0] || { allowImplicit: false };
+
+        let funcInfo = {
+            upper: null,
+            codePath: null,
+            hasReturn: false,
+            shouldCheck: false,
+            node: null
+        };
+
+        /**
+         * Checks whether or not the last code path segment is reachable.
+         * Then reports this function if the segment is reachable.
+         *
+         * If the last code path segment is reachable, there are paths which are not
+         * returned or thrown.
+         *
+         * @param {ASTNode} node - A node to check.
+         * @returns {void}
+         */
+        function checkLastSegment(node) {
+            if (funcInfo.shouldCheck &&
+                funcInfo.codePath.currentSegments.some(isReachable)
+            ) {
+                context.report({
+                    node,
+                    loc: getId(node).loc.start,
+                    message: funcInfo.hasReturn
+                        ? "Expected {{name}} to always return a value."
+                        : "Expected to return a value in {{name}}.",
+                    data: {
+                        name: astUtils.getFunctionNameWithKind(funcInfo.node)
+                    }
+                });
+            }
+        }
+
+        return {
+
+            // Stacks this function's information.
+            onCodePathStart(codePath, node) {
+                const parent = node.parent;
+
+                funcInfo = {
+                    upper: funcInfo,
+                    codePath,
+                    hasReturn: false,
+                    shouldCheck:
+                        node.type === "FunctionExpression" &&
+                        node.body.type === "BlockStatement" &&
+
+                        // check if it is a "getter", or a method named "get".
+                        (parent.kind === "get" || astUtils.getStaticPropertyName(parent) === "get"),
+                    node
+                };
+            },
+
+            // Pops this function's information.
+            onCodePathEnd() {
+                funcInfo = funcInfo.upper;
+            },
+
+            // Checks the return statement is valid.
+            ReturnStatement(node) {
+                if (funcInfo.shouldCheck) {
+                    funcInfo.hasReturn = true;
+
+                    // if allowImplicit: false, should also check node.argument
+                    if (!options.allowImplicit && !node.argument) {
+                        context.report({
+                            node,
+                            message: "Expected to return a value in {{name}}.",
+                            data: {
+                                name: astUtils.getFunctionNameWithKind(funcInfo.node)
+                            }
+                        });
+                    }
+                }
+            },
+
+            // Reports a given function if the last path is reachable.
+            "FunctionExpression:exit": checkLastSegment
+        };
+    }
+};
diff --git a/lib/rules/indent.js b/lib/rules/indent.js
index 3027e7720..727ecfaae 100644
--- a/lib/rules/indent.js
+++ b/lib/rules/indent.js
@@ -303,6 +303,18 @@ class OffsetStorage {
         }
     }
 
+    /**
+     * Sets the desired offset of a token, ignoring the usual collapsing behavior.
+     * **WARNING**: This is usually not what you want to use. See `setDesiredOffset` instead.
+     * @param {Token} token The token
+     * @param {Token} offsetFrom The token that `token` should be offset from
+     * @param {number} offset The desired indent level
+     * @returns {void}
+     */
+    forceSetDesiredOffset(token, offsetFrom, offset) {
+        this.desiredOffsets[token.range[0]] = { offset, from: offsetFrom };
+    }
+
     /**
     * Sets the desired offset of multiple tokens
     * @param {Token[]} tokens A list of tokens. These tokens should be consecutive.
@@ -374,18 +386,6 @@ class OffsetStorage {
     getFirstDependency(token) {
         return this.desiredOffsets[token.range[0]].from;
     }
-
-    /**
-     * Increases the offset for a token from its parent by the given amount
-     * @param {Token} token The token whose offset should be increased
-     * @param {number} amount The number of indent levels that the offset should increase by
-     * @returns {void}
-     */
-    increaseOffset(token, amount) {
-        const currentOffsetInfo = this.desiredOffsets[token.range[0]];
-
-        this.desiredOffsets[token.range[0]] = { offset: currentOffsetInfo.offset + amount, from: currentOffsetInfo.from };
-    }
 }
 
 const ELEMENT_LIST_SCHEMA = {
@@ -738,7 +738,7 @@ module.exports = {
                     const firstTokenOfPreviousElement = previousElement && getFirstToken(previousElement);
 
                     if (previousElement && sourceCode.getLastToken(previousElement).loc.start.line > startToken.loc.end.line) {
-                        offsets.matchIndentOf(firstTokenOfPreviousElement, getFirstToken(element));
+                        offsets.setDesiredOffsets(getTokensAndComments(element), firstTokenOfPreviousElement, 0);
                     }
                 }
             });
@@ -813,7 +813,7 @@ module.exports = {
                  */
                 const lastToken = sourceCode.getLastToken(node);
 
-                if (astUtils.isSemicolonToken(lastToken)) {
+                if (node.type !== "EmptyStatement" && astUtils.isSemicolonToken(lastToken)) {
                     offsets.matchIndentOf(lastParentToken, lastToken);
                 }
             }
@@ -884,7 +884,7 @@ module.exports = {
 
             parameterParens.add(openingParen);
             parameterParens.add(closingParen);
-            offsets.matchIndentOf(sourceCode.getLastToken(node.callee), openingParen);
+            offsets.matchIndentOf(sourceCode.getTokenBefore(openingParen), openingParen);
 
             addElementListIndent(node.arguments, openingParen, closingParen, options.CallExpression.arguments);
         }
@@ -1018,7 +1018,7 @@ module.exports = {
                 const nodeTokens = getTokensAndComments(node);
                 const tokensFromOperator = nodeTokens.slice(lodash.sortedIndexBy(nodeTokens, operator, token => token.range[0]));
 
-                offsets.setDesiredOffsets(tokensFromOperator, sourceCode.getFirstToken(node.left), 1);
+                offsets.setDesiredOffsets(tokensFromOperator, sourceCode.getLastToken(node.left), 1);
                 offsets.ignoreToken(tokensFromOperator[0]);
                 offsets.ignoreToken(tokensFromOperator[1]);
             },
@@ -1274,7 +1274,35 @@ module.exports = {
             VariableDeclaration(node) {
                 const variableIndent = options.VariableDeclarator.hasOwnProperty(node.kind) ? options.VariableDeclarator[node.kind] : DEFAULT_VARIABLE_INDENT;
 
-                offsets.setDesiredOffsets(getTokensAndComments(node), sourceCode.getFirstToken(node), variableIndent);
+                if (node.declarations[node.declarations.length - 1].loc.start.line > node.loc.start.line) {
+
+                    /*
+                     * VariableDeclarator indentation is a bit different from other forms of indentation, in that the
+                     * indentation of an opening bracket sometimes won't match that of a closing bracket. For example,
+                     * the following indentations are correct:
+                     *
+                     * var foo = {
+                     *   ok: true
+                     * };
+                     *
+                     * var foo = {
+                     *     ok: true,
+                     *   },
+                     *   bar = 1;
+                     *
+                     * Account for when exiting the AST (after indentations have already been set for the nodes in
+                     * the declaration) by manually increasing the indentation level of the tokens in this declarator
+                     * on the same line as the start of the declaration, provided that there are declarators that
+                     * follow this one.
+                     */
+                    getTokensAndComments(node).forEach((token, index, tokens) => {
+                        if (index !== 0) {
+                            offsets.forceSetDesiredOffset(token, tokens[0], variableIndent);
+                        }
+                    });
+                } else {
+                    offsets.setDesiredOffsets(getTokensAndComments(node), sourceCode.getFirstToken(node), variableIndent);
+                }
                 const lastToken = sourceCode.getLastToken(node);
 
                 if (astUtils.isSemicolonToken(lastToken)) {
@@ -1285,43 +1313,12 @@ module.exports = {
             VariableDeclarator(node) {
                 if (node.init) {
                     const equalOperator = sourceCode.getTokenBefore(node.init, astUtils.isNotOpeningParenToken);
-                    const tokenAfterOperator = sourceCode.getTokenAfter(equalOperator);
+                    const tokensAfterOperator = sourceCode.getTokensAfter(equalOperator, token => token.range[1] <= node.range[1]);
 
                     offsets.ignoreToken(equalOperator);
-                    offsets.ignoreToken(tokenAfterOperator);
-                    offsets.matchIndentOf(equalOperator, tokenAfterOperator);
-                    offsets.matchIndentOf(sourceCode.getFirstToken(node), equalOperator);
-                }
-            },
-
-            "VariableDeclarator:exit"(node) {
-
-                /*
-                 * VariableDeclarator indentation is a bit different from other forms of indentation, in that the
-                 * indentation of an opening bracket sometimes won't match that of a closing bracket. For example,
-                 * the following indentations are correct:
-                 *
-                 * var foo = {
-                 *   ok: true
-                 * };
-                 *
-                 * var foo = {
-                 *     ok: true,
-                 *   },
-                 *   bar = 1;
-                 *
-                 * Account for when exiting the AST (after indentations have already been set for the nodes in
-                 * the declaration) by manually increasing the indentation level of the tokens in the first declarator if the
-                 * parent declaration has more than one declarator.
-                 */
-                if (node.parent.declarations.length > 1 && node.parent.declarations[0] === node && node.init) {
-                    const valueTokens = new Set(getTokensAndComments(node.init));
-
-                    valueTokens.forEach(token => {
-                        if (!valueTokens.has(offsets.getFirstDependency(token))) {
-                            offsets.increaseOffset(token, options.VariableDeclarator[node.parent.kind]);
-                        }
-                    });
+                    offsets.ignoreToken(tokensAfterOperator[0]);
+                    offsets.setDesiredOffsets(tokensAfterOperator, equalOperator, 1);
+                    offsets.matchIndentOf(sourceCode.getLastToken(node.id), equalOperator);
                 }
             },
 
@@ -1366,13 +1363,14 @@ module.exports = {
 
             JSXExpressionContainer(node) {
                 const openingCurly = sourceCode.getFirstToken(node);
-                const firstExpressionToken = sourceCode.getFirstToken(node.expression);
-
-                if (firstExpressionToken) {
-                    offsets.setDesiredOffset(firstExpressionToken, openingCurly, 1);
-                }
+                const closingCurly = sourceCode.getLastToken(node);
 
-                offsets.matchIndentOf(openingCurly, sourceCode.getLastToken(node));
+                offsets.setDesiredOffsets(
+                    sourceCode.getTokensBetween(openingCurly, closingCurly, { includeComments: true }),
+                    openingCurly,
+                    1
+                );
+                offsets.matchIndentOf(openingCurly, closingCurly);
             },
 
             "Program:exit"() {
diff --git a/lib/rules/multiline-ternary.js b/lib/rules/multiline-ternary.js
index de19bd43f..a74f241d8 100644
--- a/lib/rules/multiline-ternary.js
+++ b/lib/rules/multiline-ternary.js
@@ -20,13 +20,15 @@ module.exports = {
         },
         schema: [
             {
-                enum: ["always", "never"]
+                enum: ["always", "always-multiline", "never"]
             }
         ]
     },
 
     create(context) {
-        const multiline = context.options[0] !== "never";
+        const option = context.options[0];
+        const multiline = option !== "never";
+        const allowSingleLine = option === "always-multiline";
 
         //--------------------------------------------------------------------------
         // Helpers
@@ -69,6 +71,10 @@ module.exports = {
                         reportError(node.consequent, node, false);
                     }
                 } else {
+                    if (allowSingleLine && node.loc.start.line === node.loc.end.line) {
+                        return;
+                    }
+
                     if (areTestAndConsequentOnSameLine) {
                         reportError(node.test, node, true);
                     }
diff --git a/lib/rules/no-debugger.js b/lib/rules/no-debugger.js
index a158724fe..d79cb1816 100644
--- a/lib/rules/no-debugger.js
+++ b/lib/rules/no-debugger.js
@@ -5,6 +5,8 @@
 
 "use strict";
 
+const astUtils = require("../ast-utils");
+
 //------------------------------------------------------------------------------
 // Rule Definition
 //------------------------------------------------------------------------------
@@ -28,7 +30,10 @@ module.exports = {
                     node,
                     message: "Unexpected 'debugger' statement.",
                     fix(fixer) {
-                        return fixer.remove(node);
+                        if (astUtils.STATEMENT_LIST_PARENTS.has(node.parent.type)) {
+                            return fixer.remove(node);
+                        }
+                        return null;
                     }
                 });
             }
diff --git a/lib/rules/no-extra-parens.js b/lib/rules/no-extra-parens.js
index 9f48f1d81..fd6fd0b78 100644
--- a/lib/rules/no-extra-parens.js
+++ b/lib/rules/no-extra-parens.js
@@ -426,7 +426,7 @@ module.exports = {
                     secondToken.type === "Keyword" && (
                         secondToken.value === "function" ||
                         secondToken.value === "class" ||
-                        secondToken.value === "let" && astUtils.isOpeningBracketToken(sourceCode.getTokenAfter(secondToken))
+                        secondToken.value === "let" && astUtils.isOpeningBracketToken(sourceCode.getTokenAfter(secondToken, astUtils.isNotClosingParenToken))
                     )
                 )
             ) {
@@ -512,16 +512,27 @@ module.exports = {
             ExportDefaultDeclaration: node => checkExpressionOrExportStatement(node.declaration),
             ExpressionStatement: node => checkExpressionOrExportStatement(node.expression),
 
-            ForInStatement(node) {
-                if (hasExcessParens(node.right)) {
-                    report(node.right);
-                }
-                if (hasExcessParens(node.left)) {
-                    report(node.left);
-                }
-            },
+            "ForInStatement, ForOfStatement"(node) {
+                if (node.left.type !== "VariableDeclarator") {
+                    const firstLeftToken = sourceCode.getFirstToken(node.left, astUtils.isNotOpeningParenToken);
+
+                    if (
+                        firstLeftToken.value === "let" && (
+
+                            // If `let` is the only thing on the left side of the loop, it's the loop variable: `for ((let) of foo);`
+                            // Removing it will cause a syntax error, because it will be parsed as the start of a VariableDeclarator.
+                            firstLeftToken.range[1] === node.left.range[1] ||
 
-            ForOfStatement(node) {
+                            // If `let` is followed by a `[` token, it's a property access on the `let` value: `for ((let[foo]) of bar);`
+                            // Removing it will cause the property access to be parsed as a destructuring declaration of `foo` instead.
+                            astUtils.isOpeningBracketToken(
+                                sourceCode.getTokenAfter(firstLeftToken, astUtils.isNotClosingParenToken)
+                            )
+                        )
+                    ) {
+                        tokensToIgnore.add(firstLeftToken);
+                    }
+                }
                 if (hasExcessParens(node.right)) {
                     report(node.right);
                 }
diff --git a/lib/rules/no-regex-spaces.js b/lib/rules/no-regex-spaces.js
index 05ac86e87..09b689e8e 100644
--- a/lib/rules/no-regex-spaces.js
+++ b/lib/rules/no-regex-spaces.js
@@ -37,11 +37,11 @@ module.exports = {
          * @private
          */
         function checkRegex(node, value, valueStart) {
-            const multipleSpacesRegex = /( {2,})+?/,
+            const multipleSpacesRegex = /( {2,})( [+*{?]|[^+*{?]|$)/,
                 regexResults = multipleSpacesRegex.exec(value);
 
             if (regexResults !== null) {
-                const count = regexResults[0].length;
+                const count = regexResults[1].length;
 
                 context.report({
                     node,
diff --git a/lib/rules/no-sync.js b/lib/rules/no-sync.js
index 885b1eb62..06305969a 100644
--- a/lib/rules/no-sync.js
+++ b/lib/rules/no-sync.js
@@ -19,14 +19,26 @@ module.exports = {
             recommended: false
         },
 
-        schema: []
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    allowAtRootLevel: {
+                        type: "boolean"
+                    }
+                },
+                additionalProperties: false
+            }
+        ]
     },
 
     create(context) {
+        const selector = context.options[0] && context.options[0].allowAtRootLevel
+            ? ":function MemberExpression[property.name=/.*Sync$/]"
+            : "MemberExpression[property.name=/.*Sync$/]";
 
         return {
-
-            "MemberExpression[property.name=/.*Sync$/]"(node) {
+            [selector](node) {
                 context.report({
                     node,
                     message: "Unexpected sync method: '{{propertyName}}'.",
diff --git a/lib/util/ajv.js b/lib/util/ajv.js
new file mode 100644
index 000000000..f9e8b9853
--- /dev/null
+++ b/lib/util/ajv.js
@@ -0,0 +1,29 @@
+/**
+ * @fileoverview The instance of Ajv validator.
+ * @author Evgeny Poberezkin
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const Ajv = require("ajv"),
+    metaSchema = require("ajv/lib/refs/json-schema-draft-04.json");
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+const ajv = new Ajv({
+    meta: false,
+    validateSchema: false,
+    missingRefs: "ignore",
+    verbose: true
+});
+
+ajv.addMetaSchema(metaSchema);
+// eslint-disable-next-line no-underscore-dangle
+ajv._opts.defaultMeta = metaSchema.id;
+
+module.exports = ajv;
diff --git a/lib/util/source-code-fixer.js b/lib/util/source-code-fixer.js
index 6490a467f..1b6270a1e 100644
--- a/lib/util/source-code-fixer.js
+++ b/lib/util/source-code-fixer.js
@@ -55,10 +55,10 @@ function SourceCodeFixer() {
  * smart about the fixes and won't apply fixes over the same area in the text.
  * @param {SourceCode} sourceCode The source code to apply the changes to.
  * @param {Message[]} messages The array of messages reported by ESLint.
+ * @param {boolean|Function} [shouldFix=true] Determines whether each message should be fixed
  * @returns {Object} An object containing the fixed text and any unfixed messages.
  */
-SourceCodeFixer.applyFixes = function(sourceCode, messages) {
-
+SourceCodeFixer.applyFixes = function(sourceCode, messages, shouldFix) {
     debug("Applying fixes");
 
     if (!sourceCode) {
@@ -70,6 +70,15 @@ SourceCodeFixer.applyFixes = function(sourceCode, messages) {
         };
     }
 
+    if (shouldFix === false) {
+        debug("shouldFix parameter was false, not attempting fixes");
+        return {
+            fixed: false,
+            messages,
+            output: sourceCode.text
+        };
+    }
+
     // clone the array
     const remainingMessages = [],
         fixes = [],
@@ -78,6 +87,34 @@ SourceCodeFixer.applyFixes = function(sourceCode, messages) {
     let lastPos = Number.NEGATIVE_INFINITY,
         output = bom;
 
+    /**
+     * Try to use the 'fix' from a problem.
+     * @param   {Message} problem The message object to apply fixes from
+     * @returns {boolean}         Whether fix was successfully applied
+     */
+    function attemptFix(problem) {
+        const fix = problem.fix;
+        const start = fix.range[0];
+        const end = fix.range[1];
+
+        // Remain it as a problem if it's overlapped or it's a negative range
+        if (lastPos >= start || start > end) {
+            remainingMessages.push(problem);
+            return false;
+        }
+
+        // Remove BOM.
+        if ((start < 0 && end >= 0) || (start === 0 && fix.text.startsWith(BOM))) {
+            output = "";
+        }
+
+        // Make output to this fix.
+        output += text.slice(Math.max(0, lastPos), Math.max(0, start));
+        output += fix.text;
+        lastPos = end;
+        return true;
+    }
+
     messages.forEach(problem => {
         if (problem.hasOwnProperty("fix")) {
             fixes.push(problem);
@@ -88,32 +125,23 @@ SourceCodeFixer.applyFixes = function(sourceCode, messages) {
 
     if (fixes.length) {
         debug("Found fixes to apply");
+        let fixesWereApplied = false;
 
         for (const problem of fixes.sort(compareMessagesByFixRange)) {
-            const fix = problem.fix;
-            const start = fix.range[0];
-            const end = fix.range[1];
+            if (typeof shouldFix !== "function" || shouldFix(problem)) {
+                attemptFix(problem);
 
-            // Remain it as a problem if it's overlapped or it's a negative range
-            if (lastPos >= start || start > end) {
+                // The only time attemptFix will fail is if a previous fix was
+                // applied which conflicts with it.  So we can mark this as true.
+                fixesWereApplied = true;
+            } else {
                 remainingMessages.push(problem);
-                continue;
-            }
-
-            // Remove BOM.
-            if ((start < 0 && end >= 0) || (start === 0 && fix.text.startsWith(BOM))) {
-                output = "";
             }
-
-            // Make output to this fix.
-            output += text.slice(Math.max(0, lastPos), Math.max(0, start));
-            output += fix.text;
-            lastPos = end;
         }
         output += text.slice(Math.max(0, lastPos));
 
         return {
-            fixed: true,
+            fixed: fixesWereApplied,
             messages: remainingMessages.sort(compareMessagesByLocation),
             output
         };
diff --git a/package.json b/package.json
index 15c0eb561..fdd950684 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "eslint",
-  "version": "4.1.1",
+  "version": "4.2.0",
   "author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
   "description": "An AST-based pattern checker for JavaScript.",
   "bin": {
@@ -10,6 +10,7 @@
   "scripts": {
     "test": "node Makefile.js test",
     "lint": "node Makefile.js lint",
+    "fuzz": "node Makefile.js fuzz",
     "release": "node Makefile.js release",
     "ci-release": "node Makefile.js ciRelease",
     "alpharelease": "node Makefile.js prerelease -- alpha",
@@ -34,6 +35,7 @@
   "homepage": "http://eslint.org",
   "bugs": "https://github.com/eslint/eslint/issues/",
   "dependencies": {
+    "ajv": "^5.2.0",
     "babel-code-frame": "^6.22.0",
     "chalk": "^1.1.3",
     "concat-stream": "^1.6.0",
@@ -50,7 +52,6 @@
     "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.4",
     "json-stable-stringify": "^1.0.1",
@@ -80,9 +81,10 @@
     "coveralls": "^2.13.1",
     "dateformat": "^2.0.0",
     "ejs": "^2.5.6",
-    "eslint-plugin-eslint-plugin": "^0.7.2",
-    "eslint-plugin-node": "^5.0.0",
+    "eslint-plugin-eslint-plugin": "^0.7.4",
+    "eslint-plugin-node": "^5.1.0",
     "eslint-release": "^0.10.1",
+    "eslump": "1.6.0",
     "esprima": "^3.1.3",
     "esprima-fb": "^15001.1001.0-dev-harmony-fb",
     "istanbul": "^0.4.5",
diff --git a/packages/eslint-config-eslint/default.yml b/packages/eslint-config-eslint/default.yml
index 634fd4624..cdeac9ad4 100644
--- a/packages/eslint-config-eslint/default.yml
+++ b/packages/eslint-config-eslint/default.yml
@@ -23,6 +23,7 @@ rules:
     dot-notation: ["error", { allowKeywords: true }]
     eol-last: "error"
     eqeqeq: "error"
+    for-direction: "error"
     func-call-spacing: "error"
     func-style: ["error", "declaration"]
     generator-star-spacing: "error"
diff --git a/tools/eslint-fuzzer.js b/tools/eslint-fuzzer.js
new file mode 100644
index 000000000..3653673f3
--- /dev/null
+++ b/tools/eslint-fuzzer.js
@@ -0,0 +1,139 @@
+/**
+ * @fileoverview A fuzzer that runs eslint on randomly-generated code samples to detect bugs
+ * @author Teddy Katz
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const assert = require("assert");
+const lodash = require("lodash");
+const eslump = require("eslump");
+const SourceCodeFixer = require("../lib/util/source-code-fixer");
+const ruleConfigs = require("../lib/config/config-rule").createCoreRuleConfigs();
+
+//------------------------------------------------------------------------------
+// Public API
+//------------------------------------------------------------------------------
+
+/**
+ * Generates random JS code, runs ESLint on it, and returns a list of detected crashes or autofix bugs
+ * @param {Object} options Config options for fuzzing
+ * @param {number} options.count The number of fuzz iterations.
+ * @param {Object} options.linter The linter object to test with.
+ * @param {function(Object): string} [options.codeGenerator=eslump.generateRandomJS] A function to use to generate random
+ * code. Accepts an object argument with a `sourceType` key, indicating the source type of the generated code. The object
+ * might also be passed other keys.
+ * @param {boolean} [options.checkAutofixes=true] `true` if the fuzzer should check for autofix bugs. The fuzzer runs
+ * roughly 4 times slower with autofix checking enabled.
+ * @param {function()} [options.progressCallback] A function that gets called once for each code sample
+ * @returns {Object[]} A list of problems found. Each problem has the following properties:
+ * type (string): The type of problem. This is either "crash" (a rule crashes) or "autofix" (an autofix produces a syntax error)
+ * text (string): The text that ESLint should be run on to reproduce the problem
+ * config (object): The config object that should be used to reproduce the problem. The fuzzer will try to return a minimal
+ *     config (that only has one rule enabled), but this isn't always possible.
+ * error (*) The problem that occurred. For crashes, this will be the error stack. For autofix bugs, this will be
+ *     the parsing error object that was thrown when parsing the autofixed code.
+ */
+function fuzz(options) {
+    assert.strictEqual(typeof options, "object", "An options object must be provided");
+    assert.strictEqual(typeof options.count, "number", "The number of iterations (options.count) must be provided");
+    assert.strictEqual(typeof options.linter, "object", "An linter object (options.linter) must be provided");
+
+    const linter = options.linter;
+    const codeGenerator = options.codeGenerator || (genOptions => eslump.generateRandomJS(Object.assign({ comments: true, whitespace: true }, genOptions)));
+    const checkAutofixes = options.checkAutofixes !== false;
+    const progressCallback = options.progressCallback || (() => {});
+
+    /**
+     * Tries to isolate the smallest config that reproduces a problem
+     * @param {string} text The source text to lint
+     * @param {Object} config A config object that causes a crash or autofix error
+     * @returns {Object} A config object with only one rule enabled that produces the same crash or autofix error, if possible.
+     * Otherwise, the same as `config`
+     */
+    function isolateBadConfig(text, config) {
+        for (const ruleId of Object.keys(config.rules)) {
+            const reducedConfig = Object.assign({}, config, { rules: { [ruleId]: config.rules[ruleId] } });
+            let fixResult;
+
+            try {
+                fixResult = linter.verifyAndFix(text, reducedConfig, {});
+            } catch (err) {
+                return reducedConfig;
+            }
+
+            if (fixResult.messages.length === 1 && fixResult.messages[0].fatal) {
+                return reducedConfig;
+            }
+        }
+        return config;
+    }
+
+    /**
+     * Runs multipass autofix one pass at a time to find the last good source text before a fatal error occurs
+     * @param {string} originalText Syntactically valid source code that results in a syntax error or crash when autofixing with `config`
+     * @param {Object} config The config to lint with
+     * @returns {string} A possibly-modified version of originalText that results in the same syntax error or crash after only one pass
+     */
+    function isolateBadAutofixPass(originalText, config) {
+        let lastGoodText = originalText;
+        let currentText = originalText;
+
+        do {
+            let messages;
+
+            try {
+                messages = linter.verify(currentText, config);
+            } catch (err) {
+                return lastGoodText;
+            }
+
+            if (messages.length === 1 && messages[0].fatal) {
+                return lastGoodText;
+            }
+
+            lastGoodText = currentText;
+            currentText = SourceCodeFixer.applyFixes(linter.getSourceCode(), messages).output;
+        } while (lastGoodText !== currentText);
+
+        return lastGoodText;
+    }
+
+    const problems = [];
+
+    for (let i = 0; i < options.count; progressCallback(), i++) {
+        const sourceType = lodash.sample(["script", "module"]);
+        const text = codeGenerator({ sourceType });
+        const config = {
+            rules: lodash.mapValues(ruleConfigs, lodash.sample),
+            parserOptions: { sourceType, ecmaVersion: 2017 }
+        };
+
+        let autofixResult;
+
+        try {
+            if (checkAutofixes) {
+                autofixResult = linter.verifyAndFix(text, config, {});
+            } else {
+                linter.verify(text, config);
+            }
+        } catch (err) {
+            problems.push({ type: "crash", text, config: isolateBadConfig(text, config), error: err.stack });
+            continue;
+        }
+
+        if (checkAutofixes && autofixResult.fixed && autofixResult.messages.length === 1 && autofixResult.messages[0].fatal) {
+            const lastGoodText = isolateBadAutofixPass(text, config);
+
+            problems.push({ type: "autofix", text: lastGoodText, config: isolateBadConfig(lastGoodText, config), error: autofixResult.messages[0] });
+        }
+    }
+
+    return problems;
+}
+
+module.exports = fuzz;
diff --git a/tools/fuzzer-runner.js b/tools/fuzzer-runner.js
new file mode 100644
index 000000000..120f48c28
--- /dev/null
+++ b/tools/fuzzer-runner.js
@@ -0,0 +1,82 @@
+/**
+ * @fileoverview An opinionated wrapper around eslint-fuzzer
+ * @author Teddy Katz
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const ProgressBar = require("progress");
+const fuzz = require("./eslint-fuzzer");
+const eslint = require("..");
+const linter = new eslint.Linter();
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+// An estimate of how many times faster it is to do a crash-only fuzzer run versus an autofixing run.
+const ESTIMATED_CRASH_AUTOFIX_PERFORMANCE_RATIO = 4;
+
+// The number of crash-only tests to run for each autofix test. Right now, this is mostly arbitrary.
+const CRASH_AUTOFIX_TEST_COUNT_RATIO = 3;
+
+//------------------------------------------------------------------------------
+// Public API
+//------------------------------------------------------------------------------
+
+/**
+ * Runs the fuzzer and outputs a progress bar
+ * @param {Object} [options] Options for the fuzzer
+ * @param {number} [options.amount=300] A positive integer indicating how much testing to do. Larger values result in a higher
+ * chance of finding bugs, but cause the testing to take longer (linear increase). With the default value, the fuzzer
+ * takes about 15 seconds to run.
+ * @returns {Object[]} A list of objects, where each object represents a problem detected by the fuzzer. The objects have the same
+ * schema as objects returned from eslint-fuzzer.
+ */
+function run(options) {
+    const amount = options && options.amount || 300;
+
+    const crashTestCount = amount * CRASH_AUTOFIX_TEST_COUNT_RATIO;
+    const autofixTestCount = amount;
+
+    /*
+     * To keep the progress bar moving at a roughly constant speed, apply a different weight for finishing
+     * a crash-only fuzzer run versus an autofix fuzzer run.
+     */
+    const progressBar = new ProgressBar(
+        "Fuzzing rules [:bar] :percent, :elapseds elapsed, eta :etas",
+        { width: 30, total: crashTestCount + autofixTestCount * ESTIMATED_CRASH_AUTOFIX_PERFORMANCE_RATIO }
+    );
+
+    // Start displaying the progress bar.
+    progressBar.tick(0);
+
+    const crashTestResults = fuzz({
+        linter,
+        count: crashTestCount,
+        checkAutofixes: false,
+        progressCallback: () => {
+            progressBar.tick(1);
+            progressBar.render();
+        }
+    });
+
+    const autofixTestResults = fuzz({
+        linter,
+        count: autofixTestCount,
+        checkAutofixes: true,
+        progressCallback: () => {
+            progressBar.tick(ESTIMATED_CRASH_AUTOFIX_PERFORMANCE_RATIO);
+            progressBar.render();
+        }
+    });
+
+    return crashTestResults.concat(autofixTestResults);
+
+}
+
+module.exports = { run };


Test patch:
diff --git a/lib/testers/rule-tester.js b/lib/testers/rule-tester.js
index 9d747e96a..fc6781fdc 100644
--- a/lib/testers/rule-tester.js
+++ b/lib/testers/rule-tester.js
@@ -44,10 +44,9 @@ const lodash = require("lodash"),
     assert = require("assert"),
     util = require("util"),
     validator = require("../config/config-validator"),
-    validate = require("is-my-json-valid"),
+    ajv = require("../util/ajv"),
     Linter = require("../linter"),
     Environments = require("../config/environments"),
-    metaSchema = require("../../conf/json-schema-schema.json"),
     SourceCodeFixer = require("../util/source-code-fixer");
 
 //------------------------------------------------------------------------------
@@ -73,8 +72,6 @@ const RuleTesterParameters = [
     "output"
 ];
 
-const validateSchema = validate(metaSchema, { verbose: true });
-
 const hasOwnProperty = Function.call.bind(Object.hasOwnProperty);
 
 /**
@@ -318,12 +315,16 @@ class RuleTester {
             const schema = validator.getRuleOptionsSchema(ruleName, linter.rules);
 
             if (schema) {
-                validateSchema(schema);
+                ajv.validateSchema(schema);
+
+                if (ajv.errors) {
+                    const errors = ajv.errors.map(error => {
+                        const field = error.dataPath[0] === "." ? error.dataPath.slice(1) : error.dataPath;
+
+                        return `\t${field}: ${error.message}`;
+                    }).join("\n");
 
-                if (validateSchema.errors) {
-                    throw new Error([
-                        `Schema for rule ${ruleName} is invalid:`
-                    ].concat(validateSchema.errors.map(error => `\t${error.field}: ${error.message}`)).join("\n"));
+                    throw new Error([`Schema for rule ${ruleName} is invalid:`, errors]);
                 }
             }
 
diff --git a/tests/bin/eslint.js b/tests/bin/eslint.js
index c7e97a00c..7c5cd6552 100644
--- a/tests/bin/eslint.js
+++ b/tests/bin/eslint.js
@@ -112,6 +112,7 @@ describe("bin/eslint.js", () => {
         const tempFilePath = `${fixturesPath}/temp.js`;
         const startingText = fs.readFileSync(`${fixturesPath}/left-pad.js`).toString();
         const expectedFixedText = fs.readFileSync(`${fixturesPath}/left-pad-expected.js`).toString();
+        const expectedFixedTextQuiet = fs.readFileSync(`${fixturesPath}/left-pad-expected-quiet.js`).toString();
 
         beforeEach(() => {
             fs.writeFileSync(tempFilePath, startingText);
@@ -127,6 +128,17 @@ describe("bin/eslint.js", () => {
             return Promise.all([exitCodeAssertion, outputFileAssertion]);
         });
 
+        it("has exit code 0, fixes errors in a file, and does not report or fix warnings if --quiet and --fix are used", () => {
+            const child = runESLint(["--fix", "--quiet", "--no-eslintrc", "--no-ignore", tempFilePath]);
+            const exitCodeAssertion = assertExitCode(child, 0);
+            const stdoutAssertion = getStdout(child).then(stdout => assert.strictEqual(stdout, ""));
+            const outputFileAssertion = awaitExit(child).then(() => {
+                assert.strictEqual(fs.readFileSync(tempFilePath).toString(), expectedFixedTextQuiet);
+            });
+
+            return Promise.all([exitCodeAssertion, stdoutAssertion, outputFileAssertion]);
+        });
+
         it("has exit code 1 and fixes a file if not all rules can be fixed", () => {
             const child = runESLint(["--fix", "--no-eslintrc", "--no-ignore", "--rule", "max-len: [2, 10]", tempFilePath]);
             const exitCodeAssertion = assertExitCode(child, 1);
diff --git a/tests/conf/config-schema.js b/tests/conf/config-schema.js
new file mode 100644
index 000000000..3fdb4bb03
--- /dev/null
+++ b/tests/conf/config-schema.js
@@ -0,0 +1,26 @@
+/**
+ * @fileoverview Tests for config-schema.
+ * @author Evgeny Poberezkin
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const ajv = require("../../lib/util/ajv"),
+    configSchema = require("../../conf/config-schema.js"),
+    assert = require("assert");
+
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
+describe("config-schema", () => {
+    it("should be valid against meta-schema", () => {
+        const valid = ajv.validateSchema(configSchema);
+
+        assert.strictEqual(valid, true);
+    });
+});
diff --git a/tests/fixtures/autofix-integration/left-pad-expected-quiet.js b/tests/fixtures/autofix-integration/left-pad-expected-quiet.js
new file mode 100644
index 000000000..b189698bf
--- /dev/null
+++ b/tests/fixtures/autofix-integration/left-pad-expected-quiet.js
@@ -0,0 +1,33 @@
+/* eslint dot-notation: 2 */
+/* eslint indent: [2, 2] */
+/* eslint no-extra-parens: 2 */
+/* eslint no-implicit-coercion: 2 */
+/* eslint no-whitespace-before-property: 2 */
+/* eslint quotes: [2, "single"] */
+/* eslint semi: 2 */
+/* eslint semi-spacing: 2 */
+/* eslint space-before-function-paren: 2 */
+/* eslint space-before-blocks: 1 */
+
+/*
+* left-pad@0.0.3
+* WTFPL https://github.com/stevemao/left-pad/blob/aff6d744155a70b81f09effb8185a1564f348462/COPYING
+*/
+
+module.exports = leftpad;
+
+function leftpad (str, len, ch){
+  str = String(str);
+
+  var i = -1;
+
+  ch || (ch = ' ');
+  len = len - str.length;
+
+
+  while (++i < len) {
+    str = ch + str;
+  }
+
+  return str;
+}
diff --git a/tests/fixtures/autofix-integration/left-pad-expected.js b/tests/fixtures/autofix-integration/left-pad-expected.js
index 9e2a8321d..24da3ce17 100644
--- a/tests/fixtures/autofix-integration/left-pad-expected.js
+++ b/tests/fixtures/autofix-integration/left-pad-expected.js
@@ -7,6 +7,7 @@
 /* eslint semi: 2 */
 /* eslint semi-spacing: 2 */
 /* eslint space-before-function-paren: 2 */
+/* eslint space-before-blocks: 1 */
 
 /*
 * left-pad@0.0.3
diff --git a/tests/fixtures/autofix-integration/left-pad.js b/tests/fixtures/autofix-integration/left-pad.js
index 1dd798ff8..7b673577e 100644
--- a/tests/fixtures/autofix-integration/left-pad.js
+++ b/tests/fixtures/autofix-integration/left-pad.js
@@ -7,6 +7,7 @@
 /* eslint semi: 2 */
 /* eslint semi-spacing: 2 */
 /* eslint space-before-function-paren: 2 */
+/* eslint space-before-blocks: 1 */
 
 /*
 * left-pad@0.0.3
@@ -15,7 +16,7 @@
 
 module.exports = (leftpad)
 
-   function leftpad(str, len, ch) {
+   function leftpad(str, len, ch){
   str = ("" + str);
 
          var i = -1 ;
diff --git a/tests/lib/cli.js b/tests/lib/cli.js
index a0bcb37cf..b75640d4a 100644
--- a/tests/lib/cli.js
+++ b/tests/lib/cli.js
@@ -804,7 +804,7 @@ describe("cli", () => {
 
         });
 
-        it("should rewrite files when in fix mode and quiet mode", () => {
+        it("should provide fix predicate and rewrite files when in fix mode and quiet mode", () => {
 
             const report = {
                 errorCount: 0,
@@ -822,7 +822,7 @@ describe("cli", () => {
             };
 
             // create a fake CLIEngine to test with
-            const fakeCLIEngine = sandbox.mock().withExactArgs(sinon.match({ fix: true }));
+            const fakeCLIEngine = sandbox.mock().withExactArgs(sinon.match({ fix: sinon.match.func }));
 
             fakeCLIEngine.prototype = leche.fake(CLIEngine.prototype);
             sandbox.stub(fakeCLIEngine.prototype, "executeOnFiles").returns(report);
diff --git a/tests/lib/config/config-validator.js b/tests/lib/config/config-validator.js
index d220fdb93..5e42823b2 100644
--- a/tests/lib/config/config-validator.js
+++ b/tests/lib/config/config-validator.js
@@ -350,7 +350,7 @@ describe("Validator", () => {
 
                 const fn = validator.validate.bind(null, { rules: { "mock-no-options-rule": [2, "extra"] } }, "tests", linter.rules, linter.environments);
 
-                assert.throws(fn, "tests:\n\tConfiguration for rule \"mock-no-options-rule\" is invalid:\n\tValue \"extra\" has more items than allowed.\n");
+                assert.throws(fn, "tests:\n\tConfiguration for rule \"mock-no-options-rule\" is invalid:\n\tValue \"extra\" should NOT have more than 0 items.\n");
             });
         });
 
@@ -370,31 +370,31 @@ describe("Validator", () => {
             it("should throw if override does not specify files", () => {
                 const fn = validator.validate.bind(null, { overrides: [{ rules: {} }] }, "tests", linter.rules, linter.environments);
 
-                assert.throws(fn, "tests:\n\tESLint configuration is invalid:\n\t- \"overrides.0.files\" is required. Value: {\"rules\":{}}.\n");
+                assert.throws(fn, "tests:\n\tESLint configuration is invalid:\n\t- \"overrides[0]\" should have required property 'files'. Value: {\"rules\":{}}.\n");
             });
 
             it("should throw if override has an empty files array", () => {
                 const fn = validator.validate.bind(null, { overrides: [{ files: [] }] }, "tests", linter.rules, linter.environments);
 
-                assert.throws(fn, "tests:\n\tESLint configuration is invalid:\n\t- \"overrides.0.files\" no (or more than one) schemas match. Value: [].\n");
+                assert.throws(fn, "tests:\n\tESLint configuration is invalid:\n\t- Property \"overrides[0].files\" is the wrong type (expected string but got `[]`).\n\t- \"overrides[0].files\" should NOT have less than 1 items. Value: [].\n\t- \"overrides[0].files\" should match exactly one schema in oneOf. Value: [].\n");
             });
 
             it("should throw if override has nested overrides", () => {
                 const fn = validator.validate.bind(null, { overrides: [{ files: "*", overrides: [{ files: "*", rules: {} }] }] }, "tests", linter.rules, linter.environments);
 
-                assert.throws(fn, "tests:\n\tESLint configuration is invalid:\n\t- Unexpected top-level property \"overrides[j].overrides\".\n");
+                assert.throws(fn, "tests:\n\tESLint configuration is invalid:\n\t- Unexpected top-level property \"overrides[0].overrides\".\n");
             });
 
             it("should throw if override extends", () => {
                 const fn = validator.validate.bind(null, { overrides: [{ files: "*", extends: "eslint-recommended" }] }, "tests", linter.rules, linter.environments);
 
-                assert.throws(fn, "tests:\n\tESLint configuration is invalid:\n\t- Unexpected top-level property \"overrides[j].extends\".\n");
+                assert.throws(fn, "tests:\n\tESLint configuration is invalid:\n\t- Unexpected top-level property \"overrides[0].extends\".\n");
             });
 
             it("should throw if override tries to set root", () => {
                 const fn = validator.validate.bind(null, { overrides: [{ files: "*", root: "true" }] }, "tests", linter.rules, linter.environments);
 
-                assert.throws(fn, "tests:\n\tESLint configuration is invalid:\n\t- Unexpected top-level property \"overrides[j].root\".\n");
+                assert.throws(fn, "tests:\n\tESLint configuration is invalid:\n\t- Unexpected top-level property \"overrides[0].root\".\n");
             });
         });
 
@@ -450,13 +450,13 @@ describe("Validator", () => {
         it("should throw for incorrect configuration values", () => {
             const fn = validator.validateRuleOptions.bind(null, "mock-rule", [2, "frist"], "tests", linter.rules);
 
-            assert.throws(fn, "tests:\n\tConfiguration for rule \"mock-rule\" is invalid:\n\tValue \"frist\" must be an enum value.\n");
+            assert.throws(fn, "tests:\n\tConfiguration for rule \"mock-rule\" is invalid:\n\tValue \"frist\" should be equal to one of the allowed values.\n");
         });
 
         it("should throw for too many configuration values", () => {
             const fn = validator.validateRuleOptions.bind(null, "mock-rule", [2, "first", "second"], "tests", linter.rules);
 
-            assert.throws(fn, "tests:\n\tConfiguration for rule \"mock-rule\" is invalid:\n\tValue \"first,second\" has more items than allowed.\n");
+            assert.throws(fn, "tests:\n\tConfiguration for rule \"mock-rule\" is invalid:\n\tValue \"first,second\" should NOT have more than 1 items.\n");
         });
 
     });
diff --git a/tests/lib/linter.js b/tests/lib/linter.js
index 4acc46b3f..d52292abc 100644
--- a/tests/lib/linter.js
+++ b/tests/lib/linter.js
@@ -71,7 +71,7 @@ function getVariable(scope, name) {
 // Tests
 //------------------------------------------------------------------------------
 
-describe("eslint", () => {
+describe("Linter", () => {
     const filename = "filename.js";
     let sandbox, linter;
 
@@ -3776,6 +3776,20 @@ describe("eslint", () => {
             assert.equal(messages.output, "var a;", "Fixes were applied correctly");
             assert.isTrue(messages.fixed);
         });
+
+        it("does not require a third argument", () => {
+            const fixResult = linter.verifyAndFix("var a", {
+                rules: {
+                    semi: 2
+                }
+            });
+
+            assert.deepEqual(fixResult, {
+                fixed: true,
+                messages: [],
+                output: "var a;"
+            });
+        });
     });
 
     describe("Edge cases", () => {
diff --git a/tests/lib/rules/array-bracket-spacing.js b/tests/lib/rules/array-bracket-spacing.js
index 35f13def7..71da6a35f 100644
--- a/tests/lib/rules/array-bracket-spacing.js
+++ b/tests/lib/rules/array-bracket-spacing.js
@@ -91,7 +91,6 @@ ruleTester.run("array-bracket-spacing", rule, {
         { code: "var [ x, y\n] = z", parserOptions: { ecmaVersion: 6 }, options: ["always"] },
         { code: "var [\nx, y ] = z", parserOptions: { ecmaVersion: 6 }, options: ["always"] },
         { code: "var [\nx, y\n] = z", parserOptions: { ecmaVersion: 6 }, options: ["always"] },
-        { code: "var [\nx, y\n] = z", parserOptions: { ecmaVersion: 6 }, options: ["always"] },
         { code: "var [\nx,,,\n] = z", parserOptions: { ecmaVersion: 6 }, options: ["always"] },
         { code: "var [ ,x, ] = z", parserOptions: { ecmaVersion: 6 }, options: ["always"] },
         { code: "var [\nx, ...y\n] = z", parserOptions: { ecmaVersion: 6 }, options: ["always"] },
@@ -110,7 +109,6 @@ ruleTester.run("array-bracket-spacing", rule, {
         { code: "obj.map(function(item) { return [\n1,\n2,\n3,\n4\n]; })", options: ["never"] },
         { code: "obj['map'](function(item) { return [\n1,\n2,\n3,\n4\n]; })", options: ["never"] },
         { code: "obj['for' + 'Each'](function(item) { return [\n1,\n2,\n3,\n4\n]; })", options: ["never"] },
-        { code: "obj['for' + 'Each'](function(item) { return [\n1,\n2,\n3,\n4\n]; })", options: ["never"] },
         { code: "var arr = [1, 2, 3, 4];", options: ["never"] },
         { code: "var arr = [[1, 2], 2, 3, 4];", options: ["never"] },
         { code: "var arr = [\n1, 2, 3, 4\n];", options: ["never"] },
@@ -125,7 +123,6 @@ ruleTester.run("array-bracket-spacing", rule, {
         { code: "var [x, y\n] = z", parserOptions: { ecmaVersion: 6 }, options: ["never"] },
         { code: "var [\nx, y] = z", parserOptions: { ecmaVersion: 6 }, options: ["never"] },
         { code: "var [\nx, y\n] = z", parserOptions: { ecmaVersion: 6 }, options: ["never"] },
-        { code: "var [\nx, y\n] = z", parserOptions: { ecmaVersion: 6 }, options: ["never"] },
         { code: "var [\nx,,,\n] = z", parserOptions: { ecmaVersion: 6 }, options: ["never"] },
         { code: "var [,x,] = z", parserOptions: { ecmaVersion: 6 }, options: ["never"] },
         { code: "var [\nx, ...y\n] = z", parserOptions: { ecmaVersion: 6 }, options: ["never"] },
diff --git a/tests/lib/rules/array-element-newline.js b/tests/lib/rules/array-element-newline.js
index d1c8b7c77..1bfbfccd2 100644
--- a/tests/lib/rules/array-element-newline.js
+++ b/tests/lib/rules/array-element-newline.js
@@ -219,23 +219,6 @@ ruleTester.run("array-element-newline", rule, {
                 }
             ]
         },
-        {
-            code: "var foo = [1,(\n2\n), 3];",
-            options: ["always"],
-            output: "var foo = [1,\n(\n2\n),\n3];",
-            errors: [
-                {
-                    message: ERR_BREAK_HERE,
-                    line: 1,
-                    column: 14
-                },
-                {
-                    message: ERR_BREAK_HERE,
-                    line: 3,
-                    column: 3
-                }
-            ]
-        },
         {
             code: "var foo = [1,        \t      (\n2\n),\n3];",
             options: ["always"],
diff --git a/tests/lib/rules/arrow-body-style.js b/tests/lib/rules/arrow-body-style.js
index 46baf9a53..eda9e23a6 100644
--- a/tests/lib/rules/arrow-body-style.js
+++ b/tests/lib/rules/arrow-body-style.js
@@ -42,7 +42,6 @@ ruleTester.run("arrow-body-style", rule, {
         { code: "var foo = (retv, name) => {\nretv[name] = true;\nreturn retv;\n};", options: ["as-needed", { requireReturnForObjectLiteral: true }] },
         { code: "var foo = () => bar();", options: ["as-needed", { requireReturnForObjectLiteral: true }] },
         { code: "var foo = () => { bar(); };", options: ["as-needed", { requireReturnForObjectLiteral: true }] },
-        { code: "var addToB = (a) => { b =  b + a };", options: ["as-needed", { requireReturnForObjectLiteral: true }] },
         { code: "var foo = () => { return { bar: 0 }; };", options: ["as-needed", { requireReturnForObjectLiteral: true }] }
     ],
     invalid: [
diff --git a/tests/lib/rules/arrow-parens.js b/tests/lib/rules/arrow-parens.js
index a209d08c3..3fcd6fda6 100644
--- a/tests/lib/rules/arrow-parens.js
+++ b/tests/lib/rules/arrow-parens.js
@@ -164,6 +164,18 @@ const invalid = [
             type
         }]
     },
+    {
+        code: "(a,) => a",
+        output: "a => a",
+        options: ["as-needed"],
+        parserOptions: { ecmaVersion: 8 },
+        errors: [{
+            line: 1,
+            column: 1,
+            message: asNeededMessage,
+            type
+        }]
+    },
     {
         code: "async (a) => a",
         output: "async a => a",
diff --git a/tests/lib/rules/brace-style.js b/tests/lib/rules/brace-style.js
index 1dc11ac38..fb61054e9 100644
--- a/tests/lib/rules/brace-style.js
+++ b/tests/lib/rules/brace-style.js
@@ -229,11 +229,6 @@ ruleTester.run("brace-style", rule, {
             output: "if (foo) { \n bar(); \n}",
             errors: [{ message: OPEN_MESSAGE, type: "Punctuator" }, { message: CLOSE_MESSAGE_SINGLE, type: "Punctuator" }]
         },
-        {
-            code: "if (foo) \n { \n bar(); }",
-            output: "if (foo) { \n bar(); \n}",
-            errors: [{ message: OPEN_MESSAGE, type: "Punctuator" }, { message: CLOSE_MESSAGE_SINGLE, type: "Punctuator" }]
-        },
         {
             code: "if (a) { \nb();\n } else \n { c(); }",
             output: "if (a) { \nb();\n } else {\n c(); \n}",
diff --git a/tests/lib/rules/camelcase.js b/tests/lib/rules/camelcase.js
index 978c8ac44..af34cb219 100644
--- a/tests/lib/rules/camelcase.js
+++ b/tests/lib/rules/camelcase.js
@@ -192,16 +192,6 @@ ruleTester.run("camelcase", rule, {
                 }
             ]
         },
-        {
-            code: "obj.a_b = 2;",
-            options: [{ properties: "always" }],
-            errors: [
-                {
-                    message: "Identifier 'a_b' is not in camel case.",
-                    type: "Identifier"
-                }
-            ]
-        },
         {
             code: "var { category_id: category_id } = query;",
             parserOptions: { ecmaVersion: 6 },
diff --git a/tests/lib/rules/comma-dangle.js b/tests/lib/rules/comma-dangle.js
index 5e9adb33f..34476c6af 100644
--- a/tests/lib/rules/comma-dangle.js
+++ b/tests/lib/rules/comma-dangle.js
@@ -90,12 +90,6 @@ ruleTester.run("comma-dangle", rule, {
         { code: "var foo = {x: {\nfoo: 'bar',\n}}", options: ["only-multiline"] },
         { code: "var foo = new Map([\n[key, {\na: 1,\nb: 2,\nc: 3,\n}],\n])", options: ["always-multiline"] },
         { code: "var foo = new Map([\n[key, {\na: 1,\nb: 2,\nc: 3,\n}],\n])", options: ["only-multiline"] },
-        { code: "[,,]", options: ["always"] },
-        { code: "[\n,\n,\n]", options: ["always"] },
-        { code: "[,]", options: ["always"] },
-        { code: "[\n,\n]", options: ["always"] },
-        { code: "[]", options: ["always"] },
-        { code: "[\n]", options: ["always"] },
 
         // https://github.com/eslint/eslint/issues/3627
         {
@@ -758,19 +752,6 @@ ruleTester.run("comma-dangle", rule, {
                 }
             ]
         },
-        {
-            code: "var foo = { bar: 'baz', }",
-            output: "var foo = { bar: 'baz' }",
-            options: ["only-multiline"],
-            errors: [
-                {
-                    message: "Unexpected trailing comma.",
-                    type: "Property",
-                    line: 1,
-                    column: 23
-                }
-            ]
-        },
         {
             code: "foo({\nbar: 'baz',\nqux: 'quux'\n});",
             output: "foo({\nbar: 'baz',\nqux: 'quux',\n});",
@@ -797,19 +778,6 @@ ruleTester.run("comma-dangle", rule, {
                 }
             ]
         },
-        {
-            code: "foo({ bar: 'baz', qux: 'quux', });",
-            output: "foo({ bar: 'baz', qux: 'quux' });",
-            options: ["only-multiline"],
-            errors: [
-                {
-                    message: "Unexpected trailing comma.",
-                    type: "Property",
-                    line: 1,
-                    column: 30
-                }
-            ]
-        },
         {
             code: "var foo = [\n'baz'\n]",
             output: "var foo = [\n'baz',\n]",
@@ -1081,13 +1049,6 @@ ruleTester.run("comma-dangle", rule, {
             options: ["always-multiline"],
             errors: [{ message: "Unexpected trailing comma.", type: "ImportSpecifier" }]
         },
-        {
-            code: "import {foo,} from 'foo';",
-            output: "import {foo} from 'foo';",
-            parserOptions: { sourceType: "module" },
-            options: ["only-multiline"],
-            errors: [{ message: "Unexpected trailing comma.", type: "ImportSpecifier" }]
-        },
         {
             code: "export {foo,} from 'foo';",
             output: "export {foo} from 'foo';",
@@ -1095,13 +1056,6 @@ ruleTester.run("comma-dangle", rule, {
             options: ["always-multiline"],
             errors: [{ message: "Unexpected trailing comma.", type: "ExportSpecifier" }]
         },
-        {
-            code: "export {foo,} from 'foo';",
-            output: "export {foo} from 'foo';",
-            parserOptions: { sourceType: "module" },
-            options: ["only-multiline"],
-            errors: [{ message: "Unexpected trailing comma.", type: "ExportSpecifier" }]
-        },
         {
             code: "import {\n  foo\n} from 'foo';",
             output: "import {\n  foo,\n} from 'foo';",
diff --git a/tests/lib/rules/comma-style.js b/tests/lib/rules/comma-style.js
index 98c765c6b..0391d1bd3 100644
--- a/tests/lib/rules/comma-style.js
+++ b/tests/lib/rules/comma-style.js
@@ -71,10 +71,6 @@ ruleTester.run("comma-style", rule, {
             code: "var ar ={fst:1,\nsnd: [1,\n2]};",
             options: ["first", { exceptions: { ArrayExpression: true, ObjectExpression: true } }]
         },
-        {
-            code: "var ar ={fst:1,\nsnd: [1,\n2]};",
-            options: ["first", { exceptions: { ArrayExpression: true, ObjectExpression: true } }]
-        },
         {
             code: "var a = 'a',\nar ={fst:1,\nsnd: [1,\n2]};",
             options: ["first", {
@@ -122,12 +118,6 @@ ruleTester.run("comma-style", rule, {
                 ecmaVersion: 6
             }
         },
-        {
-            code: "var {foo\n, bar} = {foo:'apples', bar:'oranges'};",
-            parserOptions: {
-                ecmaVersion: 6
-            }
-        },
         {
             code: "var {foo\n, bar} = {foo:'apples', bar:'oranges'};",
             options: ["first", {
@@ -481,15 +471,6 @@ ruleTester.run("comma-style", rule, {
                 type: "Property"
             }]
         },
-        {
-            code: "var foo = {'a': 1, \n 'b': 2\n ,'c': 3};",
-            output: "var foo = {'a': 1 \n ,'b': 2\n ,'c': 3};",
-            options: ["first"],
-            errors: [{
-                message: FIRST_MSG,
-                type: "Property"
-            }]
-        },
         {
             code: "var a = 'a',\no = 'o',\narr = [1,\n2];",
             output: "var a = 'a',\no = 'o',\narr = [1\n,2];",
diff --git a/tests/lib/rules/computed-property-spacing.js b/tests/lib/rules/computed-property-spacing.js
index 98f34b376..69fbb4a67 100644
--- a/tests/lib/rules/computed-property-spacing.js
+++ b/tests/lib/rules/computed-property-spacing.js
@@ -35,7 +35,6 @@ ruleTester.run("computed-property-spacing", rule, {
         { code: "obj.map(function(item) { return [\n1,\n2,\n3,\n4\n]; })", options: ["always"] },
         { code: "obj[ 'map' ](function(item) { return [\n1,\n2,\n3,\n4\n]; })", options: ["always"] },
         { code: "obj[ 'for' + 'Each' ](function(item) { return [\n1,\n2,\n3,\n4\n]; })", options: ["always"] },
-        { code: "obj[ obj2[ foo ] ]", options: ["always"] },
         { code: "var foo = obj[ 1 ]", options: ["always"] },
         { code: "var foo = obj[ 'foo' ];", options: ["always"] },
         { code: "var foo = obj[ [1, 1] ];", options: ["always"] },
@@ -59,7 +58,6 @@ ruleTester.run("computed-property-spacing", rule, {
         { code: "obj.map(function(item) { return [\n1,\n2,\n3,\n4\n]; })", options: ["never"] },
         { code: "obj['map'](function(item) { return [\n1,\n2,\n3,\n4\n]; })", options: ["never"] },
         { code: "obj['for' + 'Each'](function(item) { return [\n1,\n2,\n3,\n4\n]; })", options: ["never"] },
-        { code: "obj['for' + 'Each'](function(item) { return [\n1,\n2,\n3,\n4\n]; })", options: ["never"] },
         { code: "obj[\nfoo]", options: ["never"] },
         { code: "obj[foo\n]", options: ["never"] },
         { code: "var foo = obj[1]", options: ["never"] },
@@ -128,32 +126,6 @@ ruleTester.run("computed-property-spacing", rule, {
                 }
             ]
         },
-        {
-            code: "var foo = obj[ 1];",
-            output: "var foo = obj[ 1 ];",
-            options: ["always"],
-            errors: [
-                {
-                    message: "A space is required before ']'.",
-                    type: "MemberExpression",
-                    column: 17,
-                    line: 1
-                }
-            ]
-        },
-        {
-            code: "var foo = obj[1 ];",
-            output: "var foo = obj[ 1 ];",
-            options: ["always"],
-            errors: [
-                {
-                    message: "A space is required after '['.",
-                    type: "MemberExpression",
-                    column: 14,
-                    line: 1
-                }
-            ]
-        },
         {
             code: "obj[ foo ]",
             output: "obj[foo]",
diff --git a/tests/lib/rules/dot-notation.js b/tests/lib/rules/dot-notation.js
index f20334f66..08013540a 100644
--- a/tests/lib/rules/dot-notation.js
+++ b/tests/lib/rules/dot-notation.js
@@ -178,6 +178,12 @@ ruleTester.run("dot-notation", rule, {
             code: "foo['bar']instanceof baz",
             output: "foo.bar instanceof baz",
             errors: [{ message: "[\"bar\"] is better written in dot notation." }]
+        },
+        {
+            code: "let.if()",
+            output: null, // `let["if"]()` is a syntax error because `let[` indicates a destructuring variable declaration
+            options: [{ allowKeywords: false }],
+            errors: [{ message: ".if is a syntax error." }]
         }
     ]
 });
diff --git a/tests/lib/rules/func-call-spacing.js b/tests/lib/rules/func-call-spacing.js
index 8b2bebbd3..b90e401d4 100644
--- a/tests/lib/rules/func-call-spacing.js
+++ b/tests/lib/rules/func-call-spacing.js
@@ -180,10 +180,6 @@ ruleTester.run("func-call-spacing", rule, {
             code: "f // comment\n ()",
             options: ["always", { allowNewlines: true }]
         },
-        {
-            code: "f// comment\n()",
-            options: ["always", { allowNewlines: true }]
-        },
         {
             code: "f\n/*\n*/\n()",
             options: ["always", { allowNewlines: true }]
diff --git a/tests/lib/rules/func-names.js b/tests/lib/rules/func-names.js
index cbbb1fd42..fba519173 100644
--- a/tests/lib/rules/func-names.js
+++ b/tests/lib/rules/func-names.js
@@ -193,11 +193,6 @@ ruleTester.run("func-names", rule, {
             options: ["never"],
             errors: [{ message: "Unexpected named function 'foo'.", type: "FunctionExpression" }]
         },
-        {
-            code: "({foo: function foo() {}})",
-            options: ["never"],
-            errors: [{ message: "Unexpected named method 'foo'.", type: "FunctionExpression" }]
-        },
         {
             code: "({foo: function foo() {}})",
             options: ["never"],
diff --git a/tests/lib/rules/getter-return.js b/tests/lib/rules/getter-return.js
new file mode 100644
index 000000000..82542411c
--- /dev/null
+++ b/tests/lib/rules/getter-return.js
@@ -0,0 +1,107 @@
+/**
+ * @fileoverview Enforces that a return statement is present in property getters.
+ * @author Aladdin-ADD(hh_2013@foxmail.com)
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const rule = require("../../../lib/rules/getter-return");
+const RuleTester = require("../../../lib/testers/rule-tester");
+
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
+
+// data is not working, so specify a name: "getter 'bar'"
+const name = "getter 'bar'";
+const noReturnMessage = `Expected to return a value in ${name}.`;
+const noLastReturnMessage = `Expected ${name} to always return a value.`;
+const options = [{ allowImplicit: true }];
+
+ruleTester.run("getter-return", rule, {
+
+    valid: [
+
+        // test obj: get
+        // option: {allowImplicit: false}
+        { code: "var foo = { get bar(){return true;} };" },
+
+        // option: {allowImplicit: true}
+        { code: "var foo = { get bar() {return;} };", options },
+        { code: "var foo = { get bar(){return true;} };", options },
+        { code: "var foo = { get bar(){if(bar) {return;} return true;} };", options },
+
+        // test class: get
+        // option: {allowImplicit: false}
+        { code: "class foo { get bar(){return true;} }" },
+        { code: "class foo { get bar(){if(baz){return true;} else {return false;} } }" },
+        { code: "class foo { get(){return true;} }" },
+
+        // option: {allowImplicit: true}
+        { code: "class foo { get bar(){return true;} }", options },
+        { code: "class foo { get bar(){return;} }", options },
+
+        // test object.defineProperty(s)
+        // option: {allowImplicit: false}
+        { code: "Object.defineProperty(foo, \"bar\", { get: function () {return true;}});" },
+        { code: "Object.defineProperty(foo, \"bar\", { get: function () { ~function (){ return true; }();return true;}});" },
+        { code: "Object.defineProperies(foo, { bar: { get: function () {return true;}} });" },
+        { code: "Object.defineProperies(foo, { bar: { get: function () { ~function (){ return true; }(); return true;}} });" },
+
+        // option: {allowImplicit: true}
+        { code: "Object.defineProperty(foo, \"bar\", { get: function () {return true;}});", options },
+        { code: "Object.defineProperty(foo, \"bar\", { get: function (){return;}});", options },
+        { code: "Object.defineProperies(foo, { bar: { get: function () {return true;}} });", options },
+        { code: "Object.defineProperies(foo, { bar: { get: function () {return;}} });", options },
+
+        // not getter.
+        { code: "var get = function(){};" },
+        { code: "var get = function(){ return true; };" },
+        { code: "var foo = { bar(){} };" },
+        { code: "var foo = { bar(){ return true; } };" },
+        { code: "var foo = { bar: function(){} };" },
+        { code: "var foo = { bar: function(){return;} };" },
+        { code: "var foo = { bar: function(){return true;} };" }
+    ],
+
+    invalid: [
+
+        // test obj: get
+        // option: {allowImplicit: false}
+        { code: "var foo = { get bar() {} };", errors: [{ message: noReturnMessage }] },
+        { code: "var foo = { get bar(){if(baz) {return true;}} };", errors: [{ message: noLastReturnMessage }] },
+        { code: "var foo = { get bar() { ~function () {return true;}} };", errors: [{ message: noReturnMessage }] },
+
+        // option: {allowImplicit: true}
+        { code: "var foo = { get bar() {} };", options, errors: [{ message: noReturnMessage }] },
+        { code: "var foo = { get bar() {if (baz) {return;}} };", options, errors: [{ message: noLastReturnMessage }] },
+
+        // test class: get
+        // option: {allowImplicit: false}
+        { code: "class foo { get bar(){} }", errors: [{ message: noReturnMessage }] },
+        { code: "class foo { get bar(){ if (baz) { return true; }}}", errors: [{ noLastReturnMessage }] },
+        { code: "class foo { get bar(){ ~function () { return true; }()}}", errors: [{ noLastReturnMessage }] },
+
+        // option: {allowImplicit: true}
+        { code: "class foo { get bar(){} }", options, errors: [{ message: noReturnMessage }] },
+        { code: "class foo { get bar(){if (baz) {return true;} } }", options, errors: [{ message: noLastReturnMessage }] },
+
+        // test object.defineProperty(s)
+        // option: {allowImplicit: false}
+        { code: "Object.defineProperty(foo, \"bar\", { get: function (){}});", errors: [{ noReturnMessage }] },
+        { code: "Object.defineProperty(foo, \"bar\", { get: function (){if(bar) {return true;}}});", errors: [{ message: "Expected method 'get' to always return a value." }] },
+        { code: "Object.defineProperty(foo, \"bar\", { get: function (){ ~function () { return true; }()}});", errors: [{ noReturnMessage }] },
+        { code: "Object.defineProperies(foo, { bar: { get: function () {}} });", options, errors: [{ noReturnMessage }] },
+        { code: "Object.defineProperies(foo, { bar: { get: function (){if(bar) {return true;}}}});", options, errors: [{ message: "Expected method 'get' to always return a value." }] },
+        { code: "Object.defineProperies(foo, { bar: { get: function () {~function () { return true; }()}} });", options, errors: [{ noReturnMessage }] },
+
+        // option: {allowImplicit: true}
+        { code: "Object.defineProperty(foo, \"bar\", { get: function (){}});", options, errors: [{ message: "Expected to return a value in method 'get'." }] }
+    ]
+});
diff --git a/tests/lib/rules/indent-legacy.js b/tests/lib/rules/indent-legacy.js
index 409125a18..ed2e63d05 100644
--- a/tests/lib/rules/indent-legacy.js
+++ b/tests/lib/rules/indent-legacy.js
@@ -204,11 +204,6 @@ ruleTester.run("indent-legacy", rule, {
             "    );",
             options: [4]
         },
-        {
-            code:
-            "var x = 0 && { a: 1, b: 2 };",
-            options: [4]
-        },
         {
             code:
             "require('http').request({hostname: 'localhost',\n" +
@@ -3557,18 +3552,6 @@ ruleTester.run("indent-legacy", rule, {
             "}",
             errors: expectedErrors([4, 4, 0, "Keyword"])
         },
-        {
-            code:
-            "function foo() {\n" +
-            "  bar();\n" +
-            "\t\t}",
-            output:
-            "function foo() {\n" +
-            "  bar();\n" +
-            "}",
-            options: [2],
-            errors: expectedErrors([[3, "0 spaces", "2 tabs", "BlockStatement"]])
-        },
         {
             code:
             "function foo() {\n" +
@@ -3601,18 +3584,6 @@ ruleTester.run("indent-legacy", rule, {
             options: [2],
             errors: expectedErrors([[4, "2 spaces", "4", "ReturnStatement"]])
         },
-        {
-            code:
-            "function foo() {\n" +
-            "  bar();\n" +
-            "\t\t}",
-            output:
-            "function foo() {\n" +
-            "  bar();\n" +
-            "}",
-            options: [2],
-            errors: expectedErrors([[3, "0 spaces", "2 tabs", "BlockStatement"]])
-        },
         {
             code:
             "function test(){\n" +
diff --git a/tests/lib/rules/indent.js b/tests/lib/rules/indent.js
index 12b42d712..0001c76fd 100644
--- a/tests/lib/rules/indent.js
+++ b/tests/lib/rules/indent.js
@@ -67,7 +67,7 @@ function unIndent(strings) {
     return lines.map(line => line.slice(minLineIndent)).join("\n");
 }
 
-const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } } });
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 8, ecmaFeatures: { jsx: true } } });
 
 ruleTester.run("indent", rule, {
     valid: [
@@ -218,10 +218,6 @@ ruleTester.run("indent", rule, {
             `,
             options: [4]
         },
-        {
-            code: "var x = 0 && { a: 1, b: 2 };",
-            options: [4]
-        },
         {
             code: unIndent`
                 require('http').request({hostname: 'localhost',
@@ -518,6 +514,64 @@ ruleTester.run("indent", rule, {
             `,
             options: [2, { VariableDeclarator: 2, SwitchCase: 1 }]
         },
+        {
+            code: unIndent`
+                var
+                    x = {
+                        a: 1,
+                    },
+                    y = {
+                        b: 2
+                    }
+            `
+        },
+        {
+            code: unIndent`
+                const
+                    x = {
+                        a: 1,
+                    },
+                    y = {
+                        b: 2
+                    }
+            `
+        },
+        {
+            code: unIndent`
+                let
+                    x = {
+                        a: 1,
+                    },
+                    y = {
+                        b: 2
+                    }
+            `
+        },
+        {
+            code: unIndent`
+                var foo = { a: 1 }, bar = {
+                    b: 2
+                };
+            `
+        },
+        {
+            code: unIndent`
+                var foo = { a: 1 }, bar = {
+                        b: 2
+                    },
+                    baz = {
+                        c: 3
+                    }
+            `
+        },
+        {
+            code: unIndent`
+                const {
+                        foo
+                    } = 1,
+                    bar = 2
+            `
+        },
         {
             code: unIndent`
                 var foo = 1,
@@ -1416,6 +1470,27 @@ ruleTester.run("indent", rule, {
             `,
             options: [4, { VariableDeclarator: 0, SwitchCase: 1 }]
         },
+        {
+            code: unIndent`
+                [[
+                ], function(
+                    foo
+                ) {}
+                ]
+            `
+        },
+        {
+            code: unIndent`
+                define([
+                    'foo'
+                ], function(
+                    bar
+                ) {
+                    baz;
+                }
+                )
+            `
+        },
         {
             code: unIndent`
                 const func = function (opts) {
@@ -2102,6 +2177,33 @@ ruleTester.run("indent", rule, {
             `,
             options: [2]
         },
+        {
+            code: unIndent`
+                (
+                    foo
+                )(
+                    bar
+                )
+            `
+        },
+        {
+            code: unIndent`
+                (() =>
+                    foo
+                )(
+                    bar
+                )
+            `
+        },
+        {
+            code: unIndent`
+                (() => {
+                    foo();
+                })(
+                    bar
+                )
+            `
+        },
         {
 
             // Don't lint the indentation of the first token after a :
@@ -3262,6 +3364,12 @@ ruleTester.run("indent", rule, {
                 ; [1, 2, 3].map(baz)
             `
         },
+        {
+            code: unIndent`
+                if (foo)
+                    ;
+            `
+        },
         {
             code: "x => {}"
         },
@@ -3810,6 +3918,67 @@ ruleTester.run("indent", rule, {
             `
         },
 
+        // https://github.com/eslint/eslint/issues/8815
+        {
+            code: unIndent`
+                async function test() {
+                    const {
+                        foo,
+                        bar,
+                    } = await doSomethingAsync(
+                        1,
+                        2,
+                        3,
+                    );
+                }
+            `
+        },
+        {
+            code: unIndent`
+                function* test() {
+                    const {
+                        foo,
+                        bar,
+                    } = yield doSomethingAsync(
+                        1,
+                        2,
+                        3,
+                    );
+                }
+            `
+        },
+        {
+            code: unIndent`
+                ({
+                    a: b
+                } = +foo(
+                    bar
+                ));
+            `
+        },
+        {
+            code: unIndent`
+                const {
+                    foo,
+                    bar,
+                } = typeof foo(
+                    1,
+                    2,
+                    3,
+                );
+            `
+        },
+        {
+            code: unIndent`
+                const {
+                    foo,
+                    bar,
+                } = +(
+                    foo
+                );
+            `
+        },
+
         //----------------------------------------------------------------------
         // JSX tests
         // https://github.com/eslint/eslint/issues/8425
@@ -4568,6 +4737,44 @@ ruleTester.run("indent", rule, {
                     </span>
                 )
             `
+        },
+        {
+            code: unIndent`
+                <div>
+                    {
+                        /* foo */
+                    }
+                </div>
+            `
+        },
+
+        // https://github.com/eslint/eslint/issues/8832
+        {
+            code: unIndent`
+                <div>
+                    {
+                        (
+                            1
+                        )
+                    }
+                </div>
+            `
+        },
+        {
+            code: unIndent`
+                function A() {
+                    return (
+                        <div>
+                            {
+                                b && (
+                                    <div>
+                                    </div>
+                                )
+                            }
+                        </div>
+                    );
+                }
+            `
         }
     ],
 
@@ -5402,6 +5609,46 @@ ruleTester.run("indent", rule, {
                 [5, 0, 2, "Punctuator"]
             ])
         },
+        {
+            code: unIndent`
+                [[
+                ], function(
+                        foo
+                    ) {}
+                ]
+            `,
+            output: unIndent`
+                [[
+                ], function(
+                    foo
+                ) {}
+                ]
+            `,
+            errors: expectedErrors([[3, 4, 8, "Identifier"], [4, 0, 4, "Punctuator"]])
+        },
+        {
+            code: unIndent`
+                define([
+                    'foo'
+                ], function(
+                        bar
+                    ) {
+                    baz;
+                }
+                )
+            `,
+            output: unIndent`
+                define([
+                    'foo'
+                ], function(
+                    bar
+                ) {
+                    baz;
+                }
+                )
+            `,
+            errors: expectedErrors([[4, 4, 8, "Identifier"], [5, 0, 4, "Punctuator"]])
+        },
         {
             code: unIndent`
                 while (1 < 2)
@@ -6489,20 +6736,6 @@ ruleTester.run("indent", rule, {
             `,
             errors: expectedErrors([4, 4, 0, "Keyword"])
         },
-        {
-            code: unIndent`
-                function foo() {
-                  bar();
-                \t\t}
-            `,
-            output: unIndent`
-                function foo() {
-                  bar();
-                }
-            `,
-            options: [2],
-            errors: expectedErrors([[3, "0 spaces", "2 tabs", "Punctuator"]])
-        },
         {
             code: unIndent`
                 function foo() {
@@ -6539,20 +6772,6 @@ ruleTester.run("indent", rule, {
             options: [2],
             errors: expectedErrors([[4, 2, 4, "Punctuator"]])
         },
-        {
-            code: unIndent`
-                function foo() {
-                  bar();
-                \t\t}
-            `,
-            output: unIndent`
-                function foo() {
-                  bar();
-                }
-            `,
-            options: [2],
-            errors: expectedErrors([[3, "0 spaces", "2 tabs", "Punctuator"]])
-        },
         {
             code: unIndent`
                 function test(){
@@ -7876,6 +8095,17 @@ ruleTester.run("indent", rule, {
             `,
             errors: expectedErrors([3, 0, 4, "Punctuator"])
         },
+        {
+            code: unIndent`
+                if (foo)
+                ;
+            `,
+            output: unIndent`
+                if (foo)
+                    ;
+            `,
+            errors: expectedErrors([2, 4, 0, "Punctuator"])
+        },
         {
             code: unIndent`
                 import {foo}
@@ -7951,6 +8181,57 @@ ruleTester.run("indent", rule, {
             `,
             errors: expectedErrors([[4, 4, 8, "Identifier"], [5, 0, 4, "Punctuator"]])
         },
+        {
+            code: unIndent`
+                (
+                    foo
+                )(
+                        bar
+                    )
+            `,
+            output: unIndent`
+                (
+                    foo
+                )(
+                    bar
+                )
+            `,
+            errors: expectedErrors([[4, 4, 8, "Identifier"], [5, 0, 4, "Punctuator"]])
+        },
+        {
+            code: unIndent`
+                (() =>
+                    foo
+                )(
+                        bar
+                    )
+            `,
+            output: unIndent`
+                (() =>
+                    foo
+                )(
+                    bar
+                )
+            `,
+            errors: expectedErrors([[4, 4, 8, "Identifier"], [5, 0, 4, "Punctuator"]])
+        },
+        {
+            code: unIndent`
+                (() => {
+                    foo();
+                })(
+                        bar
+                    )
+            `,
+            output: unIndent`
+                (() => {
+                    foo();
+                })(
+                    bar
+                )
+            `,
+            errors: expectedErrors([[4, 4, 8, "Identifier"], [5, 0, 4, "Punctuator"]])
+        },
         {
             code: unIndent`
                 foo.
@@ -8665,6 +8946,44 @@ ruleTester.run("indent", rule, {
                 )
             `,
             errors: expectedErrors([[5, 4, 8, "Punctuator"], [6, 4, 8, "Punctuator"], [7, 0, 4, "Punctuator"]])
+        },
+        {
+            code: unIndent`
+                <div>
+                    {
+                    (
+                        1
+                    )
+                    }
+                </div>
+            `,
+            output: unIndent`
+                <div>
+                    {
+                        (
+                            1
+                        )
+                    }
+                </div>
+            `,
+            errors: expectedErrors([[3, 8, 4, "Punctuator"], [4, 12, 8, "Numeric"], [5, 8, 4, "Punctuator"]])
+        },
+        {
+            code: unIndent`
+                <div>
+                    {
+                      /* foo */
+                    }
+                </div>
+            `,
+            output: unIndent`
+                <div>
+                    {
+                        /* foo */
+                    }
+                </div>
+            `,
+            errors: expectedErrors([3, 8, 6, "Block"])
         }
     ]
 });
diff --git a/tests/lib/rules/keyword-spacing.js b/tests/lib/rules/keyword-spacing.js
index 6cd8a5813..31ba13d7e 100644
--- a/tests/lib/rules/keyword-spacing.js
+++ b/tests/lib/rules/keyword-spacing.js
@@ -360,7 +360,6 @@ ruleTester.run("keyword-spacing", rule, {
         { code: "; class Bar {} ;", options: [NEITHER], parserOptions: { ecmaVersion: 6 } },
 
         // not conflict with `space-in-parens`
-        { code: "(class {})", parserOptions: { ecmaVersion: 6 } },
         { code: "( class{})", options: [NEITHER], parserOptions: { ecmaVersion: 6 } },
 
         // not conflict with `space-infix-ops`
diff --git a/tests/lib/rules/lines-around-directive.js b/tests/lib/rules/lines-around-directive.js
index 9ae021b30..737d1ec7b 100644
--- a/tests/lib/rules/lines-around-directive.js
+++ b/tests/lib/rules/lines-around-directive.js
@@ -91,6 +91,8 @@ ruleTester.run("lines-around-directive", rule, {
             code: "#!/usr/bin/env node\n//comment\n\n'use strict';\n'use asm';\n\nvar foo;",
             options: ["always"]
         },
+
+        // does not warn about lack of blank newlines between directives
         {
             code: "//comment\n\n'use strict';\n'use asm';\n\nvar foo;",
             options: ["always"]
@@ -182,12 +184,6 @@ ruleTester.run("lines-around-directive", rule, {
             options: ["always"]
         },
 
-        // does not warn about lack of blank newlines between directives
-        {
-            code: "//comment\n\n'use strict';\n'use asm';\n\nvar foo;",
-            options: ["always"]
-        },
-
         // is not affected by JSDoc comments when at top of function block
         {
             code: "/*\n * JSDoc comment\n */\nfunction foo() {\n'use strict';\n\nvar bar;\n}",
diff --git a/tests/lib/rules/max-lines.js b/tests/lib/rules/max-lines.js
index 5fa21eebe..354a59c25 100644
--- a/tests/lib/rules/max-lines.js
+++ b/tests/lib/rules/max-lines.js
@@ -158,20 +158,6 @@ ruleTester.run("max-lines", rule, {
             ].join("\n"),
             options: [{ max: 2, skipBlankLines: true }],
             errors: [{ message: errorMessage(2, 6) }]
-        },
-        {
-            code: [
-                "//a single line comment",
-                "var xy;",
-                " ",
-                "var xy;",
-                " ",
-                " /* a multiline",
-                " really really",
-                " long comment*/"
-            ].join("\n"),
-            options: [{ max: 2, skipComments: true }],
-            errors: [{ message: errorMessage(2, 4) }]
         }
     ]
 });
diff --git a/tests/lib/rules/multiline-ternary.js b/tests/lib/rules/multiline-ternary.js
index 74221b6c0..30750246c 100644
--- a/tests/lib/rules/multiline-ternary.js
+++ b/tests/lib/rules/multiline-ternary.js
@@ -37,6 +37,17 @@ ruleTester.run("multiline-ternary", rule, {
         { code: "a\n? b\n? c\n: d\n: e", options: ["always"] },
         { code: "a\n? (b\n? c\n: d)\n: e", options: ["always"] },
 
+        // "always-multiline"
+        { code: "a\n? b\n: c", options: ["always-multiline"] },
+        { code: "a ?\nb :\nc", options: ["always-multiline"] },
+        { code: "a\n? b\n? c\n: d\n: e", options: ["always-multiline"] },
+        { code: "a\n? (b\n? c\n: d)\n: e", options: ["always-multiline"] },
+        { code: "a ? b : c", options: ["always-multiline"] },
+        { code: "a ? b ? c : d : e", options: ["always-multiline"] },
+        { code: "a ? (b ? c : d) : e", options: ["always-multiline"] },
+        { code: "a\n? (b ? c : d)\n: e", options: ["always-multiline"] },
+        { code: "a ?\n(b ? c : d) :\ne", options: ["always-multiline"] },
+
         // "never"
         { code: "a ? b : c", options: ["never"] },
         { code: "a ? b ? c : d : e", options: ["never"] },
@@ -282,6 +293,137 @@ ruleTester.run("multiline-ternary", rule, {
             }]
         },
 
+        // "always-multiline"
+        {
+            code: "a\n? b : c",
+            options: ["always-multiline"],
+            errors: [{
+                message: expectedConsAltMsg,
+                line: 2,
+                column: 3
+            }]
+        },
+        {
+            code: "a ? b\n: c",
+            options: ["always-multiline"],
+            errors: [{
+                message: expectedTestConsMsg,
+                line: 1,
+                column: 1
+            }]
+        },
+        {
+            code: "a &&\nb ? c : d",
+            options: ["always-multiline"],
+            errors: [{
+                message: expectedTestConsMsg,
+                line: 1,
+                column: 1
+            },
+            {
+                message: expectedConsAltMsg,
+                line: 2,
+                column: 5
+            }]
+        },
+        {
+            code: "a ? b +\nc : d",
+            options: ["always-multiline"],
+            errors: [{
+                message: expectedTestConsMsg,
+                line: 1,
+                column: 1
+            },
+            {
+                message: expectedConsAltMsg,
+                line: 1,
+                column: 5
+            }]
+        },
+        {
+            code: "a ? b : c +\nd",
+            options: ["always-multiline"],
+            errors: [{
+                message: expectedTestConsMsg,
+                line: 1,
+                column: 1
+            },
+            {
+                message: expectedConsAltMsg,
+                line: 1,
+                column: 5
+            }]
+        },
+        {
+            code: "a ?\n(b ? c : d) : e",
+            options: ["always-multiline"],
+            errors: [{
+                message: expectedConsAltMsg,
+                line: 2,
+                column: 2
+            }]
+        },
+        {
+            code: "a ? (b ? c : d) :\ne",
+            options: ["always-multiline"],
+            errors: [{
+                message: expectedTestConsMsg,
+                line: 1,
+                column: 1
+            }]
+        },
+        {
+            code: "a ? (b\n? c\n: d) : e",
+            options: ["always-multiline"],
+            errors: [{
+                message: expectedTestConsMsg,
+                line: 1,
+                column: 1
+            },
+            {
+                message: expectedConsAltMsg,
+                line: 1,
+                column: 6
+            }]
+        },
+        {
+            code: "a ?\n(b ? c\n: d) : e",
+            options: ["always-multiline"],
+            errors: [{
+                message: expectedConsAltMsg,
+                line: 2,
+                column: 2
+            },
+            {
+                message: expectedTestConsMsg,
+                line: 2,
+                column: 2
+            }]
+        },
+        {
+            code: "a ?\n(b\n? c : d) : e",
+            options: ["always-multiline"],
+            errors: [{
+                message: expectedConsAltMsg,
+                line: 2,
+                column: 2
+            },
+            {
+                message: expectedConsAltMsg,
+                line: 3,
+                column: 3
+            }]
+        },
+        {
+            code: "a ?\n(b\n? c\n : d) : e",
+            options: ["always-multiline"],
+            errors: [{
+                message: expectedConsAltMsg,
+                line: 2,
+                column: 2
+            }]
+        },
+
         // "never"
         {
             code: "a\n? b : c",
diff --git a/tests/lib/rules/no-bitwise.js b/tests/lib/rules/no-bitwise.js
index 3c294a6dd..f5edfc6c5 100644
--- a/tests/lib/rules/no-bitwise.js
+++ b/tests/lib/rules/no-bitwise.js
@@ -25,7 +25,6 @@ ruleTester.run("no-bitwise", rule, {
         "a += b",
         { code: "~[1, 2, 3].indexOf(1)", options: [{ allow: ["~"] }] },
         { code: "~1<<2 === -8", options: [{ allow: ["~", "<<"] }] },
-        { code: "~1<<2 === -8", options: [{ allow: ["~", "<<"] }] },
         { code: "a|0", options: [{ int32Hint: true }] },
         { code: "a|0", options: [{ allow: ["|"], int32Hint: false }] }
     ],
diff --git a/tests/lib/rules/no-cond-assign.js b/tests/lib/rules/no-cond-assign.js
index e9fa93b78..36cd15a1e 100644
--- a/tests/lib/rules/no-cond-assign.js
+++ b/tests/lib/rules/no-cond-assign.js
@@ -24,7 +24,6 @@ ruleTester.run("no-cond-assign", rule, {
         "var x = 0; if (x == 0) { var b = 1; }",
         { code: "var x = 0; if (x == 0) { var b = 1; }", options: ["always"] },
         "var x = 5; while (x < 5) { x = x + 1; }",
-        "var x = 0; if (x == 0) { var b = 1; }",
         "if ((someNode = someNode.parentNode) !== null) { }",
         { code: "if ((someNode = someNode.parentNode) !== null) { }", options: ["except-parens"] },
         "if ((a = b));",
diff --git a/tests/lib/rules/no-debugger.js b/tests/lib/rules/no-debugger.js
index a907405c8..9edf059c4 100644
--- a/tests/lib/rules/no-debugger.js
+++ b/tests/lib/rules/no-debugger.js
@@ -23,6 +23,15 @@ ruleTester.run("no-debugger", rule, {
         "var test = { debugger: 1 }; test.debugger;"
     ],
     invalid: [
-        { code: "debugger", errors: [{ message: "Unexpected 'debugger' statement.", type: "DebuggerStatement" }], output: "" }
+        {
+            code: "debugger",
+            output: "",
+            errors: [{ message: "Unexpected 'debugger' statement.", type: "DebuggerStatement" }]
+        },
+        {
+            code: "if (foo) debugger",
+            output: null,
+            errors: [{ message: "Unexpected 'debugger' statement.", type: "DebuggerStatement" }]
+        }
     ]
 });
diff --git a/tests/lib/rules/no-extra-label.js b/tests/lib/rules/no-extra-label.js
index 0362a655f..b046fd812 100644
--- a/tests/lib/rules/no-extra-label.js
+++ b/tests/lib/rules/no-extra-label.js
@@ -30,8 +30,6 @@ ruleTester.run("no-extra-label", rule, {
         "A: while (a) { switch (b) { case 0: break A; } }",
         "A: while (a) { switch (b) { case 0: continue A; } }",
         "A: switch (a) { case 0: while (b) { break A; } }",
-        "A: switch (a) { case 0: while (b) { break A; } }",
-        "A: switch (a) { case 0: switch (b) { case 0: break A; } }",
         "A: switch (a) { case 0: switch (b) { case 0: break A; } }",
         "A: for (;;) { while (b) { break A; } }",
         "A: do { switch (b) { case 0: break A; break; } } while (a);",
diff --git a/tests/lib/rules/no-extra-parens.js b/tests/lib/rules/no-extra-parens.js
index 54990b96e..be32041c8 100644
--- a/tests/lib/rules/no-extra-parens.js
+++ b/tests/lib/rules/no-extra-parens.js
@@ -450,7 +450,12 @@ ruleTester.run("no-extra-parens", rule, {
         {
             code: "((function(){}).foo)();",
             options: ["functions"]
-        }
+        },
+        "(let)[foo]",
+        "for ((let) in foo);",
+        "for ((let[foo]) in bar);",
+        "for ((let)[foo] in bar);",
+        "for ((let[foo].bar) in baz);"
     ],
 
     invalid: [
@@ -1052,6 +1057,24 @@ ruleTester.run("no-extra-parens", rule, {
             "MemberExpression",
             1,
             { parserOptions: { ecmaVersion: 2015 } }
+        ),
+        invalid(
+            "(let).foo",
+            "let.foo",
+            "Identifier",
+            1
+        ),
+        invalid(
+            "for ((let.foo) in bar);",
+            "for (let.foo in bar);",
+            "MemberExpression",
+            1
+        ),
+        invalid(
+            "for ((let).foo.bar in baz);",
+            "for (let.foo.bar in baz);",
+            "Identifier",
+            1
         )
     ]
 });
diff --git a/tests/lib/rules/no-fallthrough.js b/tests/lib/rules/no-fallthrough.js
index 3f1f1786e..c2e48fadf 100644
--- a/tests/lib/rules/no-fallthrough.js
+++ b/tests/lib/rules/no-fallthrough.js
@@ -28,7 +28,6 @@ ruleTester.run("no-fallthrough", rule, {
         "switch(foo) { case 0: a(); /* falls through */ case 1: b(); }",
         "switch(foo) { case 0: a()\n /* falls through */ case 1: b(); }",
         "switch(foo) { case 0: a(); /* fall through */ case 1: b(); }",
-        "switch(foo) { case 0: a(); /* falls through */ case 1: b(); }",
         "switch(foo) { case 0: a(); /* fallthrough */ case 1: b(); }",
         "switch(foo) { case 0: a(); /* FALLS THROUGH */ case 1: b(); }",
         "function foo() { switch(foo) { case 0: a(); return; case 1: b(); }; }",
diff --git a/tests/lib/rules/no-lone-blocks.js b/tests/lib/rules/no-lone-blocks.js
index 8205ba7d4..201adba81 100644
--- a/tests/lib/rules/no-lone-blocks.js
+++ b/tests/lib/rules/no-lone-blocks.js
@@ -73,8 +73,6 @@ ruleTester.run("no-lone-blocks", rule, {
         // Non-block-level bindings, even in ES6
         { code: "{ function bar() {} }", errors: [{ message: "Block is redundant.", type: "BlockStatement" }], parserOptions: { ecmaVersion: 6 } },
         { code: "{var x = 1;}", errors: [{ message: "Block is redundant.", type: "BlockStatement" }], parserOptions: { ecmaVersion: 6 } },
-        { code: "{ function bar() {} }", errors: [{ message: "Block is redundant.", type: "BlockStatement" }], parserOptions: { ecmaVersion: 6 } },
-        { code: "{var x = 1;}", errors: [{ message: "Block is redundant.", type: "BlockStatement" }], parserOptions: { ecmaVersion: 6 } },
 
         {
             code: "{ \n{var x = 1;}\n let y = 2; } {let z = 1;}",
diff --git a/tests/lib/rules/no-multi-spaces.js b/tests/lib/rules/no-multi-spaces.js
index cfa1ab5e4..b983430a4 100644
--- a/tests/lib/rules/no-multi-spaces.js
+++ b/tests/lib/rules/no-multi-spaces.js
@@ -263,14 +263,6 @@ ruleTester.run("no-multi-spaces", rule, {
                 type: "Punctuator"
             }]
         },
-        {
-            code: "var o = { fetch: function    () {} };",
-            output: "var o = { fetch: function () {} };",
-            errors: [{
-                message: "Multiple spaces found before '('.",
-                type: "Punctuator"
-            }]
-        },
         {
             code: "function foo      () {}",
             output: "function foo () {}",
@@ -319,14 +311,6 @@ ruleTester.run("no-multi-spaces", rule, {
                 type: "Punctuator"
             }]
         },
-        {
-            code: "var o = { fetch: function    () {} };",
-            output: "var o = { fetch: function () {} };",
-            errors: [{
-                message: "Multiple spaces found before '('.",
-                type: "Punctuator"
-            }]
-        },
         {
             code: "throw  error;",
             output: "throw error;",
diff --git a/tests/lib/rules/no-regex-spaces.js b/tests/lib/rules/no-regex-spaces.js
index 1ed316f8a..72e1fd4e2 100644
--- a/tests/lib/rules/no-regex-spaces.js
+++ b/tests/lib/rules/no-regex-spaces.js
@@ -23,7 +23,8 @@ ruleTester.run("no-regex-spaces", rule, {
         "var foo = RegExp('bar\t\t\tbaz');",
         "var foo = new RegExp('bar\t\t\tbaz');",
         "var RegExp = function() {}; var foo = new RegExp('bar   baz');",
-        "var RegExp = function() {}; var foo = RegExp('bar   baz');"
+        "var RegExp = function() {}; var foo = RegExp('bar   baz');",
+        "var foo = /  +/;"
     ],
 
     invalid: [
@@ -69,6 +70,26 @@ ruleTester.run("no-regex-spaces", rule, {
                     type: "CallExpression"
                 }
             ]
+        },
+        {
+            code: "var foo = /bar    ?baz/;",
+            output: "var foo = /bar {3} ?baz/;",
+            errors: [
+                {
+                    message: "Spaces are hard to count. Use {3}.",
+                    type: "Literal"
+                }
+            ]
+        },
+        {
+            code: "var foo = new RegExp('bar    ');",
+            output: "var foo = new RegExp('bar {4}');",
+            errors: [
+                {
+                    message: "Spaces are hard to count. Use {4}.",
+                    type: "NewExpression"
+                }
+            ]
         }
     ]
 });
diff --git a/tests/lib/rules/no-sync.js b/tests/lib/rules/no-sync.js
index 349e198cb..026db6862 100644
--- a/tests/lib/rules/no-sync.js
+++ b/tests/lib/rules/no-sync.js
@@ -20,10 +20,18 @@ const ruleTester = new RuleTester();
 
 ruleTester.run("no-sync", rule, {
     valid: [
-        "var foo = fs.foo.foo();"
+        "var foo = fs.foo.foo();",
+        { code: "var foo = fs.fooSync;", options: [{ allowAtRootLevel: true }] },
+        { code: "if (true) {fs.fooSync();}", options: [{ allowAtRootLevel: true }] }
     ],
     invalid: [
         { code: "var foo = fs.fooSync();", errors: [{ message: "Unexpected sync method: 'fooSync'.", type: "MemberExpression" }] },
-        { code: "var foo = fs.fooSync;", errors: [{ message: "Unexpected sync method: 'fooSync'.", type: "MemberExpression" }] }
+        { code: "var foo = fs.fooSync();", options: [{ allowAtRootLevel: false }], errors: [{ message: "Unexpected sync method: 'fooSync'.", type: "MemberExpression" }] },
+        { code: "if (true) {fs.fooSync();}", errors: [{ message: "Unexpected sync method: 'fooSync'.", type: "MemberExpression" }] },
+        { code: "var foo = fs.fooSync;", errors: [{ message: "Unexpected sync method: 'fooSync'.", type: "MemberExpression" }] },
+        { code: "function someFunction() {fs.fooSync();}", errors: [{ message: "Unexpected sync method: 'fooSync'.", type: "MemberExpression" }] },
+        { code: "function someFunction() {fs.fooSync();}", options: [{ allowAtRootLevel: true }], errors: [{ message: "Unexpected sync method: 'fooSync'.", type: "MemberExpression" }] },
+        { code: "var a = function someFunction() {fs.fooSync();}", options: [{ allowAtRootLevel: true }], errors: [{ message: "Unexpected sync method: 'fooSync'.", type: "MemberExpression" }] }
+
     ]
 });
diff --git a/tests/lib/rules/no-undef.js b/tests/lib/rules/no-undef.js
index 43e9e0a84..8bf0d4774 100644
--- a/tests/lib/rules/no-undef.js
+++ b/tests/lib/rules/no-undef.js
@@ -30,7 +30,6 @@ ruleTester.run("no-undef", rule, {
         "var a; function f() { a = 1; }",
         "/*global b:true*/ b++;",
         "/*eslint-env browser*/ window;",
-        "/*eslint-env browser*/ window;",
         "/*eslint-env node*/ require(\"a\");",
         "Object; isNaN();",
         "toString()",
diff --git a/tests/lib/rules/no-unsafe-finally.js b/tests/lib/rules/no-unsafe-finally.js
index 050976f25..438d9c2e8 100644
--- a/tests/lib/rules/no-unsafe-finally.js
+++ b/tests/lib/rules/no-unsafe-finally.js
@@ -106,18 +106,6 @@ ruleTester.run("no-unsafe-finally", rule, {
         {
             code: "var foo = function() { a: switch (true) { case true: try {} finally { switch (true) { case true: break a; } } } }",
             errors: [{ message: "Unsafe usage of BreakStatement.", type: "BreakStatement", line: 1, column: 98 }]
-        },
-        {
-            code: "var foo = function() { a: switch (true) { case true: try {} finally { switch (true) { case true: break a; } } } }",
-            errors: [{ message: "Unsafe usage of BreakStatement.", type: "BreakStatement", line: 1, column: 98 }]
-        },
-        {
-            code: "var foo = function() { a: while (true) try {} finally { switch (true) { case true: break a; } } }",
-            errors: [{ message: "Unsafe usage of BreakStatement.", type: "BreakStatement", line: 1, column: 84 }]
-        },
-        {
-            code: "var foo = function() { a: while (true) try {} finally { switch (true) { case true: continue; } } }",
-            errors: [{ message: "Unsafe usage of ContinueStatement.", type: "ContinueStatement", line: 1, column: 84 }]
         }
     ]
 });
diff --git a/tests/lib/rules/no-use-before-define.js b/tests/lib/rules/no-use-before-define.js
index 3e1cf6501..409c54a89 100644
--- a/tests/lib/rules/no-use-before-define.js
+++ b/tests/lib/rules/no-use-before-define.js
@@ -81,7 +81,6 @@ ruleTester.run("no-use-before-define", rule, {
         { code: "\"use strict\"; { a(); function a() {} }", parserOptions: { ecmaVersion: 6 }, errors: [{ message: "'a' was used before it was defined.", type: "Identifier" }] },
         { code: "{a; let a = 1}", parserOptions: { ecmaVersion: 6 }, errors: [{ message: "'a' was used before it was defined.", type: "Identifier" }] },
         { code: "switch (foo) { case 1: a();\n default: \n let a;}", parserOptions: { ecmaVersion: 6 }, errors: [{ message: "'a' was used before it was defined.", type: "Identifier" }] },
-        { code: "var f = () => a; var a;", parserOptions: { ecmaVersion: 6 }, errors: [{ message: "'a' was used before it was defined.", type: "Identifier" }] },
         { code: "if (true) { function foo() { a; } let a;}", parserOptions: { ecmaVersion: 6 }, errors: [{ message: "'a' was used before it was defined.", type: "Identifier" }] },
 
         // object style options
diff --git a/tests/lib/rules/no-useless-rename.js b/tests/lib/rules/no-useless-rename.js
index fc6da680c..5fad7768e 100644
--- a/tests/lib/rules/no-useless-rename.js
+++ b/tests/lib/rules/no-useless-rename.js
@@ -32,7 +32,6 @@ ruleTester.run("no-useless-rename", rule, {
         "let {[foo]: foo} = obj;",
         "let {['foo']: foo} = obj;",
         "let {[foo]: bar} = obj;",
-        "let {['foo']: bar} = obj;",
         "function func({foo}) {}",
         "function func({foo: bar}) {}",
         "function func({foo: bar, baz: qux}) {}",
diff --git a/tests/lib/rules/no-warning-comments.js b/tests/lib/rules/no-warning-comments.js
index fd7d0a4e1..fd9f0ceff 100644
--- a/tests/lib/rules/no-warning-comments.js
+++ b/tests/lib/rules/no-warning-comments.js
@@ -26,14 +26,12 @@ ruleTester.run("no-warning-comments", rule, {
         { code: "// any comment", options: [{ location: "anywhere" }] },
         { code: "// any comment with TODO, FIXME or XXX", options: [{ location: "start" }] },
         { code: "// any comment with TODO, FIXME or XXX" },
-        { code: "// any comment with TODO, FIXME or XXX" },
         { code: "/* any block comment */", options: [{ terms: ["fixme"] }] },
         { code: "/* any block comment */", options: [{ terms: ["fixme", "todo"] }] },
         { code: "/* any block comment */" },
         { code: "/* any block comment */", options: [{ location: "anywhere" }] },
         { code: "/* any block comment with TODO, FIXME or XXX */", options: [{ location: "start" }] },
         { code: "/* any block comment with TODO, FIXME or XXX */" },
-        { code: "/* any block comment with TODO, FIXME or XXX */" },
         { code: "/* any block comment with (TODO, FIXME's or XXX!) */" },
         { code: "// comments containing terms as substrings like TodoMVC", options: [{ terms: ["todo"], location: "anywhere" }] },
         { code: "// special regex characters don't cause problems", options: [{ terms: ["[aeiou]"], location: "anywhere" }] },
diff --git a/tests/lib/rules/object-shorthand.js b/tests/lib/rules/object-shorthand.js
index ffa077126..b20c0e793 100644
--- a/tests/lib/rules/object-shorthand.js
+++ b/tests/lib/rules/object-shorthand.js
@@ -248,11 +248,6 @@ ruleTester.run("object-shorthand", rule, {
             code: "var x = {[foo]: 'foo'}",
             options: ["consistent-as-needed"]
         },
-        {
-            code: "var x = {...bar}",
-            parserOptions: { ecmaFeatures: { experimentalObjectRestSpread: true } },
-            options: ["consistent-as-needed"]
-        },
         {
             code: "var x = {bar, ...baz}",
             parserOptions: { ecmaFeatures: { experimentalObjectRestSpread: true } },
@@ -306,10 +301,6 @@ ruleTester.run("object-shorthand", rule, {
             code: "function foo() { ({ x: () => { arguments; } }) }",
             options: ["always", { avoidExplicitReturnArrows: true }]
         },
-        {
-            code: "function foo() { ({ x: () => { arguments; } }) }",
-            options: ["always", { avoidExplicitReturnArrows: true }]
-        },
         {
             code: `
                 class Foo extends Bar {
diff --git a/tests/lib/rules/one-var.js b/tests/lib/rules/one-var.js
index 0eb1e3b6e..e7d44adf2 100644
--- a/tests/lib/rules/one-var.js
+++ b/tests/lib/rules/one-var.js
@@ -67,10 +67,6 @@ ruleTester.run("one-var", rule, {
             code: "var bar, baz; var a = true; var b = false;",
             options: [{ uninitialized: "always", initialized: "never" }]
         },
-        {
-            code: "var bar, baz; var a = true; var b = false;",
-            options: [{ uninitialized: "always", initialized: "never" }]
-        },
         {
             code: "var bar = true, baz = false; var a; var b;",
             options: [{ uninitialized: "never", initialized: "always" }]
@@ -174,11 +170,6 @@ ruleTester.run("one-var", rule, {
             parserOptions: { ecmaVersion: 6 },
             options: [{ const: "always" }]
         },
-        {
-            code: "var foo, bar; const a=1; const b=2; let c, d",
-            parserOptions: { ecmaVersion: 6 },
-            options: [{ var: "always", let: "always" }]
-        },
         {
             code: "for (let x of foo) {}; for (let y of foo) {}",
             parserOptions: { ecmaVersion: 6 },
diff --git a/tests/lib/rules/operator-assignment.js b/tests/lib/rules/operator-assignment.js
index fb1ff889e..19fb03bd8 100644
--- a/tests/lib/rules/operator-assignment.js
+++ b/tests/lib/rules/operator-assignment.js
@@ -72,16 +72,6 @@ ruleTester.run("operator-assignment", rule, {
             options: ["never"]
         },
         "x = y ** x",
-        "x = x < y",
-        "x = x > y",
-        "x = x <= y",
-        "x = x >= y",
-        "x = x == y",
-        "x = x != y",
-        "x = x === y",
-        "x = x !== y",
-        "x = x && y",
-        "x = x || y",
         "x = x * y + z"
     ],
 
diff --git a/tests/lib/rules/padded-blocks.js b/tests/lib/rules/padded-blocks.js
index a691e1108..f1adab495 100644
--- a/tests/lib/rules/padded-blocks.js
+++ b/tests/lib/rules/padded-blocks.js
@@ -22,7 +22,6 @@ const ruleTester = new RuleTester(),
 
 ruleTester.run("padded-blocks", rule, {
     valid: [
-        { code: "{\n\na();\n\n}" },
         { code: "{\n\na();\n\n}" },
         { code: "{\n\n\na();\n\n\n}" },
         { code: "{\n\n//comment\na();\n\n}" },
diff --git a/tests/lib/rules/padding-line-between-statements.js b/tests/lib/rules/padding-line-between-statements.js
index 2cb260f3e..e4a0d8109 100644
--- a/tests/lib/rules/padding-line-between-statements.js
+++ b/tests/lib/rules/padding-line-between-statements.js
@@ -1404,34 +1404,6 @@ ruleTester.run("padding-line-between-statements", rule, {
                 { blankLine: "any", prev: ["const", "let", "var"], next: ["const", "let", "var"] }
             ]
         },
-        {
-            code: "for(let a in obj){\n break;\n}",
-            options: [
-                { blankLine: "always", prev: ["const", "let", "var"], next: "*" },
-                { blankLine: "any", prev: ["const", "let", "var"], next: ["const", "let", "var"] }
-            ]
-        },
-        {
-            code: "for(var a in obj){\n break;\n}",
-            options: [
-                { blankLine: "always", prev: ["const", "let", "var"], next: "*" },
-                { blankLine: "any", prev: ["const", "let", "var"], next: ["const", "let", "var"] }
-            ]
-        },
-        {
-            code: "for(let a in obj){\n break;\n}",
-            options: [
-                { blankLine: "never", prev: ["const", "let", "var"], next: "*" },
-                { blankLine: "any", prev: ["const", "let", "var"], next: ["const", "let", "var"] }
-            ]
-        },
-        {
-            code: "for(var a in obj){\n break;\n}",
-            options: [
-                { blankLine: "never", prev: ["const", "let", "var"], next: "*" },
-                { blankLine: "any", prev: ["const", "let", "var"], next: ["const", "let", "var"] }
-            ]
-        },
 
         // should handle export specifiers
         {
@@ -2472,14 +2444,6 @@ ruleTester.run("padding-line-between-statements", rule, {
             ],
             errors: [MESSAGE_ALWAYS]
         },
-        {
-            code: "{}\nfoo()",
-            output: "{}\n\nfoo()",
-            options: [
-                { blankLine: "always", prev: "block-like", next: "*" }
-            ],
-            errors: [MESSAGE_ALWAYS]
-        },
         {
             code: "if(a){}\nfoo()",
             output: "if(a){}\n\nfoo()",
@@ -2772,14 +2736,6 @@ ruleTester.run("padding-line-between-statements", rule, {
             ],
             errors: [MESSAGE_ALWAYS]
         },
-        {
-            code: "{\n}\nfoo()",
-            output: "{\n}\n\nfoo()",
-            options: [
-                { blankLine: "always", prev: "block-like", next: "*" }
-            ],
-            errors: [MESSAGE_ALWAYS]
-        },
         {
             code: "if(a){\n}\nfoo()",
             output: "if(a){\n}\n\nfoo()",
@@ -2820,14 +2776,6 @@ ruleTester.run("padding-line-between-statements", rule, {
             ],
             errors: [MESSAGE_ALWAYS]
         },
-        {
-            code: "switch(a){case 0:}\nfoo()",
-            output: "switch(a){case 0:}\n\nfoo()",
-            options: [
-                { blankLine: "always", prev: "block-like", next: "*" }
-            ],
-            errors: [MESSAGE_ALWAYS]
-        },
         {
             code: "try{\n}catch(e){\n}\nfoo()",
             output: "try{\n}catch(e){\n}\n\nfoo()",
diff --git a/tests/lib/rules/quote-props.js b/tests/lib/rules/quote-props.js
index 93f74bb42..e721a4912 100644
--- a/tests/lib/rules/quote-props.js
+++ b/tests/lib/rules/quote-props.js
@@ -42,7 +42,6 @@ ruleTester.run("quote-props", rule, {
         { code: "({ a: 0, volatile: 0 })", options: ["as-needed"] },
         { code: "({ a: 0, '-b': 0 })", options: ["as-needed"] },
         { code: "({ a: 0, '@': 0 })", options: ["as-needed"] },
-        { code: "({ a: 0, 0: 0 })", options: ["as-needed"] },
         { code: "({ a: 0, '0x0': 0 })", options: ["as-needed"] },
         { code: "({ ' 0': 0, '0x0': 0 })", options: ["as-needed"] },
         { code: "({ '0 ': 0 })", options: ["as-needed"] },
diff --git a/tests/lib/rules/radix.js b/tests/lib/rules/radix.js
index 45c94fb82..f523edc84 100644
--- a/tests/lib/rules/radix.js
+++ b/tests/lib/rules/radix.js
@@ -20,7 +20,6 @@ ruleTester.run("radix", rule, {
         "parseInt(\"10\", 10);",
         "parseInt(\"10\", foo);",
         "Number.parseInt(\"10\", foo);",
-        "Number.parseInt(\"10\", foo);",
         {
             code: "parseInt(\"10\", 10);",
             options: ["always"]
diff --git a/tests/lib/rules/require-jsdoc.js b/tests/lib/rules/require-jsdoc.js
index 0158d049d..75ac9e813 100644
--- a/tests/lib/rules/require-jsdoc.js
+++ b/tests/lib/rules/require-jsdoc.js
@@ -39,7 +39,6 @@ ruleTester.run("require-jsdoc", rule, {
         "var object = {\n/**\n @method myFunction - Some function \n*/\nmyFunction: function() {} }",
         "var object = {\n/**\n @function myFunction - Some function \n*/\nmyFunction: function() {} }",
 
-        "var array = [1,2,3];\narray.forEach(function() {});",
         "var array = [1,2,3];\narray.filter(function() {});",
         "Object.keys(this.options.rules || {}).forEach(function(name) {}.bind(this));",
         "var object = { name: 'key'};\nObject.keys(object).forEach(function() {})",
diff --git a/tests/lib/rules/semi.js b/tests/lib/rules/semi.js
index 2c272b6eb..506d10cae 100644
--- a/tests/lib/rules/semi.js
+++ b/tests/lib/rules/semi.js
@@ -112,7 +112,6 @@ ruleTester.run("semi", rule, {
         { code: "var x = 5, y", output: "var x = 5, y;", errors: [{ message: "Missing semicolon.", type: "VariableDeclaration" }] },
         { code: "debugger", output: "debugger;", errors: [{ message: "Missing semicolon.", type: "DebuggerStatement" }] },
         { code: "foo()", output: "foo();", errors: [{ message: "Missing semicolon.", type: "ExpressionStatement" }] },
-        { code: "var x = 5, y", output: "var x = 5, y;", errors: [{ message: "Missing semicolon.", type: "VariableDeclaration" }] },
         { code: "for (var a in b) var i ", output: "for (var a in b) var i; ", errors: [{ message: "Missing semicolon.", type: "VariableDeclaration" }] },
         { code: "for (;;){var i}", output: "for (;;){var i;}", errors: [{ message: "Missing semicolon.", type: "VariableDeclaration" }] },
         { code: "for (;;) var i ", output: "for (;;) var i; ", errors: [{ message: "Missing semicolon.", type: "VariableDeclaration" }] },
@@ -131,7 +130,6 @@ ruleTester.run("semi", rule, {
         { code: "var x = 5, y;", output: "var x = 5, y", options: ["never"], errors: [{ message: "Extra semicolon.", type: "VariableDeclaration" }] },
         { code: "debugger;", output: "debugger", options: ["never"], errors: [{ message: "Extra semicolon.", type: "DebuggerStatement" }] },
         { code: "foo();", output: "foo()", options: ["never"], errors: [{ message: "Extra semicolon.", type: "ExpressionStatement" }] },
-        { code: "var x = 5, y;", output: "var x = 5, y", options: ["never"], errors: [{ message: "Extra semicolon.", type: "VariableDeclaration" }] },
         { code: "for (var a in b) var i; ", output: "for (var a in b) var i ", options: ["never"], errors: [{ message: "Extra semicolon.", type: "VariableDeclaration" }] },
         { code: "for (;;){var i;}", output: "for (;;){var i}", options: ["never"], errors: [{ message: "Extra semicolon.", type: "VariableDeclaration" }] },
         { code: "for (;;) var i; ", output: "for (;;) var i ", options: ["never"], errors: [{ message: "Extra semicolon.", type: "VariableDeclaration" }] },
diff --git a/tests/lib/rules/space-in-parens.js b/tests/lib/rules/space-in-parens.js
index f933105e8..9e344e977 100644
--- a/tests/lib/rules/space-in-parens.js
+++ b/tests/lib/rules/space-in-parens.js
@@ -337,12 +337,6 @@ ruleTester.run("space-in-parens", rule, {
                 { message: REJECTED_SPACE_ERROR, line: 1, column: 13 }
             ]
         },
-        {
-            code: "(( 1 + 2 ))",
-            output: "( ( 1 + 2 ) )",
-            options: ["always", { exceptions: ["[]"] }],
-            errors: [MISSING_SPACE_ERROR, MISSING_SPACE_ERROR]
-        },
         {
             code: "( ( 1 + 2 ) )",
             output: "((1 + 2))",
@@ -402,12 +396,6 @@ ruleTester.run("space-in-parens", rule, {
             options: ["always", { exceptions: ["()"] }],
             errors: [MISSING_SPACE_ERROR]
         },
-        {
-            code: "var result = (1 / (1 + 2)) + 3",
-            output: "var result = (1 / (1 + 2) ) + 3",
-            options: ["never", { exceptions: ["()"] }],
-            errors: [MISSING_SPACE_ERROR]
-        },
         {
             code: "foo( )",
             output: "foo()",
diff --git a/tests/lib/testers/rule-tester.js b/tests/lib/testers/rule-tester.js
index b2ca45e9e..86bf40cde 100644
--- a/tests/lib/testers/rule-tester.js
+++ b/tests/lib/testers/rule-tester.js
@@ -619,7 +619,7 @@ describe("RuleTester", () => {
                     { code: "var answer = 6 * 7;", options: ["bar"], errors: [{ message: "Expected nothing." }] }
                 ]
             });
-        }, /Schema for rule .* is invalid/);
+        }, "Schema for rule no-invalid-schema is invalid:,\titems: should be object\n\titems[0].enum: should NOT have less than 1 items\n\titems: should match some schema in anyOf");
 
     });
 
@@ -635,7 +635,7 @@ describe("RuleTester", () => {
                     { code: "var answer = 6 * 7;", options: ["bar"], errors: [{ message: "Expected foo." }] }
                 ]
             });
-        }, /Value "bar" must be an enum value./);
+        }, /Value "bar" should be equal to one of the allowed values./);
 
     });
 
diff --git a/tests/lib/util/source-code-fixer.js b/tests/lib/util/source-code-fixer.js
index f3a51856f..1ce5adc25 100644
--- a/tests/lib/util/source-code-fixer.js
+++ b/tests/lib/util/source-code-fixer.js
@@ -10,6 +10,7 @@
 
 const assert = require("chai").assert,
     espree = require("espree"),
+    sinon = require("sinon"),
     SourceCode = require("../../../lib/util/source-code"),
     SourceCodeFixer = require("../../../lib/util/source-code-fixer");
 
@@ -162,6 +163,69 @@ describe("SourceCodeFixer", () => {
             assert.equal(result.output.length, 0);
         });
 
+        describe("shouldFix parameter", () => {
+
+            beforeEach(() => {
+                sourceCode = new SourceCode(TEST_CODE, TEST_AST);
+            });
+
+            it("Should not perform any fixes if 'shouldFix' is false", () => {
+                const result = SourceCodeFixer.applyFixes(sourceCode, [INSERT_AT_END], false);
+
+                assert.isFalse(result.fixed);
+                assert.equal(result.output, sourceCode.text);
+            });
+
+            it("Should perform fixes if 'shouldFix' is not provided", () => {
+                const result = SourceCodeFixer.applyFixes(sourceCode, [INSERT_AT_END]);
+
+                assert.isTrue(result.fixed);
+            });
+
+            it("should call a function provided as 'shouldFix' for each message", () => {
+                const shouldFixSpy = sinon.spy();
+
+                SourceCodeFixer.applyFixes(sourceCode, [INSERT_IN_MIDDLE, INSERT_AT_START, INSERT_AT_END], shouldFixSpy);
+                assert.isTrue(shouldFixSpy.calledThrice);
+            });
+
+            it("should provide a message object as an argument to 'shouldFix'", () => {
+                const shouldFixSpy = sinon.spy();
+
+                SourceCodeFixer.applyFixes(sourceCode, [INSERT_AT_START], shouldFixSpy);
+                assert.equal(shouldFixSpy.firstCall.args[0], INSERT_AT_START);
+            });
+
+            it("should not perform fixes if 'shouldFix' function returns false", () => {
+                const shouldFixSpy = sinon.spy(() => false);
+                const result = SourceCodeFixer.applyFixes(sourceCode, [INSERT_AT_START], shouldFixSpy);
+
+                assert.isFalse(result.fixed);
+            });
+
+            it("should return original text as output if 'shouldFix' function prevents all fixes", () => {
+                const shouldFixSpy = sinon.spy(() => false);
+                const result = SourceCodeFixer.applyFixes(sourceCode, [INSERT_AT_START], shouldFixSpy);
+
+                assert.equal(result.output, TEST_CODE);
+            });
+
+            it("should only apply fixes for which the 'shouldFix' function returns true", () => {
+                const shouldFixSpy = sinon.spy(problem => problem.message === "foo");
+                const result = SourceCodeFixer.applyFixes(sourceCode, [INSERT_AT_START, REPLACE_ID], shouldFixSpy);
+
+                assert.equal(result.output, "var foo = 6 * 7;");
+            });
+
+            it("is called without access to internal eslint state", () => {
+                const shouldFixSpy = sinon.spy();
+
+                SourceCodeFixer.applyFixes(sourceCode, [INSERT_AT_START], shouldFixSpy);
+
+                assert.isUndefined(shouldFixSpy.thisValues[0]);
+            });
+        });
+
         describe("Text Insertion", () => {
 
             it("should insert text at the end of the code", () => {
diff --git a/tests/tools/eslint-fuzzer.js b/tests/tools/eslint-fuzzer.js
new file mode 100644
index 000000000..8ea25e0f5
--- /dev/null
+++ b/tests/tools/eslint-fuzzer.js
@@ -0,0 +1,258 @@
+"use strict";
+
+const assert = require("chai").assert;
+const eslint = require("../..");
+const espree = require("espree");
+const sinon = require("sinon");
+const configRule = require("../../lib/config/config-rule");
+
+describe("eslint-fuzzer", function() {
+    let fakeRule, fuzz;
+
+    /*
+     * These tests take awhile because isolating which rule caused an error requires running eslint up to hundreds of
+     * times, one rule at a time.
+     */
+    this.timeout(15000); // eslint-disable-line no-invalid-this
+
+    const linter = new eslint.Linter();
+    const coreRules = linter.getRules();
+    const fixableRuleNames = Array.from(coreRules)
+        .filter(rulePair => rulePair[1].meta && rulePair[1].meta.fixable)
+        .map(rulePair => rulePair[0]);
+    const CRASH_BUG = new TypeError("error thrown from a rule");
+
+    // A comment to disable all core fixable rules
+    const disableFixableRulesComment = `// eslint-disable-line ${fixableRuleNames.join(",")}`;
+
+    before(() => {
+        const realCoreRuleConfigs = configRule.createCoreRuleConfigs();
+
+        // Make sure the config generator generates a config for "test-fuzzer-rule"
+        sinon.stub(configRule, "createCoreRuleConfigs").returns(Object.assign(realCoreRuleConfigs, { "test-fuzzer-rule": [2] }));
+
+        // Create a closure around `fakeRule` so that tests can reassign it and have the changes take effect.
+        linter.defineRule("test-fuzzer-rule", Object.assign(context => fakeRule(context), { meta: { fixable: "code" } }));
+
+        fuzz = require("../../tools/eslint-fuzzer");
+    });
+
+    after(() => {
+        linter.reset();
+        configRule.createCoreRuleConfigs.restore();
+    });
+
+    describe("when running in crash-only mode", () => {
+        describe("when a rule crashes on the given input", () => {
+            it("should report the crash with a minimal config", () => {
+                fakeRule = () => ({
+                    Program() {
+                        throw CRASH_BUG;
+                    }
+                });
+
+                const results = fuzz({ count: 1, codeGenerator: () => "foo", checkAutofixes: false, linter });
+
+                assert.strictEqual(results.length, 1);
+                assert.strictEqual(results[0].type, "crash");
+                assert.strictEqual(results[0].text, "foo");
+                assert.deepEqual(results[0].config.rules, { "test-fuzzer-rule": 2 });
+                assert.strictEqual(results[0].error, CRASH_BUG.stack);
+            });
+        });
+
+        describe("when no rules crash", () => {
+            it("should return an empty array", () => {
+                fakeRule = () => ({});
+
+                assert.deepEqual(fuzz({ count: 1, codeGenerator: () => "foo", checkAutofixes: false, linter }), []);
+            });
+        });
+    });
+
+    describe("when running in crash-and-autofix mode", () => {
+        const INVALID_SYNTAX = "this is not valid javascript syntax";
+        let expectedSyntaxError;
+
+        try {
+            espree.parse(INVALID_SYNTAX);
+        } catch (err) {
+            expectedSyntaxError = err;
+        }
+
+        describe("when a rule crashes on the given input", () => {
+            it("should report the crash with a minimal config", () => {
+                fakeRule = () => ({
+                    Program() {
+                        throw CRASH_BUG;
+                    }
+                });
+
+                const results = fuzz({ count: 1, codeGenerator: () => "foo", checkAutofixes: false, linter });
+
+                assert.strictEqual(results.length, 1);
+                assert.strictEqual(results[0].type, "crash");
+                assert.strictEqual(results[0].text, "foo");
+                assert.deepEqual(results[0].config.rules, { "test-fuzzer-rule": 2 });
+                assert.strictEqual(results[0].error, CRASH_BUG.stack);
+            });
+        });
+
+        describe("when a rule's autofix produces valid syntax", () => {
+            it("does not report any errors", () => {
+
+                // Replaces programs that start with "foo" with "bar"
+                fakeRule = context => ({
+                    Program(node) {
+                        if (context.getSourceCode().text.startsWith("foo")) {
+                            context.report({
+                                node,
+                                message: "no foos allowed",
+                                fix: fixer => fixer.replaceText(node, `bar ${disableFixableRulesComment}`)
+                            });
+                        }
+                    }
+                });
+
+                const results = fuzz({
+                    count: 1,
+
+                    /*
+                     * To ensure that no other rules produce a different autofix and mess up the test, add a big disable
+                     * comment for all core fixable rules.
+                     */
+                    codeGenerator: () => `foo ${disableFixableRulesComment}`,
+                    checkAutofixes: true,
+                    linter
+                });
+
+                assert.deepEqual(results, []);
+            });
+        });
+
+        describe("when a rule's autofix produces invalid syntax on the first pass", () => {
+            it("reports an autofix error with a minimal config", () => {
+
+                // Replaces programs that start with "foo" with invalid syntax
+                fakeRule = context => ({
+                    Program(node) {
+                        const sourceCode = context.getSourceCode();
+
+                        if (sourceCode.text.startsWith("foo")) {
+                            context.report({
+                                node,
+                                message: "no foos allowed",
+                                fix: fixer => fixer.replaceTextRange([0, sourceCode.text.length], INVALID_SYNTAX)
+                            });
+                        }
+                    }
+                });
+
+                const results = fuzz({
+                    count: 1,
+                    codeGenerator: () => `foo ${disableFixableRulesComment}`,
+                    checkAutofixes: true,
+                    linter
+                });
+
+                assert.strictEqual(results.length, 1);
+                assert.strictEqual(results[0].type, "autofix");
+                assert.strictEqual(results[0].text, `foo ${disableFixableRulesComment}`);
+                assert.deepEqual(results[0].config.rules, { "test-fuzzer-rule": 2 });
+                assert.deepEqual(results[0].error, {
+                    ruleId: null,
+                    fatal: true,
+                    severity: 2,
+                    source: INVALID_SYNTAX,
+                    message: `Parsing error: ${expectedSyntaxError.message}`,
+                    line: expectedSyntaxError.lineNumber,
+                    column: expectedSyntaxError.column
+                });
+            });
+        });
+
+        describe("when a rule's autofix produces invalid syntax on the second pass", () => {
+            it("reports an autofix error with a minimal config and the text from the second pass", () => {
+                const intermediateCode = `bar ${disableFixableRulesComment}`;
+
+                // Replaces programs that start with "foo" with invalid syntax
+                fakeRule = context => ({
+                    Program(node) {
+                        const sourceCode = context.getSourceCode();
+
+                        if (sourceCode.text.startsWith("foo") || sourceCode.text.startsWith("bar")) {
+                            context.report({
+                                node,
+                                message: "no foos allowed",
+                                fix(fixer) {
+                                    return fixer.replaceTextRange(
+                                        [0, sourceCode.text.length],
+                                        sourceCode.text === intermediateCode ? INVALID_SYNTAX : intermediateCode
+                                    );
+                                }
+                            });
+                        }
+                    }
+                });
+
+                const results = fuzz({
+                    count: 1,
+                    codeGenerator: () => `foo ${disableFixableRulesComment}`,
+                    checkAutofixes: true,
+                    linter
+                });
+
+                assert.strictEqual(results.length, 1);
+                assert.strictEqual(results[0].type, "autofix");
+                assert.strictEqual(results[0].text, intermediateCode);
+                assert.deepEqual(results[0].config.rules, { "test-fuzzer-rule": 2 });
+                assert.deepEqual(results[0].error, {
+                    ruleId: null,
+                    fatal: true,
+                    severity: 2,
+                    source: INVALID_SYNTAX,
+                    message: `Parsing error: ${expectedSyntaxError.message}`,
+                    line: expectedSyntaxError.lineNumber,
+                    column: expectedSyntaxError.column
+                });
+            });
+        });
+
+        describe("when a rule crashes on the second autofix pass", () => {
+            it("reports a crash error with a minimal config", () => {
+
+                // Replaces programs that start with "foo" with invalid syntax
+                fakeRule = context => ({
+                    Program(node) {
+                        const sourceCode = context.getSourceCode();
+
+                        if (sourceCode.text.startsWith("foo")) {
+                            context.report({
+                                node,
+                                message: "no foos allowed",
+                                fix: fixer => fixer.replaceText(node, "bar")
+                            });
+                        } else if (sourceCode.text.startsWith("bar")) {
+                            throw CRASH_BUG;
+                        }
+                    }
+                });
+
+                const results = fuzz({
+                    count: 1,
+                    codeGenerator: () => `foo ${disableFixableRulesComment}`,
+                    checkAutofixes: true,
+                    linter
+                });
+
+                assert.strictEqual(results.length, 1);
+                assert.strictEqual(results[0].type, "crash");
+
+                // TODO: (not-an-aardvark) It might be more useful to output the intermediate code here.
+                assert.strictEqual(results[0].text, `foo ${disableFixableRulesComment}`);
+                assert.deepEqual(results[0].config.rules, { "test-fuzzer-rule": 2 });
+                assert.strictEqual(results[0].error, CRASH_BUG.stack);
+            });
+        });
+    });
+});