Repository: eslint/eslint
Pull request #8773 (2017-06-21 15:31:52)

Fix: no-regex-spaces rule incorrectly fixes quantified spaces
If the last space has a quantifer attached, that one should not be
considered.

**What is the purpose of this pull request? (put an "X" next to item)**

[ ] Documentation update
[x ] Bug fix ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/bug-report.md))
[ ] New rule ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/rule-proposal.md))
[ ] Changes an existing rule ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/rule-change-proposal.md))
[ ] Add autofixing to a rule
[ ] Add a CLI option
[ ] Add something to the core
[ ] Other, please explain:

**Tell us about your environment**

* **ESLint Version:** latest
* **Node Version:** various
* **npm Version:** various

**What parser (default, Babel-ESLint, etc.) are you using?** default

**Please show your full configuration:**
```
{
  "rules": {
    "no-regex-spaces": "error"
  }
}
```

**What did you do? Please include the actual source code causing the issue.**
```
λ cat test.js
/  +/;

λ eslint --fix test.js
```


**What did you expect to happen?**

eslint to produce valid javascript

**What actually happened? Please include the actual, raw output from ESLint.**
```
λ eslint --fix test.js

/home/keri/projects/test/test.js
  1:2  error  Parsing error: Error parsing regular expression: Invalid regular expression: / {2}+/: Nothing to repeat

✖ 1 problem (1 error, 0 warnings)

λ cat test.js
/ {2}+/;

```

**What changes did you make? (Give an overview)**

I took the simplest option in this case: If there is a quantified space at the end of a string of spaces, ignore that space.
While it may have been better to analyse the quantifier and generate a new one accordingly, there are many cases that would need to be covered (see below) and I thought that would have been too complicated. Better to fix the bug first anyways.
`/  +/;` => `/ {2,}/;`
`/  */;` => `/ +/`;

Here's the original PR btw: https://github.com/eslint/eslint/pull/141/files

**Is there anything you'd like reviewers to focus on?**

no =]



eslintbot: LGTM
jsf-clabot: [![CLA assistant check](https://cla.js.foundation/pull/badge/not_signed)](https://cla.js.foundation/eslint/eslint?pullRequest=8773) <br/>Thank you for your submission, we really appreciate it. Like many open source projects, we ask that you sign our [Contributor License Agreement](https://cla.js.foundation/eslint/eslint?pullRequest=8773) before we can accept your contribution.<br/>

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/.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 7626f8970..5e017bec8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,34 @@
+v4.1.1 - June 25, 2017
+
+* f307aa0 Fix: ensure configs from a plugin are cached separately (fixes #8792) (#8798) (Teddy Katz)
+* 8b48ae8 Docs: Add doc on parser services (fixes #8390) (#8795) (Victor Hom)
+* 0d041e7 Fix: avoid crashing when using baseConfig with extends (fixes #8791) (#8797) (Teddy Katz)
+* 03213bb Chore: improve comment explanation of `indent` internal functions (#8800) (Teddy Katz)
+* d2e88ed Chore: Fix misleading comment in ConfigCache.js (#8799) (Teddy Katz)
+
+v4.1.0 - June 23, 2017
+
+* e8f1362 Docs: Remove wrong descriptions in `padded-block` rule (#8783) (Plusb Preco)
+* 291a783 Update: `enforceForArrowConditionals` to `no-extra-parens` (fixes #6196) (#8439) (Evilebot Tnawi)
+* a21dd32 New: Add `overrides`/`files` options for glob-based config (fixes #3611) (#8081) (Sylvan Mably)
+* 879688c Update: Add ignoreComments option to no-trailing-spaces (#8061) (Jake Roussel)
+* b58ae2e Chore: Only instantiate fileEntryCache when cache flage set (perf) (#8763) (Gyandeep Singh)
+* 9851288 Update: fix indent errors on multiline destructure (fixes #8729) (#8756) (Victor Hom)
+* 3608f06 Docs: Increase visibility of code of conduct (fixes #8758) (#8764) (Kai Cataldo)
+* 673a58b Update: support multiple fixes in a report (fixes #7348) (#8101) (Toru Nagashima)
+* 7a1bc38 Fix: don't pass default parserOptions to custom parsers (fixes #8744) (#8745) (Teddy Katz)
+* c5b4052 Chore: enable computed-property-spacing on ESLint codebase (#8760) (Teddy Katz)
+* 3419f64 Docs: describe how to use formatters on the formatter demo page (#8754) (Teddy Katz)
+* a3ff8f2 Chore: combine tests in tests/lib/eslint.js and tests/lib/linter.js (#8746) (Teddy Katz)
+* b7cc1e6 Fix: Space-infix-ops should ignore type annotations in TypeScript (#8341) (Reyad Attiyat)
+* 46e73ee Fix: eslint --init installs wrong dependencies of popular styles (fixes #7338) (#8713) (Toru Nagashima)
+* a82361b Chore: Prevent package-lock.json files from being created (fixes #8742) (#8747) (Teddy Katz)
+* 5f81a68 New: Add eslintIgnore support to package.json (fixes #8458) (#8690) (Victor Hom)
+* b5a70b4 Update: fix multiline binary operator/parentheses indentation (#8719) (Teddy Katz)
+* ab8b016 Update: fix MemberExpression indentation with "off" option (fixes #8721) (#8724) (Teddy Katz)
+* eb5d12c Update: Add Fixer method to Linter API (#8631) (Gyandeep Singh)
+* 26a2daa Chore: Cache fs reads in ignored-paths (fixes #8363) (#8706) (Victor Hom)
+
 v4.0.0 - June 11, 2017
 
 * 4aefb49 Chore: avoid using deprecated rules on ESLint codebase (#8708) (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..0d6567f92 100644
--- a/Makefile.js
+++ b/Makefile.js
@@ -758,13 +758,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/config-schema.js b/conf/config-schema.js
new file mode 100644
index 000000000..4ef958c40
--- /dev/null
+++ b/conf/config-schema.js
@@ -0,0 +1,68 @@
+/**
+ * @fileoverview Defines a schema for configs.
+ * @author Sylvan Mably
+ */
+
+"use strict";
+
+const baseConfigProperties = {
+    env: { type: "object" },
+    globals: { type: "object" },
+    parser: { type: ["string", "null"] },
+    parserOptions: { type: "object" },
+    plugins: { type: "array" },
+    rules: { type: "object" },
+    settings: { type: "object" }
+};
+
+const overrideProperties = Object.assign(
+    {},
+    baseConfigProperties,
+    {
+        files: {
+            oneOf: [
+                { type: "string" },
+                {
+                    type: "array",
+                    items: { type: "string" },
+                    minItems: 1
+                }
+            ]
+        },
+        excludedFiles: {
+            oneOf: [
+                { type: "string" },
+                {
+                    type: "array",
+                    items: { type: "string" }
+                }
+            ]
+        }
+    }
+);
+
+const topLevelConfigProperties = Object.assign(
+    {},
+    baseConfigProperties,
+    {
+        extends: { type: ["string", "array"] },
+        root: { type: "boolean" },
+        overrides: {
+            type: "array",
+            items: {
+                type: "object",
+                properties: overrideProperties,
+                required: ["files"],
+                additionalProperties: false
+            }
+        }
+    }
+);
+
+const configSchema = {
+    type: "object",
+    properties: topLevelConfigProperties,
+    additionalProperties: false
+};
+
+module.exports = configSchema;
diff --git a/conf/config-schema.json b/conf/config-schema.json
deleted file mode 100644
index c3676bde9..000000000
--- a/conf/config-schema.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
-    "type": "object",
-    "properties": {
-        "root": { "type": "boolean" },
-        "globals": { "type": ["object"] },
-        "parser": { "type": ["string", "null"] },
-        "env": { "type": "object" },
-        "plugins": { "type": ["array"] },
-        "settings": { "type": "object" },
-        "extends": { "type": ["string", "array"] },
-        "rules": { "type": "object" },
-        "parserOptions": { "type": "object" }
-    },
-    "additionalProperties": false
-}
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..b4e77e184 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:
@@ -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 b8667d33a..0c8bfad22 100644
--- a/docs/developer-guide/working-with-plugins.md
+++ b/docs/developer-guide/working-with-plugins.md
@@ -218,3 +218,32 @@ Add these keywords into your `package.json` file to make it easy for others to f
 ## Further Reading
 
 * [npm Developer Guide](https://docs.npmjs.com/misc/developers)
+
+### 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. 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`.
+
+You can find an ESLint parser project [here](https://github.com/eslint/typescript-eslint-parser).
+
+    {
+
+        "parser": './path/to/awesome-custom-parser.js'
+    }
+
+```javascript
+var espree = require("espree");
+// awesome-custom-parser.js
+exports.parseForESLint = function(code, options) {
+    return {
+        ast: espree.parse(code, options),
+        services: {
+            foo: function() {
+                console.log("foo");
+            }
+        }
+    };
+};
+
+```
+
+
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..4faaad79c 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"]*/
+
+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"]*/
+
+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 0a3583882..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:
@@ -22,6 +24,7 @@ This rule has an object option for exceptions to the `"all"` option:
 * `"returnAssign": false` allows extra parentheses around assignments in `return` statements
 * `"nestedBinaryExpressions": false` allows extra parentheses in nested binary expressions
 * `"ignoreJSX": "none|all|multi-line|single-line"` allows extra parentheses around no/all/multi-line/single-line JSX components. Defaults to `none`.
+* `"enforceForArrowConditionals": false` allows extra parentheses around ternary expressions which are the body of an arrow function
 
 ### all
 
@@ -165,6 +168,17 @@ const Component = (<div />)
 const Component = (<div><p /></div>)
 ```
 
+### enforceForArrowConditionals
+
+Examples of **correct** code for this rule with the `"all"` and `{ "enforceForArrowConditionals": false }` options:
+
+```js
+/* eslint no-extra-parens: ["error", "all", { "enforceForArrowConditionals": false }] */
+
+const b = a => 1 ? 2 : 3;
+const d = c => (1 ? 2 : 3);
+```
+
 ### functions
 
 Examples of **incorrect** code for this rule with the `"functions"` option:
@@ -205,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/no-trailing-spaces.md b/docs/rules/no-trailing-spaces.md
index 46f61fa60..050e93c39 100644
--- a/docs/rules/no-trailing-spaces.md
+++ b/docs/rules/no-trailing-spaces.md
@@ -31,6 +31,8 @@ This rule has an object option:
 
 * `"skipBlankLines": false` (default) disallows trailing whitespace on empty lines
 * `"skipBlankLines": true` allows trailing whitespace on empty lines
+* `"ignoreComments": false` (default) disallows trailing whitespace in comment blocks
+* `"ignoreComments": true` allows trailing whitespace in comment blocks
 
 ### skipBlankLines
 
@@ -43,3 +45,19 @@ var foo = 0;
 var baz = 5;
 //•••••
 ```
+
+### ignoreComments
+
+Examples of **correct** code for this rule with the `{ "ignoreComments": true }` option:
+
+```js
+/*eslint no-trailing-spaces: ["error", { "ignoreComments": true }]*/
+
+//foo•
+//•••••
+/**
+ *•baz
+ *••
+ *•bar
+ */
+```
\ No newline at end of file
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/rules/padded-blocks.md b/docs/rules/padded-blocks.md
index 81d3ec461..1ea3b1199 100644
--- a/docs/rules/padded-blocks.md
+++ b/docs/rules/padded-blocks.md
@@ -24,8 +24,8 @@ This rule has one option, which can be a string option or an object option.
 
 String option:
 
-* `"always"` (default) requires empty lines at the beginning and ending of block statements (except `switch` statements and classes)
-* `"never"` disallows empty lines at the beginning and ending of block statements (except `switch` statements and classes)
+* `"always"` (default) requires empty lines at the beginning and ending of block statements and classes
+* `"never"` disallows empty lines at the beginning and ending of block statements and classes
 
 Object option:
 
diff --git a/docs/user-guide/configuring.md b/docs/user-guide/configuring.md
index 86469a5af..ed1f573fa 100644
--- a/docs/user-guide/configuring.md
+++ b/docs/user-guide/configuring.md
@@ -713,6 +713,61 @@ module.exports = {
 }
 ```
 
+## Configuration Based on Glob Patterns
+
+Sometimes a more fine-controlled configuration is necessary, for example if the configuration for files within the same directory has to be different. Therefore you can provide configurations under the `overrides` key that will only apply to files that match specific glob patterns, using the same format you would pass on the command line (e.g., `app/**/*.test.js`).
+
+### How it works
+
+* Glob pattern overrides can only be configured within config files (`.eslintrc.*` or `package.json`).
+* The patterns are applied against the file path relative to the directory of the config file. For example, if your config file has the path `/Users/john/workspace/any-project/.eslintrc.js` and the file you want to lint has the path `/Users/john/workspace/any-project/lib/util.js`, then the pattern provided in `.eslintrc.js` will be executed against the relative path `lib/util.js`.
+* Glob pattern overrides have higher precedence than the regular configuration in the same config file. Multiple overrides within the same config are applied in order. That is, the last override block in a config file always has the highest precedence.
+* A glob specific configuration works almost the same as any other ESLint config. Override blocks can contain any configuration options that are valid in a regular config, with the exception of `extends`, `overrides`, and `root`.
+* Multiple glob patterns can be provided within a single override block. A file must match at least one of the supplied patterns for the configuration to apply.
+* Override blocks can also specify patterns to exclude from matches. If a file matches any of the excluded patterns, the configuration won't apply.
+
+### Relative glob patterns
+
+```
+project-root
+├── app
+│   ├── lib
+│   │   ├── foo.js
+│   │   ├── fooSpec.js
+│   ├── components
+│   │   ├── bar.js
+│   │   ├── barSpec.js
+│   ├── .eslintrc.json
+├── server
+│   ├── server.js
+│   ├── serverSpec.js
+├── .eslintrc.json
+```
+
+The config in `app/.eslintrc.json` defines the glob pattern `**/*Spec.js`. This pattern is relative to the base directory of `app/.eslintrc.json`. So, this pattern would match `app/lib/fooSpec.js` and `app/components/barSpec.js` but **NOT** `server/serverSpec.js`. If you defined the same pattern in the `.eslintrc.json` file within in the `project-root` folder, it would match all three of the `*Spec` files.
+
+### Example configuration
+
+In your `.eslintrc.json`:
+
+```json
+{
+  "rules": {
+    "quotes": [ 2, "double" ]
+  },
+
+  "overrides": [
+    {
+      "files": [ "bin/*.js", "lib/*.js" ],
+      "excludedFiles": "*.test.js",
+      "rules": {
+        "quotes": [ 2, "single" ]
+      }
+    }
+  ]
+}
+```
+
 ## Comments in Configuration Files
 
 Both the JSON and YAML configuration file formats support comments (`package.json` files should not include them). You can use JavaScript-style comments or YAML-style comments in either type of file and ESLint will safely ignore them. This allows your configuration files to be more human-friendly. For example:
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 ebca4cb81..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 {
@@ -398,15 +399,17 @@ class CLIEngine {
         this.options = options;
         this.linter = new Linter();
 
-        const cacheFile = getCacheFile(this.options.cacheLocation || this.options.cacheFile, this.options.cwd);
-
-        /**
-         * Cache used to avoid operating on files that haven't changed since the
-         * last successful execution (e.g., file passed linting with no errors and
-         * no warnings).
-         * @type {Object}
-         */
-        this._fileCache = fileEntryCache.create(cacheFile);
+        if (options.cache) {
+            const cacheFile = getCacheFile(this.options.cacheLocation || this.options.cacheFile, this.options.cwd);
+
+            /**
+             * Cache used to avoid operating on files that haven't changed since the
+             * last successful execution (e.g., file passed linting with no errors and
+             * no warnings).
+             * @type {Object}
+             */
+            this._fileCache = fileEntryCache.create(cacheFile);
+        }
 
         // load in additional rules
         if (this.options.rulePaths) {
@@ -495,6 +498,11 @@ class CLIEngine {
             fileCache = this._fileCache,
             configHelper = this.config;
         let prevConfig; // the previous configuration used
+        const cacheFile = getCacheFile(this.options.cacheLocation || this.options.cacheFile, this.options.cwd);
+
+        if (!options.cache && fs.existsSync(cacheFile)) {
+            fs.unlinkSync(cacheFile);
+        }
 
         /**
          * Calculates the hash of the config file used to validate a given file
@@ -570,8 +578,6 @@ class CLIEngine {
                     // move to the next file
                     return;
                 }
-            } else {
-                fileCache.destroy();
             }
 
             debug(`Processing ${filename}`);
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.js b/lib/config.js
index 5c04ee4e1..2db87d942 100644
--- a/lib/config.js
+++ b/lib/config.js
@@ -13,6 +13,7 @@ const path = require("path"),
     os = require("os"),
     ConfigOps = require("./config/config-ops"),
     ConfigFile = require("./config/config-file"),
+    ConfigCache = require("./config/config-cache"),
     Plugins = require("./config/plugins"),
     FileFinder = require("./file-finder"),
     isResolvable = require("is-resolvable");
@@ -24,149 +25,22 @@ const debug = require("debug")("eslint:config");
 //------------------------------------------------------------------------------
 
 const PERSONAL_CONFIG_DIR = os.homedir() || null;
+const SUBCONFIG_SEP = ":";
 
 //------------------------------------------------------------------------------
 // Helpers
 //------------------------------------------------------------------------------
 
 /**
- * Check if item is an javascript object
- * @param {*} item object to check for
- * @returns {boolean} True if its an object
- * @private
- */
-function isObject(item) {
-    return typeof item === "object" && !Array.isArray(item) && item !== null;
-}
-
-/**
- * Load and parse a JSON config object from a file.
- * @param {string|Object} configToLoad the path to the JSON config file or the config object itself.
- * @param {Config} configContext config instance object
- * @returns {Object} the parsed config object (empty object if there was a parse error)
- * @private
- */
-function loadConfig(configToLoad, configContext) {
-    let config = {},
-        filePath = "";
-
-    if (configToLoad) {
-
-        if (isObject(configToLoad)) {
-            config = configToLoad;
-
-            if (config.extends) {
-                config = ConfigFile.applyExtends(config, configContext, filePath);
-            }
-        } else {
-            filePath = configToLoad;
-            config = ConfigFile.load(filePath, configContext);
-        }
-
-    }
-    return config;
-}
-
-/**
- * Get personal config object from ~/.eslintrc.
- * @param {Config} configContext Plugin context for the config instance
- * @returns {Object} the personal config object (null if there is no personal config)
- * @private
- */
-function getPersonalConfig(configContext) {
-    let config;
-
-    if (PERSONAL_CONFIG_DIR) {
-
-        const filename = ConfigFile.getFilenameForDirectory(PERSONAL_CONFIG_DIR);
-
-        if (filename) {
-            debug("Using personal config");
-            config = loadConfig(filename, configContext);
-        }
-    }
-
-    return config || null;
-}
-
-/**
- * Determine if rules were explicitly passed in as options.
+ * Determines if any rules were explicitly passed in as options.
  * @param {Object} options The options used to create our configuration.
  * @returns {boolean} True if rules were passed in as options, false otherwise.
+ * @private
  */
 function hasRules(options) {
     return options.rules && Object.keys(options.rules).length > 0;
 }
 
-/**
- * Get a local config object.
- * @param {Config} thisConfig A Config object.
- * @param {string} directory The directory to start looking in for a local config file.
- * @returns {Object} The local config object, or an empty object if there is no local config.
- */
-function getLocalConfig(thisConfig, directory) {
-
-    const projectConfigPath = ConfigFile.getFilenameForDirectory(thisConfig.options.cwd);
-    const localConfigFiles = thisConfig.findLocalConfigFiles(directory);
-    let found,
-        config = {};
-
-    for (const localConfigFile of localConfigFiles) {
-
-        // Don't consider the personal config file in the home directory,
-        // except if the home directory is the same as the current working directory
-        if (path.dirname(localConfigFile) === PERSONAL_CONFIG_DIR && localConfigFile !== projectConfigPath) {
-            continue;
-        }
-
-        debug(`Loading ${localConfigFile}`);
-        const localConfig = loadConfig(localConfigFile, thisConfig);
-
-        // Don't consider a local config file found if the config is null
-        if (!localConfig) {
-            continue;
-        }
-
-        found = true;
-        debug(`Using ${localConfigFile}`);
-        config = ConfigOps.merge(localConfig, config);
-
-        // Check for root flag
-        if (localConfig.root === true) {
-            break;
-        }
-    }
-
-    if (!found && !thisConfig.useSpecificConfig) {
-
-        /*
-         * - Is there a personal config in the user's home directory? If so,
-         *   merge that with the passed-in config.
-         * - Otherwise, if no rules were manually passed in, throw and error.
-         * - Note: This function is not called if useEslintrc is false.
-         */
-        const personalConfig = getPersonalConfig(thisConfig);
-
-        if (personalConfig) {
-            config = ConfigOps.merge(config, personalConfig);
-        } else if (!hasRules(thisConfig.options) && !thisConfig.options.baseConfig) {
-
-            // No config file, no manual configuration, and no rules, so error.
-            const noConfigError = new Error("No ESLint configuration found.");
-
-            noConfigError.messageTemplate = "no-config-found";
-            noConfigError.messageData = {
-                directory,
-                filesExamined: localConfigFiles
-            };
-
-            throw noConfigError;
-        }
-    }
-
-    return config;
-}
-
 //------------------------------------------------------------------------------
 // API
 //------------------------------------------------------------------------------
@@ -177,7 +51,6 @@ function getLocalConfig(thisConfig, directory) {
 class Config {
 
     /**
-     * Config options
      * @param {Object} options Options to be passed in
      * @param {Linter} linterContext Linter instance object
      */
@@ -187,13 +60,22 @@ class Config {
         this.linterContext = linterContext;
         this.plugins = new Plugins(linterContext.environments, linterContext.rules);
 
+        this.options = options;
         this.ignore = options.ignore;
         this.ignorePath = options.ignorePath;
-        this.cache = {};
         this.parser = options.parser;
         this.parserOptions = options.parserOptions || {};
 
-        this.baseConfig = options.baseConfig ? loadConfig(options.baseConfig, this) : { rules: {} };
+        this.configCache = new ConfigCache();
+
+        this.baseConfig = options.baseConfig
+            ? ConfigOps.merge({}, ConfigFile.loadObject(options.baseConfig, this))
+            : { rules: {} };
+        this.baseConfig.filePath = "";
+        this.baseConfig.baseDirectory = this.options.cwd;
+
+        this.configCache.setConfig(this.baseConfig.filePath, this.baseConfig);
+        this.configCache.setMergedVectorConfig(this.baseConfig.filePath, this.baseConfig);
 
         this.useEslintrc = (options.useEslintrc !== false);
 
@@ -209,132 +91,275 @@ class Config {
          * If user declares "foo", convert to "foo:false".
          */
         this.globals = (options.globals || []).reduce((globals, def) => {
-            const parts = def.split(":");
+            const parts = def.split(SUBCONFIG_SEP);
 
             globals[parts[0]] = (parts.length > 1 && parts[1] === "true");
 
             return globals;
         }, {});
 
-        this.options = options;
-        this.loadConfigFile(options.configFile);
+        this.loadSpecificConfig(options.configFile);
+
+        // Empty values in configs don't merge properly
+        const cliConfigOptions = {
+            env: this.env,
+            rules: this.options.rules,
+            globals: this.globals,
+            parserOptions: this.parserOptions,
+            plugins: this.options.plugins
+        };
+
+        this.cliConfig = {};
+        Object.keys(cliConfigOptions).forEach(configKey => {
+            const value = cliConfigOptions[configKey];
+
+            if (value) {
+                this.cliConfig[configKey] = value;
+            }
+        });
     }
 
     /**
-     * Loads the config from the configuration file
-     * @param {string} configFile - patch to the config file
-     * @returns {undefined}
-     */
-    loadConfigFile(configFile) {
-        if (configFile) {
-            debug(`Using command line config ${configFile}`);
-            if (isResolvable(configFile) || isResolvable(`eslint-config-${configFile}`) || configFile.charAt(0) === "@") {
-                this.useSpecificConfig = loadConfig(configFile, this);
-            } else {
-                this.useSpecificConfig = loadConfig(path.resolve(this.options.cwd, configFile), this);
+    * Loads the config options from a config specified on the command line.
+    * @param {string} [config] A shareable named config or path to a config file.
+    * @returns {void}
+    */
+    loadSpecificConfig(config) {
+        if (config) {
+            debug(`Using command line config ${config}`);
+            const isNamedConfig =
+                isResolvable(config) ||
+                isResolvable(`eslint-config-${config}`) ||
+                config.charAt(0) === "@";
+
+            if (!isNamedConfig) {
+                config = path.resolve(this.options.cwd, config);
             }
+
+            this.specificConfig = ConfigFile.load(config, this);
         }
     }
 
     /**
-     * Build a config object merging the base config (conf/eslint-recommended),
-     * the environments config (conf/environments.js) and eventually the user
-     * config.
-     * @param {string} filePath a file in whose directory we start looking for a local config
-     * @returns {Object} config object
+     * Gets the personal config object from user's home directory.
+     * @returns {Object} the personal config object (null if there is no personal config)
+     * @private
      */
-    getConfig(filePath) {
-        const directory = filePath ? path.dirname(filePath) : this.options.cwd;
-        let config,
-            userConfig;
+    getPersonalConfig() {
+        if (typeof this.personalConfig === "undefined") {
+            let config;
 
-        debug(`Constructing config for ${filePath ? filePath : "text"}`);
+            if (PERSONAL_CONFIG_DIR) {
+                const filename = ConfigFile.getFilenameForDirectory(PERSONAL_CONFIG_DIR);
 
-        config = this.cache[directory];
-        if (config) {
-            debug("Using config from cache");
-            return config;
+                if (filename) {
+                    debug("Using personal config");
+                    config = ConfigFile.load(filename, this);
+                }
+            }
+            this.personalConfig = config || null;
         }
 
-        // Step 1: Determine user-specified config from .eslintrc.* and package.json files
+        return this.personalConfig;
+    }
+
+    /**
+     * Builds a hierarchy of config objects, including the base config, all local configs from the directory tree,
+     * and a config file specified on the command line, if applicable.
+     * @param {string} directory a file in whose directory we start looking for a local config
+     * @returns {Object[]} The config objects, in ascending order of precedence
+     * @private
+     */
+    getConfigHierarchy(directory) {
+        debug(`Constructing config file hierarchy for ${directory}`);
+
+        // Step 1: Always include baseConfig
+        let configs = [this.baseConfig];
+
+        // Step 2: Add user-specified config from .eslintrc.* and package.json files
         if (this.useEslintrc) {
             debug("Using .eslintrc and package.json files");
-            userConfig = getLocalConfig(this, directory);
+            configs = configs.concat(this.getLocalConfigHierarchy(directory));
         } else {
             debug("Not using .eslintrc or package.json files");
-            userConfig = {};
         }
 
-        // Step 2: Create a copy of the baseConfig
-        config = ConfigOps.merge({}, this.baseConfig);
+        // Step 3: Merge in command line config file
+        if (this.specificConfig) {
+            debug("Using command line config file");
+            configs.push(this.specificConfig);
+        }
 
-        // Step 3: Merge in the user-specified configuration from .eslintrc and package.json
-        config = ConfigOps.merge(config, userConfig);
+        return configs;
+    }
 
-        // Step 4: Merge in command line config file
-        if (this.useSpecificConfig) {
-            debug("Merging command line config file");
+    /**
+     * Gets a list of config objects extracted from local config files that apply to the current directory, in
+     * descending order, beginning with the config that is highest in the directory tree.
+     * @param {string} directory The directory to start looking in for local config files.
+     * @returns {Object[]} The shallow local config objects, in ascending order of precedence (closest to the current
+     * directory at the end), or an empty array if there are no local configs.
+     * @private
+     */
+    getLocalConfigHierarchy(directory) {
+        const localConfigFiles = this.findLocalConfigFiles(directory),
+            projectConfigPath = ConfigFile.getFilenameForDirectory(this.options.cwd),
+            searched = [],
+            configs = [];
 
-            config = ConfigOps.merge(config, this.useSpecificConfig);
-        }
+        for (const localConfigFile of localConfigFiles) {
+            const localConfigDirectory = path.dirname(localConfigFile);
+            const localConfigHierarchyCache = this.configCache.getHierarchyLocalConfigs(localConfigDirectory);
 
-        // Step 5: Merge in command line environments
-        debug("Merging command line environment settings");
-        config = ConfigOps.merge(config, { env: this.env });
+            if (localConfigHierarchyCache) {
+                const localConfigHierarchy = localConfigHierarchyCache.concat(configs.reverse());
 
-        // Step 6: Merge in command line rules
-        if (this.options.rules) {
-            debug("Merging command line rules");
-            config = ConfigOps.merge(config, { rules: this.options.rules });
-        }
+                this.configCache.setHierarchyLocalConfigs(searched, localConfigHierarchy);
+                return localConfigHierarchy;
+            }
+
+            // Don't consider the personal config file in the home directory,
+            // except if the home directory is the same as the current working directory
+            if (localConfigDirectory === PERSONAL_CONFIG_DIR && localConfigFile !== projectConfigPath) {
+                continue;
+            }
 
-        // Step 7: Merge in command line globals
-        config = ConfigOps.merge(config, { globals: this.globals });
+            debug(`Loading ${localConfigFile}`);
+            const localConfig = ConfigFile.load(localConfigFile, this);
 
-        // Only override parser if it is passed explicitly through the command line or if it's not
-        // defined yet (because the final object will at least have the parser key)
-        if (this.parser || !config.parser) {
-            config = ConfigOps.merge(config, {
-                parser: this.parser
-            });
-        }
+            // Ignore empty config files
+            if (!localConfig) {
+                continue;
+            }
 
-        if (this.parserOptions) {
-            config = ConfigOps.merge(config, {
-                parserOptions: this.parserOptions
-            });
-        }
+            debug(`Using ${localConfigFile}`);
+            configs.push(localConfig);
+            searched.push(localConfigDirectory);
 
-        // Step 8: Merge in command line plugins
-        if (this.options.plugins) {
-            debug("Merging command line plugins");
-            this.plugins.loadAll(this.options.plugins);
-            config = ConfigOps.merge(config, { plugins: this.options.plugins });
+            // Stop traversing if a config is found with the root flag set
+            if (localConfig.root) {
+                break;
+            }
         }
 
-        // Step 9: Apply environments to the config if present
-        if (config.env) {
-            config = ConfigOps.applyEnvironments(config, this.linterContext.environments);
+        if (!configs.length && !this.specificConfig) {
+
+            // Fall back on the personal config from ~/.eslintrc
+            debug("Using personal config file");
+            const personalConfig = this.getPersonalConfig();
+
+            if (personalConfig) {
+                configs.push(personalConfig);
+            } else if (!hasRules(this.options) && !this.options.baseConfig) {
+
+                // No config file, no manual configuration, and no rules, so error.
+                const noConfigError = new Error("No ESLint configuration found.");
+
+                noConfigError.messageTemplate = "no-config-found";
+                noConfigError.messageData = {
+                    directory,
+                    filesExamined: localConfigFiles
+                };
+
+                throw noConfigError;
+            }
         }
 
-        this.cache[directory] = config;
+        // Set the caches for the parent directories
+        this.configCache.setHierarchyLocalConfigs(searched, configs.reverse());
 
-        return config;
+        return configs;
     }
 
     /**
-     * Find local config files from directory and parent directories.
+     * Gets the vector of applicable configs and subconfigs from the hierarchy for a given file. A vector is an array of
+     * entries, each of which in an object specifying a config file path and an array of override indices corresponding
+     * to entries in the config file's overrides section whose glob patterns match the specified file path; e.g., the
+     * vector entry { configFile: '/home/john/app/.eslintrc', matchingOverrides: [0, 2] } would indicate that the main
+     * project .eslintrc file and its first and third override blocks apply to the current file.
+     * @param {string} filePath The file path for which to build the hierarchy and config vector.
+     * @returns {Array<Object>} config vector applicable to the specified path
+     * @private
+     */
+    getConfigVector(filePath) {
+        const directory = filePath ? path.dirname(filePath) : this.options.cwd;
+
+        return this.getConfigHierarchy(directory).map(config => {
+            const vectorEntry = {
+                filePath: config.filePath,
+                matchingOverrides: []
+            };
+
+            if (config.overrides) {
+                const relativePath = path.relative(config.baseDirectory, filePath || directory);
+
+                config.overrides.forEach((override, i) => {
+                    if (ConfigOps.pathMatchesGlobs(relativePath, override.files, override.excludedFiles)) {
+                        vectorEntry.matchingOverrides.push(i);
+                    }
+                });
+            }
+
+            return vectorEntry;
+        });
+    }
+
+    /**
+     * Finds local config files from the specified directory and its parent directories.
      * @param {string} directory The directory to start searching from.
      * @returns {GeneratorFunction} The paths of local config files found.
      */
     findLocalConfigFiles(directory) {
-
         if (!this.localConfigFinder) {
             this.localConfigFinder = new FileFinder(ConfigFile.CONFIG_FILES, this.options.cwd);
         }
 
         return this.localConfigFinder.findAllInDirectoryAndParents(directory);
     }
+
+    /**
+     * Builds the authoritative config object for the specified file path by merging the hierarchy of config objects
+     * that apply to the current file, including the base config (conf/eslint-recommended), the user's personal config
+     * from their homedir, all local configs from the directory tree, any specific config file passed on the command
+     * line, any configuration overrides set directly on the command line, and finally the environment configs
+     * (conf/environments).
+     * @param {string} filePath a file in whose directory we start looking for a local config
+     * @returns {Object} config object
+     */
+    getConfig(filePath) {
+        const vector = this.getConfigVector(filePath);
+        let config = this.configCache.getMergedConfig(vector);
+
+        if (config) {
+            debug("Using config from cache");
+            return config;
+        }
+
+        // Step 1: Merge in the filesystem configurations (base, local, and personal)
+        config = ConfigOps.getConfigFromVector(vector, this.configCache);
+
+        // Step 2: Merge in command line configurations
+        config = ConfigOps.merge(config, this.cliConfig);
+
+        if (this.cliConfig.plugins) {
+            this.plugins.loadAll(this.cliConfig.plugins);
+        }
+
+        // Step 3: Override parser only if it is passed explicitly through the command line
+        // or if it's not defined yet (because the final object will at least have the parser key)
+        if (this.parser || !config.parser) {
+            config = ConfigOps.merge(config, { parser: this.parser });
+        }
+
+        // Step 4: Apply environments to the config if present
+        if (config.env) {
+            config = ConfigOps.applyEnvironments(config, this.linterContext.environments);
+        }
+
+        this.configCache.setMergedConfig(vector, config);
+
+        return config;
+    }
 }
 
 module.exports = Config;
diff --git a/lib/config/config-cache.js b/lib/config/config-cache.js
new file mode 100644
index 000000000..07436a87c
--- /dev/null
+++ b/lib/config/config-cache.js
@@ -0,0 +1,130 @@
+/**
+ * @fileoverview Responsible for caching config files
+ * @author Sylvan Mably
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Get a string hash for a config vector
+ * @param {Array<Object>} vector config vector to hash
+ * @returns {string} hash of the vector values
+ * @private
+ */
+function hash(vector) {
+    return JSON.stringify(vector);
+}
+
+//------------------------------------------------------------------------------
+// API
+//------------------------------------------------------------------------------
+
+/**
+ * Configuration caching class
+ */
+module.exports = class ConfigCache {
+
+    constructor() {
+        this.configFullNameCache = new Map();
+        this.localHierarchyCache = new Map();
+        this.mergedVectorCache = new Map();
+        this.mergedCache = new Map();
+    }
+
+    /**
+     * Gets a config object from the cache for the specified config file path.
+     * @param {string} configFullName the name of the configuration as used in the eslint config(e.g. 'plugin:node/recommended'),
+     * or the absolute path to a config file. This should uniquely identify a config.
+     * @returns {Object|null} config object, if found in the cache, otherwise null
+     * @private
+     */
+    getConfig(configFullName) {
+        return this.configFullNameCache.get(configFullName);
+    }
+
+    /**
+     * Sets a config object in the cache for the specified config file path.
+     * @param {string} configFullName the name of the configuration as used in the eslint config(e.g. 'plugin:node/recommended'),
+     * or the absolute path to a config file. This should uniquely identify a config.
+     * @param {Object} config the config object to add to the cache
+     * @returns {void}
+     * @private
+     */
+    setConfig(configFullName, config) {
+        this.configFullNameCache.set(configFullName, config);
+    }
+
+    /**
+     * Gets a list of hierarchy-local config objects that apply to the specified directory.
+     * @param {string} directory the path to the directory
+     * @returns {Object[]|null} a list of config objects, if found in the cache, otherwise null
+     * @private
+     */
+    getHierarchyLocalConfigs(directory) {
+        return this.localHierarchyCache.get(directory);
+    }
+
+    /**
+     * For each of the supplied parent directories, sets the list of config objects for that directory to the
+     * appropriate subset of the supplied parent config objects.
+     * @param {string[]} parentDirectories a list of parent directories to add to the config cache
+     * @param {Object[]} parentConfigs a list of config objects that apply to the lowest directory in parentDirectories
+     * @returns {void}
+     * @private
+     */
+    setHierarchyLocalConfigs(parentDirectories, parentConfigs) {
+        parentDirectories.forEach((localConfigDirectory, i) => {
+            const directoryParentConfigs = parentConfigs.slice(0, parentConfigs.length - i);
+
+            this.localHierarchyCache.set(localConfigDirectory, directoryParentConfigs);
+        });
+    }
+
+    /**
+     * Gets a merged config object corresponding to the supplied vector.
+     * @param {Array<Object>} vector the vector to find a merged config for
+     * @returns {Object|null} a merged config object, if found in the cache, otherwise null
+     * @private
+     */
+    getMergedVectorConfig(vector) {
+        return this.mergedVectorCache.get(hash(vector));
+    }
+
+    /**
+     * Sets a merged config object in the cache for the supplied vector.
+     * @param {Array<Object>} vector the vector to save a merged config for
+     * @param {Object} config the merged config object to add to the cache
+     * @returns {void}
+     * @private
+     */
+    setMergedVectorConfig(vector, config) {
+        this.mergedVectorCache.set(hash(vector), config);
+    }
+
+    /**
+     * Gets a merged config object corresponding to the supplied vector, including configuration options from outside
+     * the vector.
+     * @param {Array<Object>} vector the vector to find a merged config for
+     * @returns {Object|null} a merged config object, if found in the cache, otherwise null
+     * @private
+     */
+    getMergedConfig(vector) {
+        return this.mergedCache.get(hash(vector));
+    }
+
+    /**
+     * Sets a merged config object in the cache for the supplied vector, including configuration options from outside
+     * the vector.
+     * @param {Array<Object>} vector the vector to save a merged config for
+     * @param {Object} config the merged config object to add to the cache
+     * @returns {void}
+     * @private
+     */
+    setMergedConfig(vector, config) {
+        this.mergedCache.set(hash(vector), config);
+    }
+};
diff --git a/lib/config/config-file.js b/lib/config/config-file.js
index c9fcc9dff..3c790cf3b 100644
--- a/lib/config/config-file.js
+++ b/lib/config/config-file.js
@@ -418,7 +418,7 @@ function applyExtends(config, configContext, filePath, relativeTo) {
                 );
             }
             debug(`Loading ${parentPath}`);
-            return ConfigOps.merge(load(parentPath, configContext, false, relativeTo), previousValue);
+            return ConfigOps.merge(load(parentPath, configContext, relativeTo), previousValue);
         } catch (e) {
 
             /*
@@ -488,12 +488,15 @@ function normalizePackageName(name, prefix) {
  * @returns {Object} An object containing 3 properties:
  * - 'filePath' (required) the resolved path that can be used directly to load the configuration.
  * - 'configName' the name of the configuration inside the plugin.
- * - 'configFullName' the name of the configuration as used in the eslint config (e.g. 'plugin:node/recommended').
+ * - 'configFullName' (required) the name of the configuration as used in the eslint config(e.g. 'plugin:node/recommended'),
+ *     or the absolute path to a config file. This should uniquely identify a config.
  * @private
  */
 function resolve(filePath, relativeTo) {
     if (isFilePath(filePath)) {
-        return { filePath: path.resolve(relativeTo || "", filePath) };
+        const fullPath = path.resolve(relativeTo || "", filePath);
+
+        return { filePath: fullPath, configFullName: fullPath };
     }
     let normalizedPackageName;
 
@@ -510,23 +513,19 @@ function resolve(filePath, relativeTo) {
     normalizedPackageName = normalizePackageName(filePath, "eslint-config");
     debug(`Attempting to resolve ${normalizedPackageName}`);
     filePath = resolver.resolve(normalizedPackageName, getLookupPath(relativeTo));
-    return { filePath };
+    return { filePath, configFullName: filePath };
 
 
 }
 
 /**
  * Loads a configuration file from the given file path.
- * @param {string} filePath The filename or package name to load the configuration
- *      information from.
+ * @param {Object} resolvedPath The value from calling resolve() on a filename or package name.
  * @param {Config} configContext Plugins context
- * @param {boolean} [applyEnvironments=false] Set to true to merge in environment settings.
- * @param {string} [relativeTo] The path to resolve relative to.
  * @returns {Object} The configuration information.
  */
-function load(filePath, configContext, applyEnvironments, relativeTo) {
-    const resolvedPath = resolve(filePath, relativeTo),
-        dirname = path.dirname(resolvedPath.filePath),
+function loadFromDisk(resolvedPath, configContext) {
+    const dirname = path.dirname(resolvedPath.filePath),
         lookupPath = getLookupPath(dirname);
     let config = loadConfigFile(resolvedPath);
 
@@ -547,27 +546,61 @@ function load(filePath, configContext, applyEnvironments, relativeTo) {
         }
 
         // validate the configuration before continuing
-        validator.validate(config, filePath, configContext.linterContext.rules, configContext.linterContext.environments);
+        validator.validate(config, resolvedPath, configContext.linterContext.rules, configContext.linterContext.environments);
 
         /*
          * If an `extends` property is defined, it represents a configuration file to use as
          * a "parent". Load the referenced file and merge the configuration recursively.
          */
         if (config.extends) {
-            config = applyExtends(config, configContext, filePath, dirname);
+            config = applyExtends(config, configContext, resolvedPath.filePath, dirname);
         }
+    }
 
-        if (config.env && applyEnvironments) {
+    return config;
+}
 
-            // Merge in environment-specific globals and parserOptions.
-            config = ConfigOps.applyEnvironments(config, configContext.linterContext.environments);
-        }
+/**
+ * Loads a config object, applying extends if present.
+ * @param {Object} configObject a config object to load
+ * @param {Config} configContext Context for the config instance
+ * @returns {Object} the config object with extends applied if present, or the passed config if not
+ * @private
+ */
+function loadObject(configObject, configContext) {
+    return configObject.extends ? applyExtends(configObject, configContext, "") : configObject;
+}
 
+/**
+ * Loads a config object from the config cache based on its filename, falling back to the disk if the file is not yet
+ * cached.
+ * @param {string} filePath the path to the config file
+ * @param {Config} configContext Context for the config instance
+ * @param {string} [relativeTo] The path to resolve relative to.
+ * @returns {Object} the parsed config object (empty object if there was a parse error)
+ * @private
+ */
+function load(filePath, configContext, relativeTo) {
+    const resolvedPath = resolve(filePath, relativeTo);
+
+    const cachedConfig = configContext.configCache.getConfig(resolvedPath.configFullName);
+
+    if (cachedConfig) {
+        return cachedConfig;
+    }
+
+    const config = loadFromDisk(resolvedPath, configContext);
+
+    if (config) {
+        config.filePath = resolvedPath.filePath;
+        config.baseDirectory = path.dirname(resolvedPath.filePath);
+        configContext.configCache.setConfig(resolvedPath.configFullName, config);
     }
 
     return config;
 }
 
+
 //------------------------------------------------------------------------------
 // Public Interface
 //------------------------------------------------------------------------------
@@ -577,6 +610,7 @@ module.exports = {
     getBaseDir,
     getLookupPath,
     load,
+    loadObject,
     resolve,
     write,
     applyExtends,
diff --git a/lib/config/config-ops.js b/lib/config/config-ops.js
index e1d9a9013..d169e60dc 100644
--- a/lib/config/config-ops.js
+++ b/lib/config/config-ops.js
@@ -9,6 +9,9 @@
 // Requirements
 //------------------------------------------------------------------------------
 
+const minimatch = require("minimatch"),
+    path = require("path");
+
 const debug = require("debug")("eslint:config-ops");
 
 //------------------------------------------------------------------------------
@@ -174,7 +177,9 @@ module.exports = {
                 });
             }
             Object.keys(src).forEach(key => {
-                if (Array.isArray(src[key]) || Array.isArray(target[key])) {
+                if (key === "overrides") {
+                    dst[key] = (target[key] || []).concat(src[key] || []);
+                } else if (Array.isArray(src[key]) || Array.isArray(target[key])) {
                     dst[key] = deepmerge(target[key], src[key], key === "plugins" || key === "extends", isRule);
                 } else if (typeof src[key] !== "object" || !src[key] || key === "exported" || key === "astGlobals") {
                     dst[key] = src[key];
@@ -268,5 +273,111 @@ module.exports = {
      */
     isEverySeverityValid(config) {
         return Object.keys(config).every(ruleId => this.isValidSeverity(config[ruleId]));
+    },
+
+    /**
+     * Merges all configurations in a given config vector. A vector is an array of objects, each containing a config
+     * file path and a list of subconfig indices that match the current file path. All config data is assumed to be
+     * cached.
+     * @param {Array<Object>} vector list of config files and their subconfig indices that match the current file path
+     * @param {Object} configCache the config cache
+     * @returns {Object} config object
+     */
+    getConfigFromVector(vector, configCache) {
+
+        const cachedConfig = configCache.getMergedVectorConfig(vector);
+
+        if (cachedConfig) {
+            return cachedConfig;
+        }
+
+        debug("Using config from partial cache");
+
+        const subvector = Array.from(vector);
+        let nearestCacheIndex = subvector.length - 1,
+            partialCachedConfig;
+
+        while (nearestCacheIndex >= 0) {
+            partialCachedConfig = configCache.getMergedVectorConfig(subvector);
+            if (partialCachedConfig) {
+                break;
+            }
+            subvector.pop();
+            nearestCacheIndex--;
+        }
+
+        if (!partialCachedConfig) {
+            partialCachedConfig = {};
+        }
+
+        let finalConfig = partialCachedConfig;
+
+        // Start from entry immediately following nearest cached config (first uncached entry)
+        for (let i = nearestCacheIndex + 1; i < vector.length; i++) {
+            finalConfig = this.mergeVectorEntry(finalConfig, vector[i], configCache);
+            configCache.setMergedVectorConfig(vector.slice(0, i + 1), finalConfig);
+        }
+
+        return finalConfig;
+    },
+
+    /**
+     * Merges the config options from a single vector entry into the supplied config.
+     * @param {Object} config the base config to merge the vector entry's options into
+     * @param {Object} vectorEntry a single entry from a vector, consisting of a config file path and an array of
+     * matching override indices
+     * @param {Object} configCache the config cache
+     * @returns {Object} merged config object
+     */
+    mergeVectorEntry(config, vectorEntry, configCache) {
+        const vectorEntryConfig = Object.assign({}, configCache.getConfig(vectorEntry.filePath));
+        let mergedConfig = Object.assign({}, config),
+            overrides;
+
+        if (vectorEntryConfig.overrides) {
+            overrides = vectorEntryConfig.overrides.filter(
+                (override, overrideIndex) => vectorEntry.matchingOverrides.indexOf(overrideIndex) !== -1
+            );
+        } else {
+            overrides = [];
+        }
+
+        mergedConfig = this.merge(mergedConfig, vectorEntryConfig);
+
+        delete mergedConfig.overrides;
+
+        mergedConfig = overrides.reduce((lastConfig, override) => this.merge(lastConfig, override), mergedConfig);
+
+        if (mergedConfig.filePath) {
+            delete mergedConfig.filePath;
+            delete mergedConfig.baseDirectory;
+        } else if (mergedConfig.files) {
+            delete mergedConfig.files;
+        }
+
+        return mergedConfig;
+    },
+
+    /**
+     * Checks that the specified file path matches all of the supplied glob patterns.
+     * @param {string} filePath The file path to test patterns against
+     * @param {string|string[]} patterns One or more glob patterns, of which at least one should match the file path
+     * @param {string|string[]} [excludedPatterns] One or more glob patterns, of which none should match the file path
+     * @returns {boolean} True if all the supplied patterns match the file path, false otherwise
+     */
+    pathMatchesGlobs(filePath, patterns, excludedPatterns) {
+        const patternList = [].concat(patterns);
+        const excludedPatternList = [].concat(excludedPatterns || []);
+
+        patternList.concat(excludedPatternList).forEach(pattern => {
+            if (path.isAbsolute(pattern) || pattern.includes("..")) {
+                throw new Error(`Invalid override pattern (expected relative path not containing '..'): ${pattern}`);
+            }
+        });
+
+        const opts = { matchBase: true };
+
+        return patternList.some(pattern => minimatch(filePath, pattern, opts)) &&
+            !excludedPatternList.some(excludedPattern => minimatch(filePath, excludedPattern, opts));
     }
 };
diff --git a/lib/config/config-validator.js b/lib/config/config-validator.js
index 329a5087d..fb5a96564 100644
--- a/lib/config/config-validator.js
+++ b/lib/config/config-validator.js
@@ -9,8 +9,8 @@
 // Requirements
 //------------------------------------------------------------------------------
 
-const schemaValidator = require("is-my-json-valid"),
-    configSchema = require("../../conf/config-schema.json"),
+const ajv = require("../util/ajv"),
+    configSchema = require("../../conf/config-schema.js"),
     util = require("util");
 
 const validators = {
@@ -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: ${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 52d08ff3d..8739c58d0 100644
--- a/lib/rules/indent.js
+++ b/lib/rules/indent.js
@@ -220,8 +220,8 @@ class OffsetStorage {
 
     /**
      * Sets the offset column of token B to match the offset column of token A.
-     * This is different from matchIndentOf because it matches a *column*, even if baseToken is not
-     * the first token on its line.
+     * **WARNING**: This is different from matchIndentOf because it matches a *column*, even if baseToken is not
+     * the first token on its line. In most cases, `matchIndentOf` should be used instead.
      * @param {Token} baseToken The first token
      * @param {Token} offsetToken The second token, whose offset should be matched to the first token
      * @returns {void}
@@ -239,12 +239,62 @@ class OffsetStorage {
     }
 
     /**
-    * Sets the desired offset of a token
-    * @param {Token} token The token
-    * @param {Token} offsetFrom The token that this is offset from
-    * @param {number} offset The desired indent level
-    * @returns {void}
-    */
+     * Sets the desired offset of a token.
+     *
+     * This uses a line-based offset collapsing behavior to handle tokens on the same line.
+     * For example, consider the following two cases:
+     *
+     * (
+     *     [
+     *         bar
+     *     ]
+     * )
+     *
+     * ([
+     *     bar
+     * ])
+     *
+     * Based on the first case, it's clear that the `bar` token needs to have an offset of 1 indent level (4 spaces) from
+     * the `[` token, and the `[` token has to have an offset of 1 indent level from the `(` token. Since the `(` token is
+     * the first on its line (with an indent of 0 spaces), the `bar` token needs to be offset by 2 indent levels (8 spaces)
+     * from the start of its line.
+     *
+     * However, in the second case `bar` should only be indented by 4 spaces. This is because the offset of 1 indent level
+     * between the `(` and the `[` tokens gets "collapsed" because the two tokens are on the same line. As a result, the
+     * `(` token is mapped to the `[` token with an offset of 0, and the rule correctly decides that `bar` should be indented
+     * by 1 indent level from the start of the line.
+     *
+     * This is useful because rule listeners can usually just call `setDesiredOffset` for all the tokens in the node,
+     * without needing to check which lines those tokens are on.
+     *
+     * Note that since collapsing only occurs when two tokens are on the same line, there are a few cases where non-intuitive
+     * behavior can occur. For example, consider the following cases:
+     *
+     * foo(
+     * ).
+     *     bar(
+     *         baz
+     *     )
+     *
+     * foo(
+     * ).bar(
+     *     baz
+     * )
+     *
+     * Based on the first example, it would seem that `bar` should be offset by 1 indent level from `foo`, and `baz`
+     * should be offset by 1 indent level from `bar`. However, this is not correct, because it would result in `baz`
+     * being indented by 2 indent levels in the second case (since `foo`, `bar`, and `baz` are all on separate lines, no
+     * collapsing would occur).
+     *
+     * Instead, the correct way would be to offset `baz` by 1 level from `bar`, offset `bar` by 1 level from the `)`, and
+     * offset the `)` by 0 levels from `foo`. This ensures that the offset between `bar` and the `)` are correctly collapsed
+     * in the second case.
+     *
+     * @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}
+     */
     setDesiredOffset(token, offsetFrom, offset) {
         if (offsetFrom && token.loc.start.line === offsetFrom.loc.start.line) {
             this.matchIndentOf(offsetFrom, token);
@@ -253,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.
@@ -763,7 +825,7 @@ module.exports = {
                  */
                 const lastToken = sourceCode.getLastToken(node);
 
-                if (astUtils.isSemicolonToken(lastToken)) {
+                if (node.type !== "EmptyStatement" && astUtils.isSemicolonToken(lastToken)) {
                     offsets.matchIndentOf(lastParentToken, lastToken);
                 }
             }
@@ -834,7 +896,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);
         }
@@ -968,7 +1030,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]);
             },
@@ -1224,7 +1286,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)) {
@@ -1235,43 +1325,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);
                 }
             },
 
@@ -1316,13 +1375,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 d0d79c6a3..fd6fd0b78 100644
--- a/lib/rules/no-extra-parens.js
+++ b/lib/rules/no-extra-parens.js
@@ -44,7 +44,8 @@ module.exports = {
                                 conditionalAssign: { type: "boolean" },
                                 nestedBinaryExpressions: { type: "boolean" },
                                 returnAssign: { type: "boolean" },
-                                ignoreJSX: { enum: ["none", "all", "single-line", "multi-line"] }
+                                ignoreJSX: { enum: ["none", "all", "single-line", "multi-line"] },
+                                enforceForArrowConditionals: { type: "boolean" }
                             },
                             additionalProperties: false
                         }
@@ -67,6 +68,9 @@ module.exports = {
         const NESTED_BINARY = ALL_NODES && context.options[1] && context.options[1].nestedBinaryExpressions === false;
         const EXCEPT_RETURN_ASSIGN = ALL_NODES && context.options[1] && context.options[1].returnAssign === false;
         const IGNORE_JSX = ALL_NODES && context.options[1] && context.options[1].ignoreJSX;
+        const IGNORE_ARROW_CONDITIONALS = ALL_NODES && context.options[1] &&
+            context.options[1].enforceForArrowConditionals === false;
+
         const PRECEDENCE_OF_ASSIGNMENT_EXPR = precedence({ type: "AssignmentExpression" });
         const PRECEDENCE_OF_UPDATE_EXPR = precedence({ type: "UpdateExpression" });
 
@@ -422,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))
                     )
                 )
             ) {
@@ -448,6 +452,13 @@ module.exports = {
                     return;
                 }
 
+                if (node.body.type === "ConditionalExpression" &&
+                    IGNORE_ARROW_CONDITIONALS &&
+                    !isParenthesisedTwice(node.body)
+                ) {
+                    return;
+                }
+
                 if (node.body.type !== "BlockStatement") {
                     const firstBodyToken = sourceCode.getFirstToken(node.body, astUtils.isNotOpeningParenToken);
                     const tokenBeforeFirst = sourceCode.getTokenBefore(firstBodyToken);
@@ -501,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" && (
 
-            ForOfStatement(node) {
+                            // 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] ||
+
+                            // 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/rules/no-trailing-spaces.js b/lib/rules/no-trailing-spaces.js
index 471381f24..b5d2f8d1b 100644
--- a/lib/rules/no-trailing-spaces.js
+++ b/lib/rules/no-trailing-spaces.js
@@ -30,6 +30,9 @@ module.exports = {
                 properties: {
                     skipBlankLines: {
                         type: "boolean"
+                    },
+                    ignoreComments: {
+                        type: "boolean"
                     }
                 },
                 additionalProperties: false
@@ -45,7 +48,8 @@ module.exports = {
             NONBLANK = `${BLANK_CLASS}+$`;
 
         const options = context.options[0] || {},
-            skipBlankLines = options.skipBlankLines || false;
+            skipBlankLines = options.skipBlankLines || false,
+            ignoreComments = typeof options.ignoreComments === "undefined" || options.ignoreComments;
 
         /**
          * Report the error message
@@ -72,6 +76,22 @@ module.exports = {
             });
         }
 
+        /**
+         * Given a list of comment nodes, return the line numbers for those comments.
+         * @param {Array} comments An array of comment nodes.
+         * @returns {number[]} An array of line numbers containing comments.
+         */
+        function getCommentLineNumbers(comments) {
+            const lines = new Set();
+
+            comments.forEach(comment => {
+                for (let i = comment.loc.start.line; i <= comment.loc.end.line; i++) {
+                    lines.add(i);
+                }
+            });
+
+            return lines;
+        }
 
         //--------------------------------------------------------------------------
         // Public
@@ -87,7 +107,10 @@ module.exports = {
                 const re = new RegExp(NONBLANK),
                     skipMatch = new RegExp(SKIP_BLANK),
                     lines = sourceCode.lines,
-                    linebreaks = sourceCode.getText().match(astUtils.createGlobalLinebreakMatcher());
+                    linebreaks = sourceCode.getText().match(astUtils.createGlobalLinebreakMatcher()),
+                    comments = sourceCode.getAllComments(),
+                    commentLineNumbers = getCommentLineNumbers(comments);
+
                 let totalLength = 0,
                     fixRange = [];
 
@@ -125,7 +148,10 @@ module.exports = {
                         }
 
                         fixRange = [rangeStart, rangeEnd];
-                        report(node, location, fixRange);
+
+                        if (!ignoreComments || !commentLineNumbers.has(location.line)) {
+                            report(node, location, fixRange);
+                        }
                     }
 
                     totalLength += lineLength;
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 26dbdd8c8..9793252de 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "eslint",
-  "version": "4.0.0",
+  "version": "4.1.1",
   "author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
   "description": "An AST-based pattern checker for JavaScript.",
   "bin": {
@@ -34,6 +34,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,12 +51,12 @@
     "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",
     "levn": "^0.3.0",
     "lodash": "^4.17.4",
+    "minimatch": "^3.0.2",
     "mkdirp": "^0.5.1",
     "natural-compare": "^1.4.0",
     "optionator": "^0.8.2",
@@ -79,8 +80,8 @@
     "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",
     "esprima": "^3.1.3",
     "esprima-fb": "^15001.1001.0-dev-harmony-fb",
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"


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/fixtures/config-file/plugins/.eslintrc2.yml b/tests/fixtures/config-file/plugins/.eslintrc2.yml
new file mode 100644
index 000000000..973d77096
--- /dev/null
+++ b/tests/fixtures/config-file/plugins/.eslintrc2.yml
@@ -0,0 +1,3 @@
+extends:
+    - plugin:test/foo
+    - plugin:test/bar
diff --git a/tests/fixtures/config-hierarchy/file-structure.json b/tests/fixtures/config-hierarchy/file-structure.json
index 9ebe7de30..f840fead5 100644
--- a/tests/fixtures/config-hierarchy/file-structure.json
+++ b/tests/fixtures/config-hierarchy/file-structure.json
@@ -48,6 +48,12 @@
             ".eslintrc": "{\n    \"env\": {\n        \"commonjs\": true\n    }\n}\n"
         }
     },
+    "overrides": {
+        ".eslintrc": "{\n    \"rules\": {\n        \"quotes\": [2, \"double\"],\n        \"no-else-return\": 0,\n        \"no-unused-vars\": 1,\n        \"semi\": [1, \"never\"]\n    },\n    \"overrides\": [\n        {\n            \"files\": \"foo.js\",\n            \"rules\": {\n                \"quotes\": [2, \"single\"]\n            }\n        },\n        {\n            \"files\": \"bar.js\",\n            \"rules\": {\n                \"no-else-return\": 1\n            }\n        },\n        {\n            \"files\": \"**/*one.js\",\n            \"rules\": {\n                \"curly\": [\"error\", \"multi\", \"consistent\"]\n            }\n        },\n        {\n            \"files\": \"two/child-two.js\",\n            \"rules\": {\n                \"no-unused-vars\": 2,\n                \"no-console\": 1\n            }\n        }\n    ]\n}\n",
+        "two": {
+            ".eslintrc": "{\n    \"rules\": {\n        \"semi\": [2, \"never\"]\n    },\n    \"overrides\": [\n        {\n            \"files\": \"child-two.js\",\n            \"rules\": {\n                \"no-console\": 0\n            }\n        }\n    ]\n}\n"
+        }
+    },
     "packagejson": {
         ".eslintrc": "rules:\r\n    quotes: [2, \"double\"]\r\n",
         "package.json": "{\r\n  \"name\": \"\",\r\n  \"version\": \"\",\r\n  \"eslintConfig\": {\r\n    \"rules\": {\r\n      \"quotes\": [1, \"single\"]\r\n    }\r\n  }\r\n}\r\n",
diff --git a/tests/lib/cli-engine.js b/tests/lib/cli-engine.js
index f0f43e6e1..7529d8edf 100644
--- a/tests/lib/cli-engine.js
+++ b/tests/lib/cli-engine.js
@@ -77,7 +77,7 @@ describe("CLIEngine", () => {
         engine.config.plugins.define(examplePreprocessorName, require("../fixtures/processors/custom-processor"));
 
         // load the real file now so that it can consume the loaded plugins
-        engine.config.loadConfigFile(options.configFile);
+        engine.config.loadSpecificConfig(options.configFile);
 
         return engine;
     }
@@ -1324,6 +1324,8 @@ describe("CLIEngine", () => {
             fakeFS.realpathSync = function() {
                 throw new Error("this error should not happen");
             };
+            fakeFS.existsSync = fs.existsSync;
+            fakeFS.unlinkSync = fs.unlinkSync;
 
             engine = new LocalCLIEngine({
                 ignorePattern: "tests"
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.js b/tests/lib/config.js
index 1942a1c08..59e1a47d0 100644
--- a/tests/lib/config.js
+++ b/tests/lib/config.js
@@ -158,6 +158,24 @@ describe("Config", () => {
             assert.deepEqual(customBaseConfig, { foo: "bar" });
             assert.equal(configHelper.options.format, "foo");
         });
+
+        it("should create config object when using baseConfig with extends", () => {
+            const customBaseConfig = {
+                extends: path.resolve(__dirname, "..", "fixtures", "config-extends", "array", ".eslintrc")
+            };
+            const configHelper = new Config({ baseConfig: customBaseConfig }, linter);
+
+            assert.deepEqual(configHelper.baseConfig.env, {
+                browser: false,
+                es6: true,
+                node: true
+            });
+            assert.deepEqual(configHelper.baseConfig.rules, {
+                "no-empty": 1,
+                "comma-dangle": 2,
+                "no-console": 2
+            });
+        });
     });
 
     describe("findLocalConfigFiles()", () => {
@@ -338,8 +356,8 @@ describe("Config", () => {
         it("should load the config file when there are JS-style comments in the text", () => {
             const configPath = path.resolve(__dirname, "..", "fixtures", "configurations", "comments.json"),
                 configHelper = new Config({ configFile: configPath }, linter),
-                semi = configHelper.useSpecificConfig.rules.semi,
-                strict = configHelper.useSpecificConfig.rules.strict;
+                semi = configHelper.specificConfig.rules.semi,
+                strict = configHelper.specificConfig.rules.strict;
 
             assert.equal(semi, 1);
             assert.equal(strict, 0);
@@ -349,8 +367,8 @@ describe("Config", () => {
         it("should load the config file when a YAML file is used", () => {
             const configPath = path.resolve(__dirname, "..", "fixtures", "configurations", "env-browser.yaml"),
                 configHelper = new Config({ configFile: configPath }, linter),
-                noAlert = configHelper.useSpecificConfig.rules["no-alert"],
-                noUndef = configHelper.useSpecificConfig.rules["no-undef"];
+                noAlert = configHelper.specificConfig.rules["no-alert"],
+                noUndef = configHelper.specificConfig.rules["no-undef"];
 
             assert.equal(noAlert, 0);
             assert.equal(noUndef, 2);
@@ -1081,6 +1099,287 @@ describe("Config", () => {
                 }, "No ESLint configuration found");
             });
         });
+
+
+        describe("with overrides", () => {
+
+            /**
+             * Returns the path inside of the fixture directory.
+             * @param {...string} pathSegments One or more path segments, in order of depth, shallowest first
+             * @returns {string} The path inside the fixture directory.
+             * @private
+             */
+            function getFakeFixturePath() {
+                const pathSegments = Array.from(arguments);
+
+                pathSegments.unshift("config-hierarchy");
+                pathSegments.unshift("fixtures");
+                pathSegments.unshift("eslint");
+                pathSegments.unshift(process.cwd());
+
+                return path.join.apply(path, pathSegments);
+            }
+
+            before(() => {
+                mockFs({
+                    eslint: {
+                        fixtures: {
+                            "config-hierarchy": DIRECTORY_CONFIG_HIERARCHY
+                        }
+                    }
+                });
+            });
+
+            after(() => {
+                mockFs.restore();
+            });
+
+            it("should merge override config when the pattern matches the file name", () => {
+                const config = new Config({ cwd: process.cwd() }, linter);
+                const targetPath = getFakeFixturePath("overrides", "foo.js");
+                const expected = {
+                    rules: {
+                        quotes: [2, "single"],
+                        "no-else-return": 0,
+                        "no-unused-vars": 1,
+                        semi: [1, "never"]
+                    }
+                };
+                const actual = config.getConfig(targetPath);
+
+                assertConfigsEqual(actual, expected);
+            });
+
+            it("should merge override config when the pattern matches the file path relative to the config file", () => {
+                const config = new Config({ cwd: process.cwd() }, linter);
+                const targetPath = getFakeFixturePath("overrides", "child", "child-one.js");
+                const expected = {
+                    rules: {
+                        curly: ["error", "multi", "consistent"],
+                        "no-else-return": 0,
+                        "no-unused-vars": 1,
+                        quotes: [2, "double"],
+                        semi: [1, "never"]
+                    }
+                };
+                const actual = config.getConfig(targetPath);
+
+                assertConfigsEqual(actual, expected);
+            });
+
+            it("should not merge override config when the pattern matches the absolute file path", () => {
+                const targetPath = getFakeFixturePath("overrides", "bar.js");
+                const resolvedPath = path.resolve(__dirname, "..", "fixtures", "config-hierarchy", "overrides", "bar.js");
+                const config = new Config({
+                    cwd: process.cwd(),
+                    baseConfig: {
+                        overrides: [{
+                            files: resolvedPath,
+                            rules: {
+                                quotes: [1, "double"]
+                            }
+                        }],
+                        useEslintrc: false
+                    }
+                }, linter);
+
+                assert.throws(() => config.getConfig(targetPath), /Invalid override pattern/);
+            });
+
+            it("should not merge override config when the pattern traverses up the directory tree", () => {
+                const targetPath = getFakeFixturePath("overrides", "bar.js");
+                const parentPath = "overrides/../**/*.js";
+
+                const config = new Config({
+                    cwd: process.cwd(),
+                    baseConfig: {
+                        overrides: [{
+                            files: parentPath,
+                            rules: {
+                                quotes: [1, "single"]
+                            }
+                        }],
+                        useEslintrc: false
+                    }
+                }, linter);
+
+                assert.throws(() => config.getConfig(targetPath), /Invalid override pattern/);
+            });
+
+            it("should merge all local configs (override and non-override) before non-local configs", () => {
+                const config = new Config({ cwd: process.cwd() }, linter);
+                const targetPath = getFakeFixturePath("overrides", "two", "child-two.js");
+                const expected = {
+                    rules: {
+                        "no-console": 0,
+                        "no-else-return": 0,
+                        "no-unused-vars": 2,
+                        quotes: [2, "double"],
+                        semi: [2, "never"]
+                    }
+                };
+                const actual = config.getConfig(targetPath);
+
+                assertConfigsEqual(actual, expected);
+            });
+
+            it("should apply overrides in parent .eslintrc over non-override rules in child .eslintrc", () => {
+                const targetPath = getFakeFixturePath("overrides", "three", "foo.js");
+                const config = new Config({
+                    cwd: getFakeFixturePath("overrides"),
+                    baseConfig: {
+                        overrides: [
+                            {
+                                files: "three/**/*.js",
+                                rules: {
+                                    "semi-style": [2, "last"]
+                                }
+                            }
+                        ]
+                    },
+                    useEslintrc: false
+                }, linter);
+                const expected = {
+                    rules: {
+                        "semi-style": [2, "last"]
+                    }
+                };
+                const actual = config.getConfig(targetPath);
+
+                assertConfigsEqual(actual, expected);
+            });
+
+            it("should apply overrides if all glob patterns match", () => {
+                const targetPath = getFakeFixturePath("overrides", "one", "child-one.js");
+                const config = new Config({
+                    cwd: getFakeFixturePath("overrides"),
+                    baseConfig: {
+                        overrides: [{
+                            files: ["one/**/*", "*.js"],
+                            rules: {
+                                quotes: [2, "single"]
+                            }
+                        }]
+                    },
+                    useEslintrc: false
+                }, linter);
+                const expected = {
+                    rules: {
+                        quotes: [2, "single"]
+                    }
+                };
+                const actual = config.getConfig(targetPath);
+
+                assertConfigsEqual(actual, expected);
+            });
+
+            it("should apply overrides even if some glob patterns do not match", () => {
+                const targetPath = getFakeFixturePath("overrides", "one", "child-one.js");
+                const config = new Config({
+                    cwd: getFakeFixturePath("overrides"),
+                    baseConfig: {
+                        overrides: [{
+                            files: ["one/**/*", "*two.js"],
+                            rules: {
+                                quotes: [2, "single"]
+                            }
+                        }]
+                    },
+                    useEslintrc: false
+                }, linter);
+                const expected = {
+                    rules: {
+                        quotes: [2, "single"]
+                    }
+                };
+                const actual = config.getConfig(targetPath);
+
+                assertConfigsEqual(actual, expected);
+            });
+
+            it("should not apply overrides if any excluded glob patterns match", () => {
+                const targetPath = getFakeFixturePath("overrides", "one", "child-one.js");
+                const config = new Config({
+                    cwd: getFakeFixturePath("overrides"),
+                    baseConfig: {
+                        overrides: [{
+                            files: "one/**/*",
+                            excludedFiles: ["two/**/*", "*one.js"],
+                            rules: {
+                                quotes: [2, "single"]
+                            }
+                        }]
+                    },
+                    useEslintrc: false
+                }, linter);
+                const expected = {
+                    rules: {}
+                };
+                const actual = config.getConfig(targetPath);
+
+                assertConfigsEqual(actual, expected);
+            });
+
+            it("should apply overrides if all excluded glob patterns fail to match", () => {
+                const targetPath = getFakeFixturePath("overrides", "one", "child-one.js");
+                const config = new Config({
+                    cwd: getFakeFixturePath("overrides"),
+                    baseConfig: {
+                        overrides: [{
+                            files: "one/**/*",
+                            excludedFiles: ["two/**/*", "*two.js"],
+                            rules: {
+                                quotes: [2, "single"]
+                            }
+                        }]
+                    },
+                    useEslintrc: false
+                }, linter);
+                const expected = {
+                    rules: {
+                        quotes: [2, "single"]
+                    }
+                };
+                const actual = config.getConfig(targetPath);
+
+                assertConfigsEqual(actual, expected);
+            });
+
+            it("should cascade", () => {
+                const targetPath = getFakeFixturePath("overrides", "foo.js");
+                const config = new Config({
+                    cwd: getFakeFixturePath("overrides"),
+                    baseConfig: {
+                        overrides: [
+                            {
+                                files: "foo.js",
+                                rules: {
+                                    semi: [2, "never"],
+                                    quotes: [2, "single"]
+                                }
+                            },
+                            {
+                                files: "foo.js",
+                                rules: {
+                                    semi: [2, "never"],
+                                    quotes: [2, "double"]
+                                }
+                            }
+                        ]
+                    },
+                    useEslintrc: false
+                }, linter);
+                const expected = {
+                    rules: {
+                        semi: [2, "never"],
+                        quotes: [2, "double"]
+                    }
+                };
+                const actual = config.getConfig(targetPath);
+
+                assertConfigsEqual(actual, expected);
+            });
+        });
     });
 
     describe("Plugin Environments", () => {
diff --git a/tests/lib/config/config-file.js b/tests/lib/config/config-file.js
index a8f6046d2..23a206b05 100644
--- a/tests/lib/config/config-file.js
+++ b/tests/lib/config/config-file.js
@@ -16,7 +16,6 @@ const assert = require("chai").assert,
     os = require("os"),
     yaml = require("js-yaml"),
     shell = require("shelljs"),
-    environments = require("../../../conf/environments"),
     ConfigFile = require("../../../lib/config/config-file"),
     Linter = require("../../../lib/linter"),
     Config = require("../../../lib/config");
@@ -24,7 +23,7 @@ const assert = require("chai").assert,
 const userHome = os.homedir();
 const temp = require("temp").track();
 const proxyquire = require("proxyquire").noCallThru().noPreserveCache();
-const configContext = new Config({}, new Linter());
+let configContext;
 
 //------------------------------------------------------------------------------
 // Helpers
@@ -144,6 +143,10 @@ function createStubModuleResolver(mapping) {
 
 describe("ConfigFile", () => {
 
+    beforeEach(() => {
+        configContext = new Config({}, new Linter());
+    });
+
     describe("CONFIG_FILES", () => {
         it("should be present when imported", () => {
             assert.isTrue(Array.isArray(ConfigFile.CONFIG_FILES));
@@ -177,6 +180,8 @@ describe("ConfigFile", () => {
             }, configContext, "/whatever");
 
             assert.deepEqual(config, {
+                baseDirectory: path.dirname(resolvedPath),
+                filePath: resolvedPath,
                 extends: "foo",
                 parserOptions: {},
                 env: { browser: true },
@@ -332,6 +337,8 @@ describe("ConfigFile", () => {
             }, configContext, "/whatever");
 
             assert.deepEqual(config, {
+                baseDirectory: path.dirname(resolvedPaths[0]),
+                filePath: resolvedPaths[0],
                 extends: "foo",
                 parserOptions: {},
                 env: { browser: true },
@@ -346,13 +353,18 @@ describe("ConfigFile", () => {
 
         it("should apply extensions when specified from a JavaScript file", () => {
 
+            const extendsFile = ".eslintrc.js";
+            const filePath = getFixturePath("js/foo.js");
+
             const config = ConfigFile.applyExtends({
-                extends: ".eslintrc.js",
+                extends: extendsFile,
                 rules: { eqeqeq: 2 }
-            }, configContext, getFixturePath("js/foo.js"));
+            }, configContext, filePath);
 
             assert.deepEqual(config, {
-                extends: ".eslintrc.js",
+                baseDirectory: path.dirname(filePath),
+                filePath: path.join(path.dirname(filePath), extendsFile),
+                extends: extendsFile,
                 parserOptions: {},
                 env: {},
                 globals: {},
@@ -366,13 +378,18 @@ describe("ConfigFile", () => {
 
         it("should apply extensions when specified from a YAML file", () => {
 
+            const extendsFile = ".eslintrc.yaml";
+            const filePath = getFixturePath("yaml/foo.js");
+
             const config = ConfigFile.applyExtends({
-                extends: ".eslintrc.yaml",
+                extends: extendsFile,
                 rules: { eqeqeq: 2 }
-            }, configContext, getFixturePath("yaml/foo.js"));
+            }, configContext, filePath);
 
             assert.deepEqual(config, {
-                extends: ".eslintrc.yaml",
+                baseDirectory: path.dirname(filePath),
+                filePath: path.join(path.dirname(filePath), extendsFile),
+                extends: extendsFile,
                 parserOptions: {},
                 env: { browser: true },
                 globals: {},
@@ -385,13 +402,18 @@ describe("ConfigFile", () => {
 
         it("should apply extensions when specified from a JSON file", () => {
 
+            const extendsFile = ".eslintrc.json";
+            const filePath = getFixturePath("json/foo.js");
+
             const config = ConfigFile.applyExtends({
-                extends: ".eslintrc.json",
+                extends: extendsFile,
                 rules: { eqeqeq: 2 }
-            }, configContext, getFixturePath("json/foo.js"));
+            }, configContext, filePath);
 
             assert.deepEqual(config, {
-                extends: ".eslintrc.json",
+                baseDirectory: path.dirname(filePath),
+                filePath: path.join(path.dirname(filePath), extendsFile),
+                extends: extendsFile,
                 parserOptions: {},
                 env: {},
                 globals: {},
@@ -405,13 +427,18 @@ describe("ConfigFile", () => {
 
         it("should apply extensions when specified from a package.json file in a sibling directory", () => {
 
+            const extendsFile = "../package-json/package.json";
+            const filePath = getFixturePath("json/foo.js");
+
             const config = ConfigFile.applyExtends({
-                extends: "../package-json/package.json",
+                extends: extendsFile,
                 rules: { eqeqeq: 2 }
-            }, configContext, getFixturePath("json/foo.js"));
+            }, configContext, filePath);
 
             assert.deepEqual(config, {
-                extends: "../package-json/package.json",
+                baseDirectory: path.dirname(path.resolve(path.dirname(filePath), extendsFile)),
+                filePath: path.resolve(path.dirname(filePath), extendsFile),
+                extends: extendsFile,
                 parserOptions: {},
                 env: { es6: true },
                 globals: {},
@@ -437,9 +464,12 @@ describe("ConfigFile", () => {
         });
 
         it("should load information from a legacy file", () => {
-            const config = ConfigFile.load(getFixturePath("legacy/.eslintrc"), configContext);
+            const configFilePath = getFixturePath("legacy/.eslintrc");
+            const config = ConfigFile.load(configFilePath, configContext);
 
             assert.deepEqual(config, {
+                baseDirectory: path.dirname(configFilePath),
+                filePath: configFilePath,
                 parserOptions: {},
                 env: {},
                 globals: {},
@@ -450,9 +480,12 @@ describe("ConfigFile", () => {
         });
 
         it("should load information from a JavaScript file", () => {
-            const config = ConfigFile.load(getFixturePath("js/.eslintrc.js"), configContext);
+            const configFilePath = getFixturePath("js/.eslintrc.js");
+            const config = ConfigFile.load(configFilePath, configContext);
 
             assert.deepEqual(config, {
+                baseDirectory: path.dirname(configFilePath),
+                filePath: configFilePath,
                 parserOptions: {},
                 env: {},
                 globals: {},
@@ -469,9 +502,12 @@ describe("ConfigFile", () => {
         });
 
         it("should interpret parser module name when present in a JavaScript file", () => {
-            const config = ConfigFile.load(getFixturePath("js/.eslintrc.parser.js"), configContext);
+            const configFilePath = getFixturePath("js/.eslintrc.parser.js");
+            const config = ConfigFile.load(configFilePath, configContext);
 
             assert.deepEqual(config, {
+                baseDirectory: path.dirname(configFilePath),
+                filePath: configFilePath,
                 parser: path.resolve(getFixturePath("js/node_modules/foo/index.js")),
                 parserOptions: {},
                 env: {},
@@ -483,9 +519,12 @@ describe("ConfigFile", () => {
         });
 
         it("should interpret parser path when present in a JavaScript file", () => {
-            const config = ConfigFile.load(getFixturePath("js/.eslintrc.parser2.js"), configContext);
+            const configFilePath = getFixturePath("js/.eslintrc.parser2.js");
+            const config = ConfigFile.load(configFilePath, configContext);
 
             assert.deepEqual(config, {
+                baseDirectory: path.dirname(configFilePath),
+                filePath: configFilePath,
                 parser: path.resolve(getFixturePath("js/not-a-config.js")),
                 parserOptions: {},
                 env: {},
@@ -497,9 +536,12 @@ describe("ConfigFile", () => {
         });
 
         it("should interpret parser module name or path when parser is set to default parser in a JavaScript file", () => {
-            const config = ConfigFile.load(getFixturePath("js/.eslintrc.parser3.js"), configContext);
+            const configFilePath = getFixturePath("js/.eslintrc.parser3.js");
+            const config = ConfigFile.load(configFilePath, configContext);
 
             assert.deepEqual(config, {
+                baseDirectory: path.dirname(configFilePath),
+                filePath: configFilePath,
                 parser: require.resolve("espree"),
                 parserOptions: {},
                 env: {},
@@ -511,9 +553,12 @@ describe("ConfigFile", () => {
         });
 
         it("should load information from a JSON file", () => {
-            const config = ConfigFile.load(getFixturePath("json/.eslintrc.json"), configContext);
+            const configFilePath = getFixturePath("json/.eslintrc.json");
+            const config = ConfigFile.load(configFilePath, configContext);
 
             assert.deepEqual(config, {
+                baseDirectory: path.dirname(configFilePath),
+                filePath: configFilePath,
                 parserOptions: {},
                 env: {},
                 globals: {},
@@ -544,16 +589,26 @@ describe("ConfigFile", () => {
                 tmpFilePath = writeTempConfigFile(initialConfig, tmpFilename);
             let config = ConfigFile.load(tmpFilePath, configContext);
 
-            assert.deepEqual(config, initialConfig);
+            assert.deepEqual(config, Object.assign({}, initialConfig, {
+                baseDirectory: path.dirname(tmpFilePath),
+                filePath: tmpFilePath
+            }));
             writeTempConfigFile(updatedConfig, tmpFilename, path.dirname(tmpFilePath));
+            configContext = new Config({}, new Linter());
             config = ConfigFile.load(tmpFilePath, configContext);
-            assert.deepEqual(config, updatedConfig);
+            assert.deepEqual(config, Object.assign({}, updatedConfig, {
+                baseDirectory: path.dirname(tmpFilePath),
+                filePath: tmpFilePath
+            }));
         });
 
         it("should load information from a package.json file", () => {
-            const config = ConfigFile.load(getFixturePath("package-json/package.json"), configContext);
+            const configFilePath = getFixturePath("package-json/package.json");
+            const config = ConfigFile.load(configFilePath, configContext);
 
             assert.deepEqual(config, {
+                baseDirectory: path.dirname(configFilePath),
+                filePath: configFilePath,
                 parserOptions: {},
                 env: { es6: true },
                 globals: {},
@@ -567,17 +622,6 @@ describe("ConfigFile", () => {
             }, /Cannot read config file/);
         });
 
-        it("should load information from a package.json file and apply environments", () => {
-            const config = ConfigFile.load(getFixturePath("package-json/package.json"), configContext, true);
-
-            assert.deepEqual(config, {
-                parserOptions: { ecmaVersion: 6 },
-                env: { es6: true },
-                globals: environments.es6.globals,
-                rules: {}
-            });
-        });
-
         it("should load fresh information from a package.json file", () => {
             const initialConfig = {
                     eslintConfig: {
@@ -603,10 +647,17 @@ describe("ConfigFile", () => {
                 tmpFilePath = writeTempConfigFile(initialConfig, tmpFilename);
             let config = ConfigFile.load(tmpFilePath, configContext);
 
-            assert.deepEqual(config, initialConfig.eslintConfig);
+            assert.deepEqual(config, Object.assign({}, initialConfig.eslintConfig, {
+                baseDirectory: path.dirname(tmpFilePath),
+                filePath: tmpFilePath
+            }));
             writeTempConfigFile(updatedConfig, tmpFilename, path.dirname(tmpFilePath));
+            configContext = new Config({}, new Linter());
             config = ConfigFile.load(tmpFilePath, configContext);
-            assert.deepEqual(config, updatedConfig.eslintConfig);
+            assert.deepEqual(config, Object.assign({}, updatedConfig.eslintConfig, {
+                baseDirectory: path.dirname(tmpFilePath),
+                filePath: tmpFilePath
+            }));
         });
 
         it("should load fresh information from a .eslintrc.js file", () => {
@@ -630,16 +681,26 @@ describe("ConfigFile", () => {
                 tmpFilePath = writeTempJsConfigFile(initialConfig, tmpFilename);
             let config = ConfigFile.load(tmpFilePath, configContext);
 
-            assert.deepEqual(config, initialConfig);
+            assert.deepEqual(config, Object.assign({}, initialConfig, {
+                baseDirectory: path.dirname(tmpFilePath),
+                filePath: tmpFilePath
+            }));
             writeTempJsConfigFile(updatedConfig, tmpFilename, path.dirname(tmpFilePath));
+            configContext = new Config({}, new Linter());
             config = ConfigFile.load(tmpFilePath, configContext);
-            assert.deepEqual(config, updatedConfig);
+            assert.deepEqual(config, Object.assign({}, updatedConfig, {
+                baseDirectory: path.dirname(tmpFilePath),
+                filePath: tmpFilePath
+            }));
         });
 
         it("should load information from a YAML file", () => {
-            const config = ConfigFile.load(getFixturePath("yaml/.eslintrc.yaml"), configContext);
+            const configFilePath = getFixturePath("yaml/.eslintrc.yaml");
+            const config = ConfigFile.load(configFilePath, configContext);
 
             assert.deepEqual(config, {
+                baseDirectory: path.dirname(configFilePath),
+                filePath: configFilePath,
                 parserOptions: {},
                 env: { browser: true },
                 globals: {},
@@ -648,9 +709,12 @@ describe("ConfigFile", () => {
         });
 
         it("should load information from a YAML file", () => {
-            const config = ConfigFile.load(getFixturePath("yaml/.eslintrc.empty.yaml"), configContext);
+            const configFilePath = getFixturePath("yaml/.eslintrc.empty.yaml");
+            const config = ConfigFile.load(configFilePath, configContext);
 
             assert.deepEqual(config, {
+                baseDirectory: path.dirname(configFilePath),
+                filePath: configFilePath,
                 parserOptions: {},
                 env: {},
                 globals: {},
@@ -658,21 +722,13 @@ describe("ConfigFile", () => {
             });
         });
 
-        it("should load information from a YAML file and apply environments", () => {
-            const config = ConfigFile.load(getFixturePath("yaml/.eslintrc.yaml"), configContext, true);
-
-            assert.deepEqual(config, {
-                parserOptions: {},
-                env: { browser: true },
-                globals: environments.browser.globals,
-                rules: {}
-            });
-        });
-
         it("should load information from a YML file", () => {
-            const config = ConfigFile.load(getFixturePath("yml/.eslintrc.yml"), configContext);
+            const configFilePath = getFixturePath("yml/.eslintrc.yml");
+            const config = ConfigFile.load(configFilePath, configContext);
 
             assert.deepEqual(config, {
+                baseDirectory: path.dirname(configFilePath),
+                filePath: configFilePath,
                 parserOptions: {},
                 env: { node: true },
                 globals: {},
@@ -680,33 +736,28 @@ describe("ConfigFile", () => {
             });
         });
 
-        it("should load information from a YML file and apply environments", () => {
-            const config = ConfigFile.load(getFixturePath("yml/.eslintrc.yml"), configContext, true);
-
-            assert.deepEqual(config, {
-                parserOptions: { ecmaFeatures: { globalReturn: true } },
-                env: { node: true },
-                globals: environments.node.globals,
-                rules: {}
-            });
-        });
-
         it("should load information from a YML file and apply extensions", () => {
-            const config = ConfigFile.load(getFixturePath("extends/.eslintrc.yml"), configContext, true);
+            const configFilePath = getFixturePath("extends/.eslintrc.yml");
+            const config = ConfigFile.load(configFilePath, configContext);
 
             assert.deepEqual(config, {
-                extends: "../package-json/package.json",
-                parserOptions: { ecmaVersion: 6 },
+                baseDirectory: path.dirname(configFilePath),
+                filePath: configFilePath,
                 env: { es6: true },
-                globals: environments.es6.globals,
+                extends: "../package-json/package.json",
+                globals: {},
+                parserOptions: {},
                 rules: { booya: 2 }
             });
         });
 
         it("should load information from `extends` chain.", () => {
-            const config = ConfigFile.load(getFixturePath("extends-chain/.eslintrc.json"), configContext);
+            const configFilePath = getFixturePath("extends-chain/.eslintrc.json");
+            const config = ConfigFile.load(configFilePath, configContext);
 
             assert.deepEqual(config, {
+                baseDirectory: path.dirname(configFilePath),
+                filePath: configFilePath,
                 env: {},
                 extends: "a",
                 globals: {},
@@ -720,9 +771,12 @@ describe("ConfigFile", () => {
         });
 
         it("should load information from `extends` chain with relative path.", () => {
-            const config = ConfigFile.load(getFixturePath("extends-chain-2/.eslintrc.json"), configContext);
+            const configFilePath = getFixturePath("extends-chain-2/.eslintrc.json");
+            const config = ConfigFile.load(configFilePath, configContext);
 
             assert.deepEqual(config, {
+                baseDirectory: path.dirname(configFilePath),
+                filePath: configFilePath,
                 env: {},
                 extends: "a",
                 globals: {},
@@ -735,9 +789,12 @@ describe("ConfigFile", () => {
         });
 
         it("should load information from `extends` chain in .eslintrc with relative path.", () => {
-            const config = ConfigFile.load(getFixturePath("extends-chain-2/relative.eslintrc.json"), configContext);
+            const configFilePath = getFixturePath("extends-chain-2/relative.eslintrc.json");
+            const config = ConfigFile.load(configFilePath, configContext);
 
             assert.deepEqual(config, {
+                baseDirectory: path.dirname(configFilePath),
+                filePath: configFilePath,
                 env: {},
                 extends: "./node_modules/eslint-config-a/index.js",
                 globals: {},
@@ -750,10 +807,13 @@ describe("ConfigFile", () => {
         });
 
         it("should load information from `parser` in .eslintrc with relative path.", () => {
-            const config = ConfigFile.load(getFixturePath("extends-chain-2/parser.eslintrc.json"), configContext);
+            const configFilePath = getFixturePath("extends-chain-2/parser.eslintrc.json");
+            const config = ConfigFile.load(configFilePath, configContext);
             const parserPath = getFixturePath("extends-chain-2/parser.js");
 
             assert.deepEqual(config, {
+                baseDirectory: path.dirname(configFilePath),
+                filePath: configFilePath,
                 env: {},
                 globals: {},
                 parser: parserPath,
@@ -778,9 +838,12 @@ describe("ConfigFile", () => {
             });
 
             it("should load information from `extends` chain in .eslintrc with relative path.", () => {
-                const config = ConfigFile.load(path.join(fixturePath, "relative.eslintrc.json"), configContext);
+                const configFilePath = path.join(fixturePath, "relative.eslintrc.json");
+                const config = ConfigFile.load(configFilePath, configContext);
 
                 assert.deepEqual(config, {
+                    baseDirectory: path.dirname(configFilePath),
+                    filePath: configFilePath,
                     env: {},
                     extends: "./node_modules/eslint-config-a/index.js",
                     globals: {},
@@ -793,10 +856,13 @@ describe("ConfigFile", () => {
             });
 
             it("should load information from `parser` in .eslintrc with relative path.", () => {
-                const config = ConfigFile.load(path.join(fixturePath, "parser.eslintrc.json"), configContext);
+                const configFilePath = path.join(fixturePath, "parser.eslintrc.json");
+                const config = ConfigFile.load(configFilePath, configContext);
                 const parserPath = path.join(fixturePath, "parser.js");
 
                 assert.deepEqual(config, {
+                    baseDirectory: path.dirname(configFilePath),
+                    filePath: configFilePath,
                     env: {},
                     globals: {},
                     parser: parserPath,
@@ -818,9 +884,12 @@ describe("ConfigFile", () => {
                     }
                 });
 
-                const config = ConfigFile.load(getFixturePath("plugins/.eslintrc.yml"), stubConfig);
+                const configFilePath = getFixturePath("plugins/.eslintrc.yml");
+                const config = ConfigFile.load(configFilePath, stubConfig);
 
                 assert.deepEqual(config, {
+                    baseDirectory: path.dirname(configFilePath),
+                    filePath: configFilePath,
                     parserOptions: {},
                     env: { "test/bar": true },
                     globals: {},
@@ -830,13 +899,59 @@ describe("ConfigFile", () => {
                     }
                 });
             });
+
+            it("should load two separate configs from a plugin", () => {
+                const stubConfig = new Config({}, new Linter());
+                const resolvedPath = path.resolve(PROJECT_PATH, "./node_modules/eslint-plugin-test/index.js");
+
+                const configDeps = {
+                    "../util/module-resolver": createStubModuleResolver({
+                        "eslint-plugin-test": resolvedPath
+                    }),
+                    "require-uncached"(filename) {
+                        return configDeps[filename];
+                    }
+                };
+
+                configDeps[resolvedPath] = {
+                    configs: {
+                        foo: { rules: { semi: 2, quotes: 1 } },
+                        bar: { rules: { quotes: 2, yoda: 2 } }
+                    }
+                };
+
+                const StubbedConfigFile = proxyquire("../../../lib/config/config-file", configDeps);
+
+                const configFilePath = getFixturePath("plugins/.eslintrc2.yml");
+                const config = StubbedConfigFile.load(configFilePath, stubConfig);
+
+                assert.deepEqual(config, {
+                    baseDirectory: path.dirname(configFilePath),
+                    filePath: configFilePath,
+                    parserOptions: {},
+                    globals: {},
+                    env: {},
+                    rules: {
+                        semi: 2,
+                        quotes: 2,
+                        yoda: 2
+                    },
+                    extends: [
+                        "plugin:test/foo",
+                        "plugin:test/bar"
+                    ]
+                });
+            });
         });
 
         describe("even if config files have Unicode BOM,", () => {
             it("should read the JSON config file correctly.", () => {
-                const config = ConfigFile.load(getFixturePath("bom/.eslintrc.json"), configContext);
+                const configFilePath = getFixturePath("bom/.eslintrc.json");
+                const config = ConfigFile.load(configFilePath, configContext);
 
                 assert.deepEqual(config, {
+                    baseDirectory: path.dirname(configFilePath),
+                    filePath: configFilePath,
                     env: {},
                     globals: {},
                     parserOptions: {},
@@ -847,9 +962,12 @@ describe("ConfigFile", () => {
             });
 
             it("should read the YAML config file correctly.", () => {
-                const config = ConfigFile.load(getFixturePath("bom/.eslintrc.yaml"), configContext);
+                const configFilePath = getFixturePath("bom/.eslintrc.yaml");
+                const config = ConfigFile.load(configFilePath, configContext);
 
                 assert.deepEqual(config, {
+                    baseDirectory: path.dirname(configFilePath),
+                    filePath: configFilePath,
                     env: {},
                     globals: {},
                     parserOptions: {},
@@ -860,9 +978,12 @@ describe("ConfigFile", () => {
             });
 
             it("should read the config in package.json correctly.", () => {
-                const config = ConfigFile.load(getFixturePath("bom/package.json"), configContext);
+                const configFilePath = getFixturePath("bom/package.json");
+                const config = ConfigFile.load(configFilePath, configContext);
 
                 assert.deepEqual(config, {
+                    baseDirectory: path.dirname(configFilePath),
+                    filePath: configFilePath,
                     env: {},
                     globals: {},
                     parserOptions: {},
diff --git a/tests/lib/config/config-ops.js b/tests/lib/config/config-ops.js
index f0c72c488..ed95238d0 100644
--- a/tests/lib/config/config-ops.js
+++ b/tests/lib/config/config-ops.js
@@ -12,6 +12,7 @@ const assert = require("chai").assert,
     leche = require("leche"),
     environments = require("../../../conf/environments"),
     Environments = require("../../../lib/config/environments"),
+    ConfigCache = require("../../../lib/config/config-cache"),
     ConfigOps = require("../../../lib/config/config-ops");
 
 const envContext = new Environments();
@@ -475,7 +476,78 @@ describe("ConfigOps", () => {
             });
         });
 
+        describe("overrides", () => {
+            it("should combine the override entries in the correct order", () => {
+                const baseConfig = { overrides: [{ files: ["**/*Spec.js"], env: { mocha: true } }] };
+                const customConfig = { overrides: [{ files: ["**/*.jsx"], ecmaFeatures: { jsx: true } }] };
+                const expectedResult = {
+                    overrides: [
+                        { files: ["**/*Spec.js"], env: { mocha: true } },
+                        { files: ["**/*.jsx"], ecmaFeatures: { jsx: true } }
+                    ]
+                };
 
+                const result = ConfigOps.merge(baseConfig, customConfig);
+
+                assert.deepEqual(result, expectedResult);
+            });
+
+            it("should work if the base config doesn’t have an overrides property", () => {
+                const baseConfig = {};
+                const customConfig = { overrides: [{ files: ["**/*.jsx"], ecmaFeatures: { jsx: true } }] };
+                const expectedResult = {
+                    overrides: [
+                        { files: ["**/*.jsx"], ecmaFeatures: { jsx: true } }
+                    ]
+                };
+
+                const result = ConfigOps.merge(baseConfig, customConfig);
+
+                assert.deepEqual(result, expectedResult);
+            });
+
+            it("should work if the custom config doesn’t have an overrides property", () => {
+                const baseConfig = { overrides: [{ files: ["**/*Spec.js"], env: { mocha: true } }] };
+                const customConfig = {};
+                const expectedResult = {
+                    overrides: [
+                        { files: ["**/*Spec.js"], env: { mocha: true } }
+                    ]
+                };
+
+                const result = ConfigOps.merge(baseConfig, customConfig);
+
+                assert.deepEqual(result, expectedResult);
+            });
+
+            it("should work if overrides are null in the base config", () => {
+                const baseConfig = { overrides: null };
+                const customConfig = { overrides: [{ files: ["**/*.jsx"], ecmaFeatures: { jsx: true } }] };
+                const expectedResult = {
+                    overrides: [
+                        { files: ["**/*.jsx"], ecmaFeatures: { jsx: true } }
+                    ]
+                };
+
+                const result = ConfigOps.merge(baseConfig, customConfig);
+
+                assert.deepEqual(result, expectedResult);
+            });
+
+            it("should work if overrides are null in the custom config", () => {
+                const baseConfig = { overrides: [{ files: ["**/*Spec.js"], env: { mocha: true } }] };
+                const customConfig = { overrides: null };
+                const expectedResult = {
+                    overrides: [
+                        { files: ["**/*Spec.js"], env: { mocha: true } }
+                    ]
+                };
+
+                const result = ConfigOps.merge(baseConfig, customConfig);
+
+                assert.deepEqual(result, expectedResult);
+            });
+        });
     });
 
     describe("normalize()", () => {
@@ -794,4 +866,57 @@ describe("ConfigOps", () => {
 
     });
 
+    describe("getConfigFromVector()", () => {
+        let configCache;
+
+        beforeEach(() => {
+            configCache = new ConfigCache();
+        });
+
+        it("should get from merged vector cache when present", () => {
+            const vector = [
+                { filePath: "configFile1", matchingOverrides: [1] },
+                { filePath: "configFile2", matchingOverrides: [0, 1] }
+            ];
+            const merged = { merged: true };
+
+            configCache.setMergedVectorConfig(vector, merged);
+
+            const result = ConfigOps.getConfigFromVector(vector, configCache);
+
+            assert.deepEqual(result, merged);
+        });
+
+        it("should get from raw cached configs when no merged vectors are cached", () => {
+            const config = [
+                {
+                    rules: { foo1: "off" },
+                    overrides: [
+                        { files: "pattern1", rules: { foo1: "warn" } },
+                        { files: "pattern2", rules: { foo1: "error" } }
+                    ]
+                },
+                {
+                    rules: { foo2: "warn" },
+                    overrides: [
+                        { files: "pattern1", rules: { foo2: "error" } },
+                        { files: "pattern2", rules: { foo2: "off" } }
+                    ]
+                }
+            ];
+
+            configCache.setConfig("configFile1", config[0]);
+            configCache.setConfig("configFile2", config[1]);
+
+            const vector = [
+                { filePath: "configFile1", matchingOverrides: [1] },
+                { filePath: "configFile2", matchingOverrides: [0, 1] }
+            ];
+
+            const result = ConfigOps.getConfigFromVector(vector, configCache);
+
+            assert.equal(result.rules.foo1, "error");
+            assert.equal(result.rules.foo2, "off");
+        });
+    });
 });
diff --git a/tests/lib/config/config-validator.js b/tests/lib/config/config-validator.js
index 3349c07ef..5e42823b2 100644
--- a/tests/lib/config/config-validator.js
+++ b/tests/lib/config/config-validator.js
@@ -350,7 +350,51 @@ 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");
+            });
+        });
+
+        describe("overrides", () => {
+            it("should not throw with an empty overrides array", () => {
+                const fn = validator.validate.bind(null, { overrides: [] }, "tests", linter.rules, linter.environments);
+
+                assert.doesNotThrow(fn);
+            });
+
+            it("should not throw with a valid overrides array", () => {
+                const fn = validator.validate.bind(null, { overrides: [{ files: "*", rules: {} }] }, "tests", linter.rules, linter.environments);
+
+                assert.doesNotThrow(fn);
+            });
+
+            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]\" 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- 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[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[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[0].root\".\n");
             });
         });
 
@@ -406,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..0f6e05148 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,
@@ -2102,6 +2156,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 +3343,12 @@ ruleTester.run("indent", rule, {
                 ; [1, 2, 3].map(baz)
             `
         },
+        {
+            code: unIndent`
+                if (foo)
+                    ;
+            `
+        },
         {
             code: "x => {}"
         },
@@ -3810,6 +3897,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 +4716,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>
+                    );
+                }
+            `
         }
     ],
 
@@ -6489,20 +6675,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 +6711,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 +8034,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 +8120,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 +8885,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 fb69eb713..be32041c8 100644
--- a/tests/lib/rules/no-extra-parens.js
+++ b/tests/lib/rules/no-extra-parens.js
@@ -368,6 +368,10 @@ ruleTester.run("no-extra-parens", rule, {
             "/>)"
         ].join("\n"), options: ["all", { ignoreJSX: "multi-line" }] },
 
+        // ["all", { enforceForArrowConditionals: false }]
+        { code: "var a = b => 1 ? 2 : 3", options: ["all", { enforceForArrowConditionals: false }], parserOptions: { ecmaVersion: 6 } },
+        { code: "var a = (b) => (1 ? 2 : 3)", options: ["all", { enforceForArrowConditionals: false }], parserOptions: { ecmaVersion: 6 } },
+
         {
             code: "let a = [ ...b ]",
             parserOptions: { ecmaVersion: 2015 }
@@ -446,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: [
@@ -921,6 +930,32 @@ ruleTester.run("no-extra-parens", rule, {
             options: ["all", { ignoreJSX: "none" }]
         }),
 
+        // ["all", { enforceForArrowConditionals: true }]
+        {
+            code: "var a = (b) => (1 ? 2 : 3)",
+            parserOptions: { ecmaVersion: 6 },
+            options: ["all", { enforceForArrowConditionals: true }],
+            errors: [
+                {
+                    message: "Gratuitous parentheses around expression."
+                }
+            ],
+            output: "var a = (b) => 1 ? 2 : 3"
+        },
+
+        // ["all", { enforceForArrowConditionals: false }]
+        {
+            code: "var a = (b) => ((1 ? 2 : 3))",
+            parserOptions: { ecmaVersion: 6 },
+            options: ["all", { enforceForArrowConditionals: false }],
+            errors: [
+                {
+                    message: "Gratuitous parentheses around expression."
+                }
+            ],
+            output: "var a = (b) => (1 ? 2 : 3)"
+        },
+
         // https://github.com/eslint/eslint/issues/8175
         invalid(
             "let a = [...(b)]",
@@ -1022,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-trailing-spaces.js b/tests/lib/rules/no-trailing-spaces.js
index 44a6ba5b6..3cfcfda97 100644
--- a/tests/lib/rules/no-trailing-spaces.js
+++ b/tests/lib/rules/no-trailing-spaces.js
@@ -70,6 +70,18 @@ ruleTester.run("no-trailing-spaces", rule, {
             code: "let str = `${a}\n   \n${b}`;\n   \n   ",
             parserOptions: { ecmaVersion: 6 },
             options: [{ skipBlankLines: true }]
+        },
+        {
+            code: "// Trailing comment test. ",
+            options: [{ ignoreComments: true }]
+        },
+        {
+            code: "/* \nTrailing comments test. \n*/",
+            options: [{ ignoreComments: true }]
+        },
+        {
+            code: "#!/usr/bin/env node ",
+            options: [{ ignoreComments: true }]
         }
     ],
 
@@ -416,6 +428,66 @@ ruleTester.run("no-trailing-spaces", rule, {
                     column: 8
                 }
             ]
+        },
+
+        // Tests for ignoreComments flag.
+        {
+            code: "var foo = 'bar'; ",
+            output: "var foo = 'bar';",
+            options: [{ ignoreComments: true }],
+            errors: [
+                {
+                    message: "Trailing spaces not allowed.",
+                    type: "Program",
+                    line: 1,
+                    column: 17
+                }
+            ]
+        },
+        {
+            code: "// Trailing comment test. ",
+            output: "// Trailing comment test.",
+            options: [{ ignoreComments: false }],
+            errors: [
+                {
+                    message: "Trailing spaces not allowed.",
+                    type: "Program",
+                    line: 1,
+                    column: 26
+                }
+            ]
+        },
+        {
+            code: "/* \nTrailing comments test. \n*/",
+            output: "/*\nTrailing comments test.\n*/",
+            options: [{ ignoreComments: false }],
+            errors: [
+                {
+                    message: "Trailing spaces not allowed.",
+                    type: "Program",
+                    line: 1,
+                    column: 3
+                },
+                {
+                    message: "Trailing spaces not allowed.",
+                    type: "Program",
+                    line: 2,
+                    column: 24
+                }
+            ]
+        },
+        {
+            code: "#!/usr/bin/env node ",
+            output: "#!/usr/bin/env node",
+            options: [{ ignoreComments: false }],
+            errors: [
+                {
+                    message: "Trailing spaces not allowed.",
+                    type: "Program",
+                    line: 1,
+                    column: 20
+                }
+            ]
         }
     ]
 });
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", () => {