Repository: eslint/eslint
Pull request #9261 (2017-09-08 20:47:32)

Fix handling of no-extra-parens on some MemberExpression cases
<!--
    ESLint adheres to the [JS Foundation Code of Conduct](https://js.foundation/community/code-of-conduct).
-->

**What is the purpose of this pull request? (put an "X" next to item)**

[x] Bug fix 

For https://github.com/eslint/eslint/issues/9156.

<!--
    If the item you've checked above has a template, please paste the template questions below and answer them. (If this pull request is addressing an issue, you can just paste a link to the issue here instead.)
-->

<!--
    Please ensure your pull request is ready:

    - Read the pull request guide (http://eslint.org/docs/developer-guide/contributing/pull-requests)
    - Include tests for this change
    - Update documentation for this change (if appropriate)
-->

<!--
    The following is required for all pull requests:
-->

**What changes did you make? (Give an overview)**

- Report any call expression wrapped in parentheses in a member expression
- Do not report any member expression wrapped in parentheses in a new expression if the member expression contains any call expression

**Is there anything you'd like reviewers to focus on?**

- Let me know if this is the correct behavior.
- Let me know if there is a simpler implementation you have in mind.




jsf-clabot: [![CLA assistant check](https://cla.js.foundation/pull/badge/not_signed)](https://cla.js.foundation/eslint/eslint?pullRequest=9261) <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=9261) before we can accept your contribution.<br/>
eslintbot: Thanks for the pull request, @jackyho112! I took a look to make sure it's ready for merging and found some changes are needed:


* The commit summary needs to begin with a tag (such as `Fix:` or `Update:`). Please check out our [guide](http://eslint.org/docs/developer-guide/contributing/pull-requests#step-2-make-your-changes) for how to properly format your commit summary and [update](http://eslint.org/docs/developer-guide/contributing/pull-requests#updating-the-commit-message) it on this pull request.


Can you please update the pull request to address these?

(More information can be found in our [pull request guide](http://eslint.org/docs/developer-guide/contributing/pull-requests).)
eslintbot: Thanks for the pull request, @jackyho112! I took a look to make sure it's ready for merging and found some changes are needed:


* The commit summary needs to begin with a tag (such as `Fix:` or `Update:`). Please check out our [guide](http://eslint.org/docs/developer-guide/contributing/pull-requests#step-2-make-your-changes) for how to properly format your commit summary and [update](http://eslint.org/docs/developer-guide/contributing/pull-requests#updating-the-commit-message) it on this pull request.


Can you please update the pull request to address these?

(More information can be found in our [pull request guide](http://eslint.org/docs/developer-guide/contributing/pull-requests).)
eslintbot: Thanks for the pull request, @jackyho112! I took a look to make sure it's ready for merging and found some changes are needed:


* The commit summary must be 72 characters or shorter. Please check out our [guide](http://eslint.org/docs/developer-guide/contributing/pull-requests#step-2-make-your-changes) for how to properly format your commit summary and [update](http://eslint.org/docs/developer-guide/contributing/pull-requests#updating-the-commit-message) it on this pull request.


Can you please update the pull request to address these?

(More information can be found in our [pull request guide](http://eslint.org/docs/developer-guide/contributing/pull-requests).)
eslintbot: LGTM
eslintbot: LGTM

Patch:
diff --git a/.eslintrc.yml b/.eslintrc.yml
index d7b0e414b..8a03a0ed0 100644
--- a/.eslintrc.yml
+++ b/.eslintrc.yml
@@ -9,7 +9,7 @@ extends:
     - "plugin:eslint-plugin/recommended"
 rules:
     eslint-plugin/consistent-output: "error"
-    eslint-plugin/no-identical-tests: "error"
+    eslint-plugin/no-deprecated-context-methods: "error"
     eslint-plugin/prefer-output-null: "error"
     eslint-plugin/prefer-placeholders: "error"
     eslint-plugin/report-message-format: ["error", '[^a-z].*\.$']
diff --git a/Makefile.js b/Makefile.js
index 7e22731d2..ded744d63 100644
--- a/Makefile.js
+++ b/Makefile.js
@@ -792,6 +792,7 @@ target.gensite = function(prereleaseVersion) {
     // 13. Update demos, but only for non-prereleases
     if (!prereleaseVersion) {
         echo("> Updating the demos (Step 13)");
+        target.browserify();
         cp("-f", "build/eslint.js", `${SITE_DIR}js/app/eslint.js`);
     } else {
         echo("> Skipped updating the demos (Step 13)");
diff --git a/docs/developer-guide/nodejs-api.md b/docs/developer-guide/nodejs-api.md
index 622d8612b..96c3e0754 100644
--- a/docs/developer-guide/nodejs-api.md
+++ b/docs/developer-guide/nodejs-api.md
@@ -60,16 +60,18 @@ var linter = new Linter();
 
 ### Linter#verify
 
-The most important method on `Linter` is `verify()`, which initiates linting of the given text. This method accepts four arguments:
+The most important method on `Linter` is `verify()`, which initiates linting of the given text. This method accepts three arguments:
 
 * `code` - the source code to lint (a string or instance of `SourceCode`).
 * `config` - a configuration object that has been processed and normalized by CLIEngine using eslintrc files and/or other configuration arguments.
     * **Note**: If you want to lint text and have your configuration be read and processed, use CLIEngine's [`executeOnFiles`](#executeonfiles) or [`executeOnText`](#executeontext) instead.
-* `optionsOrFilename` - (optional) Additional options for this run or a string representing the filename to associate with the code being linted.
+* `options` - (optional) Additional options for this run.
     * `filename` - (optional) the filename to associate with the source code.
-    * `saveState` - (optional) see below. This will override any value passed as the fourth argument if an options object is used here instead of the filename.
+    * `preprocess` - (optional) A function that accepts a string containing source text, and returns an array of strings containing blocks of code to lint. Also see: [Processors in Plugins](/docs/developer-guide/working-with-plugins#processors-in-plugins)
+    * `postprocess` - (optional) A function that accepts an array of problem lists (one list of problems for each block of code from `preprocess`), and returns a one-dimensional array of problems containing problems for the original, unprocessed text. Also see: [Processors in Plugins](/docs/developer-guide/working-with-plugins#processors-in-plugins)
     * `allowInlineConfig` - (optional) set to `false` to disable inline comments from changing eslint rules.
-* `saveState` - (optional) set to true to maintain the internal state of `linter` after linting (mostly used for testing purposes)
+
+If the third argument is a string, it is interpreted as the `filename`.
 
 You can call `verify()` like this:
 
@@ -693,6 +695,125 @@ CLIEngine.outputFixes(report);
 require("eslint").CLIEngine.version; // '4.5.0'
 ```
 
+## RuleTester
+
+`eslint.RuleTester` is a utility to write tests for ESLint rules. It is used internally for the bundled rules that come with ESLint, and it can also be used by plugins.
+
+Example usage:
+
+```js
+"use strict";
+
+const rule = require("../../../lib/rules/my-rule");
+const RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester();
+
+ruleTester.run("my-rule", rule, {
+    valid: [
+        {
+            code: "var foo = true",
+            options: [{ allowFoo: true }]
+        }
+    ],
+
+    invalid: [
+        {
+            code: "var invalidVariable = true",
+            errors: [{ message: "Unexpected invalid variable." }]
+        },
+        {
+            code: "var invalidVariable = true",
+            errors: [{ message: /^Unexpected.+variable/ }]
+        }
+    ]
+});
+```
+
+The `RuleTester` constructor accepts an optional object argument, which can be used to specify defaults for your test cases. For example, if all of your test cases use ES2015, you can set it as a default:
+
+```js
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2015 } });
+```
+
+The `RuleTester#run()` method is used to run the tests. It should be passed the following arguments:
+
+* The name of the rule (string)
+* The rule object itself (see ["working with rules"](./working-with-rules))
+* An object containing `valid` and `invalid` properties, each of which is an array containing test cases.
+
+A test case is an object with the following properties:
+
+* `code` (string, required): The source code that the rule should be run on
+* `options` (array, optional): The options passed to the rule. The rule severity should not be included in this list.
+* `filename` (string, optional): The filename for the given case (useful for rules that make assertions about filenames).
+
+In addition to the properties above, invalid test cases can also have the following properties:
+
+* `errors` (number or array, required): Asserts some properties of the errors that the rule is expected to produce when run on this code. If this is a number, asserts the number of errors produced. Otherwise, this should be a list of objects, each containing information about a single reported error. The following properties can be used for an error (all are optional):
+    * `message` (string/regexp): The message for the error
+    * `type` (string): The type of the reported AST node
+    * `line` (number): The 1-based line number of the reported location
+    * `column` (number): The 0-based column number of the reported location
+    * `endLine` (number): The 1-based line number of the end of the reported location
+    * `endColumn` (number): The 0-based column number of the end of the reported location
+
+    If a string is provided as an error instead of an object, the string is used to assert the `message` of the error.
+* `output` (string, optional): Asserts the output that will be produced when using this rule for a single pass of autofixing (e.g. with the `--fix` command line flag). If this is `null`, asserts that none of the reported problems suggest autofixes.
+
+Any additional properties of a test case will be passed directly to the linter as config options. For example, a test case can have a `parserOptions` property to configure parser behavior:
+
+```js
+{
+    code: "let foo;",
+    parserOptions: { ecmaVersion: 2015 }
+}
+```
+
+If a valid test case only uses the `code` property, it can optionally be provided as a string containing the code, rather than an object with a `code` key.
+
+### Customizing RuleTester
+
+`RuleTester` depends on two functions to run tests: `describe` and `it`. These functions can come from various places:
+
+1. If `RuleTester.describe` and `RuleTester.it` have been set to function values, `RuleTester` will use `RuleTester.describe` and `RuleTester.it` to run tests. You can use this to customize the behavior of `RuleTester` to match a test framework that you're using.
+1. Otherwise, if `describe` and `it` are present as globals, `RuleTester` will use `global.describe` and `global.it` to run tests. This allows `RuleTester` to work when using frameworks like [Mocha](https://mochajs.org/) without any additional configuration.
+1. Otherwise, `RuleTester#run` will simply execute all of the tests in sequence, and will throw an error if one of them fails. This means you can simply execute a test file that calls `RuleTester.run` using `node`, without needing a testing framework.
+
+`RuleTester#run` calls the `describe` function with two arguments: a string describing the rule, and a callback function. The callback calls the `it` function with a string describing the test case, and a test function. The test function will return successfully if the test passes, and throw an error if the test fails. (Note that this is the standard behavior for test suites when using frameworks like [Mocha](https://mochajs.org/); this information is only relevant if you plan to customize `RuleTester.it` and `RuleTester.describe`.)
+
+Example of customizing `RuleTester`:
+
+```js
+"use strict";
+
+const RuleTester = require("eslint").RuleTester;
+const test = require("my-test-runner");
+const myRule = require("../../../lib/rules/my-rule");
+
+RuleTester.describe = function(text, method) {
+    RuleTester.it.title = text;
+    return method.call(this);
+};
+
+RuleTester.it = function(text, method) {
+    test(RuleTester.it.title + ": " + text, method);
+};
+
+// then use RuleTester as documented
+
+const ruleTester = new RuleTester();
+
+ruleTester.run("my-rule", myRule, {
+    valid: [
+        // valid test cases
+    ],
+    invalid: [
+        // invalid test cases
+    ]
+})
+```
+
 ## Deprecated APIs
 
 * `cli` - the `cli` object has been deprecated in favor of `CLIEngine`. As of v1.0.0, `cli` is no longer exported and should not be used by external tools.
diff --git a/docs/developer-guide/working-with-plugins.md b/docs/developer-guide/working-with-plugins.md
index 04274f21e..5cf63b7ee 100644
--- a/docs/developer-guide/working-with-plugins.md
+++ b/docs/developer-guide/working-with-plugins.md
@@ -72,15 +72,45 @@ processors: {
 
             // you need to return a one-dimensional array of the messages you want to keep
             return [Message];
-        }
+        },
+
+        supportsAutofix: true // (optional, defaults to false)
     }
 }
 ```
 
 The `preprocess` method takes the file contents and filename as arguments, and returns an array of strings to lint. The strings will be linted separately but still be registered to the filename. It's up to the plugin to decide if it needs to return just one part, or multiple pieces. For example in the case of processing `.html` files, you might want to return just one item in the array by combining all scripts, but for `.md` file where each JavaScript block might be independent, you can return multiple items.
 
-The `postprocess` method takes a two-dimensional array of arrays of lint messages and the filename. Each item in the input
-array corresponds to the part that was returned from the `preprocess` method. The `postprocess` method must adjust the location of all errors and aggregate them into a single flat array and return it.
+The `postprocess` method takes a two-dimensional array of arrays of lint messages and the filename. Each item in the input array corresponds to the part that was returned from the `preprocess` method. The `postprocess` method must adjust the locations of all errors to correspond to locations in the original, unprocessed code, and aggregate them into a single flat array and return it.
+
+Reported problems have the following location information:
+
+```typescript
+{
+    line: number,
+    column: number,
+
+    endLine?: number,
+    endColumn?: number
+}
+```
+
+By default, ESLint will not perform autofixes when a processor is used, even when the `--fix` flag is enabled on the command line. To allow ESLint to autofix code when using your processor, you should take the following additional steps:
+
+1. Update the `postprocess` method to additionally transform the `fix` property of reported problems. All autofixable problems will have a `fix` property, which is an object with the following schema:
+
+    ```js
+    {
+        range: [number, number],
+        text: string
+    }
+    ```
+
+    The `range` property contains two indexes in the code, referring to the start and end location of a contiguous section of text that will be replaced. The `text` property refers to the text that will replace the given range.
+
+    In the initial list of problems, the `fix` property will refer refer to a fix in the processed JavaScript. The `postprocess` method should transform the object to refer to a fix in the original, unprocessed file.
+
+2. Add a `supportsAutofix: true` property to the processor.
 
 You can have both rules and processors in a single plugin. You can also have multiple processors in one plugin.
 To support multiple extensions, add each one to the `processors` element and point them to the same object.
@@ -119,90 +149,7 @@ The plugin support was introduced in ESLint version `0.8.0`. Ensure the `peerDep
 
 ### Testing
 
-You can test the rules of your plugin [the same way as bundled ESLint rules](working-with-rules.md#rule-unit-tests) using RuleTester.
-
-Example:
-
-```js
-"use strict";
-
-var rule = require("../../../lib/rules/custom-plugin-rule"),
-    RuleTester = require("eslint").RuleTester;
-
-var ruleTester = new RuleTester();
-ruleTester.run("custom-plugin-rule", rule, {
-    valid: [
-        "var validVariable = true",
-    ],
-
-    invalid: [
-        {
-            code: "var invalidVariable = true",
-            errors: [ { message: "Unexpected invalid variable." } ]
-        },
-        {
-            code: "var invalidVariable = true",
-            errors: [ { message: /^Unexpected.+variable/ } ]
-        }
-    ]
-});
-```
-
-The `RuleTester` constructor accepts an optional object argument, which can be used to specify defaults for your test cases. For example, if all of your test cases use ES2015, you can set it as a default:
-
-```js
-const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2015 } });
-```
-
-The `RuleTester#run()` method is used to run the tests. It should be passed the following arguments:
-
-* The name of the rule (string)
-* The rule object itself (see ["working with rules"](./working-with-rules))
-* An object containing `valid` and `invalid` properties, each of which is an array containing test cases.
-
-A test case is an object with the following properties:
-
-* `code` (string, required): The source code that the rule should be run on
-* `options` (array, optional): The options passed to the rule. The rule severity should not be included in this list.
-* `filename` (string, optional): The filename for the given case (useful for rules that make assertions about filenames)
-
-In addition to the properties above, invalid test cases can also have the following properties:
-
-* `errors` (number or array, required): Asserts some properties of the errors that the rule is expected to produce when run on this code. If this is a number, asserts the number of errors produced. Otherwise, this should be a list of objects, each containing information about a single reported error. The following properties can be used for an error (all are optional):
-    * `message` (string/regexp): The message for the error
-    * `type` (string): The type of the reported AST node
-    * `line` (number): The 1-based line number of the reported location
-    * `column` (number): The 0-based column number of the reported location
-    * `endLine` (number): The 1-based line number of the end of the reported location
-    * `endColumn` (number): The 0-based column number of the end of the reported location
-* `output` (string, optional): Asserts the output that will be produced when using this rule for a single pass of autofixing (e.g. with the `--fix` command line flag). If this is `null`, asserts that none of the reported problems suggest autofixes.
-
-Any additional properties of a test case will be passed directly to the linter as config options. For example, a test case can have a `parserOptions` property to configure parser behavior.
-
-#### Customizing RuleTester
-
-To create tests for each valid and invalid case, `RuleTester` internally uses `describe` and `it` methods from the Mocha test framework when it is available. If you use another test framework, you can override `RuleTester.describe` and `RuleTester.it` to make `RuleTester` compatible with it and have proper individual tests and feedback.
-
-Example:
-
-```js
-"use strict";
-
-var RuleTester = require("eslint").RuleTester;
-var test = require("my-test-runner");
-
-RuleTester.describe = function(text, method) {
-    RuleTester.it.title = text;
-    return method.apply(this);
-};
-
-RuleTester.it = function(text, method) {
-    test(RuleTester.it.title + ": " + text, method);
-};
-
-// then use RuleTester as documented
-```
-
+ESLint provides the [`RuleTester`](/docs/developer-guide/nodejs-api#ruletester) utility to make it easy to test the rules of your plugin.
 
 ## Share Plugins
 
diff --git a/docs/developer-guide/working-with-rules.md b/docs/developer-guide/working-with-rules.md
index 0c35b19fb..66a87b3bd 100644
--- a/docs/developer-guide/working-with-rules.md
+++ b/docs/developer-guide/working-with-rules.md
@@ -106,19 +106,29 @@ The `context` object contains additional functionality that is helpful for rules
 
 * `parserOptions` - the parser options configured for this run (more details [here](../user-guide/configuring#specifying-parser-options)).
 * `id` - the rule ID.
-* `options` - an array of rule options.
-* `settings` - the `settings` from configuration.
-* `parserPath` - the full path to the `parser` from configuration.
+* `options` - an array of the [configured options](/docs/user-guide/configuring#configuring-rules) for this rule. This array does not include the rule severity. For more information, see [here](#contextoptions).
+* `settings` - the [shared settings](/docs/user-guide/configuring#adding-shared-settings) from configuration.
+* `parserPath` - the name of the `parser` from configuration.
+* `parserServices` - an object containing parser-provided services for rules. The default parser does not provide any services. However, if a rule is intended to be used with a custom parser, it could use `parserServices` to access anything provided by that parser. (For example, a TypeScript parser could provide the ability to get the computed type of a given node.)
 
 Additionally, the `context` object has the following methods:
 
-* `getAncestors()` - returns an array of ancestor nodes based on the current traversal.
-* `getDeclaredVariables(node)` - returns the declared variables on the given node.
+* `getAncestors()` - returns an array of the ancestors of the currently-traversed node, starting at the root of the AST and continuing through the direct parent of the current node. This array does not include the currently-traversed node itself.
+* `getDeclaredVariables(node)` - returns a list of [variables](https://estools.github.io/escope/Variable.html) declared by the given node. This information can be used to track references to variables.
+    * If the node is a `VariableDeclaration`, all variables declared in the declaration are returned.
+    * If the node is a `VariableDeclarator`, all variables declared in the declarator are returned.
+    * If the node is a `FunctionDeclaration` or `FunctionExpression`, the variable for the function name is returned,in addition to variables for the function parameters.
+    * If the node is an `ArrowFunctionExpression`, variables for the parameters are returned.
+    * If the node is a `ClassDeclaration` or a `ClassExpression`, the variable for the class name is returned.
+    * If the node is a `CatchClause`, the variable for the exception is returned.
+    * If the node is an `ImportDeclaration`, variables for all of its specifiers are returned.
+    * If the node is an `ImportSpecifier`, `ImportDefaultSpecifier`, or `ImportNamespaceSpecifier`, the declared variable is returned.
+    * Otherwise, if the node does not declare any variables, an empty array is returned.
 * `getFilename()` - returns the filename associated with the source.
-* `getScope()` - returns the current scope.
-* `getSourceCode()` - returns a `SourceCode` object that you can use to work with the source that was passed to ESLint
-* `markVariableAsUsed(name)` - marks the named variable in scope as used. This affects the [no-unused-vars](../rules/no-unused-vars.md) rule.
-* `report(descriptor)` - reports a problem in the code.
+* `getScope()` - returns the [scope](https://estools.github.io/escope/Scope.html) of the currently-traversed node. This information can be used track references to variables.
+* `getSourceCode()` - returns a [`SourceCode`](#contextgetsourcecode) object that you can use to work with the source that was passed to ESLint.
+* `markVariableAsUsed(name)` - marks a variable with the given name in the current scope as used. This affects the [no-unused-vars](../rules/no-unused-vars.md) rule. Returns `true` if a variable with the given name was found and marked as used, otherwise `false`.
+* `report(descriptor)` - reports a problem in the code (see the [dedicated section](#contextreport)).
 
 **Note:** Earlier versions of ESLint supported additional methods on the `context` object. Those methods were removed in the new format and should not be relied upon.
 
@@ -135,8 +145,8 @@ The main method you'll use is `context.report()`, which publishes a warning or e
     * `end` - An object of the end location.
         * `line` - the 1-based line number at which the problem occurred.
         * `column` - the 0-based column number at which the problem occurred.
-* `data` - (optional) placeholder data for `message`.
-* `fix` - (optional) a function that applies a fix to resolve the problem.
+* `data` - (optional) [placeholder](#using-message-placeholders) data for `message`.
+* `fix` - (optional) a function that applies a [fix](#applying-fixes) to resolve the problem.
 
 Note that at least one of `node` or `loc` is required.
 
@@ -151,6 +161,8 @@ context.report({
 
 The node contains all of the information necessary to figure out the line and column number of the offending text as well the source text representing the node.
 
+### Using message placeholders
+
 You can also use placeholders in the message and provide `data`:
 
 ```js
@@ -252,7 +264,7 @@ module.exports = {
 
 Since `context.options` is just an array, you can use it to determine how many options have been passed as well as retrieving the actual options themselves. Keep in mind that the error level is not part of `context.options`, as the error level cannot be known or modified from inside a rule.
 
-When using options, make sure that your rule has some logic defaults in case the options are not provided.
+When using options, make sure that your rule has some logical defaults in case the options are not provided.
 
 ### context.getSourceCode()
 
@@ -415,267 +427,9 @@ You can access that code path objects with five events related to code paths.
 
 ## Rule Unit Tests
 
-Each rule must have a set of unit tests submitted with it to be accepted. The test file is named the same as the source file but lives in `tests/lib/`. For example, if your rule source file is `lib/rules/foo.js` then your test file should be `tests/lib/rules/foo.js`.
-
-For your rule, be sure to test:
-
-1. All instances that should be flagged as warnings.
-1. At least one pattern that should **not** be flagged as a warning.
-
-The basic pattern for a rule unit test file is:
-
-```js
-/**
- * @fileoverview Tests for no-with rule.
- * @author Nicholas C. Zakas
- */
-
-"use strict";
-
-//------------------------------------------------------------------------------
-// Requirements
-//------------------------------------------------------------------------------
-
-var rule = require("../../../lib/rules/no-with"),
-    RuleTester = require("../../../lib/testers/rule-tester");
-
-//------------------------------------------------------------------------------
-// Tests
-//------------------------------------------------------------------------------
-
-var ruleTester = new RuleTester();
-ruleTester.run("no-with", rule, {
-    valid: [
-        "foo.bar()"
-    ],
-    invalid: [
-        {
-            code: "with(foo) { bar() }",
-            errors: [{ message: "Unexpected use of 'with' statement.", type: "WithStatement"}]
-        }
-    ]
-});
-```
-
-Be sure to replace the value of `"no-with"` with your rule's ID. There are plenty of examples in the `tests/lib/rules/` directory.
-
-### Valid Code
-
-Each valid case can be either a string or an object. The object form is used when you need to specify additional global variables or arguments for the rule. For example, the following defines `window` as a global variable for code that should not trigger the rule being tested:
-
-```js
-valid: [
-    {
-        code: "window.alert()",
-        globals: [ "window" ]
-    }
-]
-```
-
-You can also pass options to the rule (if it accepts them). These arguments are equivalent to how people can configure rules in their `.eslintrc` file. For example:
-
-```js
-valid: [
-    {
-        code: "var msg = 'Hello';",
-        options: [ "single" ]
-    }
-]
-```
-
-The `options` property must be an array of options. This gets passed through to `context.options` in the rule.
-
-### Invalid Code
-
-Each invalid case must be an object containing the code to test and at least one message that is produced by the rule. The `errors` key specifies an array of objects, each containing a message (your rule may trigger multiple messages for the same code). You should also specify the type of AST node you expect to receive back using the `type` key. The AST node should represent the actual spot in the code where there is a problem. For example:
-
-```js
-invalid: [
-    {
-        code: "function doSomething() { var f; if (true) { var build = true; } f = build; }",
-        errors: [
-            { message: "build used outside of binding context.", type: "Identifier" }
-        ]
-    }
-]
-```
-
-In this case, the message is specific to the variable being used and the AST node type is `Identifier`.
-
-You can also check that the rule returns the correct line and column numbers for the message by adding `line` and `column` properties as needed (both are optional, but highly recommend):
-
-```js
-invalid: [
-    {
-        code: "function doSomething() { var f; if (true) { var build = true; } f = build; }",
-        errors: [
-            {
-                message: "build used outside of binding context.",
-                type: "Identifier",
-                line: 1,
-                column: 68
-            }
-        ]
-    }
-]
-```
-
-The test fails if the line or column reported by the rule doesn't match the options specified in the test.
-
-Similar to the valid cases, you can also specify `options` to be passed to the rule:
-
-```js
-invalid: [
-    {
-        code: "function doSomething() { var f; if (true) { var build = true; } f = build; }",
-        options: [ "double" ],
-        errors: [
-            { message: "build used outside of binding context.", type: "Identifier" }
-        ]
-    }
-]
-```
-
-For simpler cases where the only thing that really matters is the error message, you can also specify any `errors` as strings. You can also have some strings and some objects, if you like.
-
-```js
-invalid: [
-    {
-        code: "'single quotes'",
-        options: ["double"],
-        errors: ["Strings must use doublequote."]
-    }
-]
-```
-
-### Specifying Globals
-
-If your rule relies on globals to be specified, you can provide global variable declarations by using the `globals` property. For example:
-
-```js
-valid: [
-    {
-        code: "for (x of a) doSomething();",
-        globals: { window: true }
-    }
-]
-```
-
-The same works on invalid tests:
-
-```js
-invalid: [
-    {
-        code: "'single quotes'",
-        globals: { window: true },
-        errors: ["Strings must use doublequote."]
-    }
-]
-```
-
-### Specifying Settings
-
-If your rule relies on `context.settings` to be specified, you can provide those settings by using the `settings` property. For example:
-
-```js
-valid: [
-    {
-        code: "for (x of a) doSomething();",
-        settings: { message: "hi" }
-    }
-]
-```
-
-The same works on invalid tests:
-
-```js
-invalid: [
-    {
-        code: "'single quotes'",
-        settings: { message: "hi" },
-        errors: ["Strings must use doublequote."]
-    }
-]
-```
-
-You can then access `context.settings.message` inside of the rule.
-
-### Specifying Filename
-
-If your rule relies on `context.getFilename()` to be specified, you can provide the filename by using the `filename` property. For example:
-
-```js
-valid: [
-    {
-        code: "for (x of a) doSomething();",
-        filename: "foo/bar.js"
-    }
-]
-```
-
-The same works on invalid tests:
-
-```js
-invalid: [
-    {
-        code: "'single quotes'",
-        filename: "foo/bar.js",
-        errors: ["Strings must use doublequote."]
-    }
-]
-```
-
-You can then access `context.getFilename()` inside of the rule.
-
-### Specifying Parser and Parser Options
-
-Some tests require that a certain parser configuration must be used. This can be specified in test specifications via the `parser` and `parserOptions` properties. While the following examples show usage in `valid` tests, you can use the same options in `invalid` tests as well.
-
-For example, to set `ecmaVersion` to 6 (in order to use constructs like `for-of`):
-
-```js
-valid: [
-    {
-        code: "for (x of a) doSomething();",
-        parserOptions: { ecmaVersion: 6 }
-    }
-]
-```
-
-If you are working with ES6 modules:
-
-```js
-valid: [
-    {
-        code: "export default function () {};",
-        parserOptions: { ecmaVersion: 6, sourceType: "module" }
-    }
-]
-```
-
-For non-version specific features such as JSX:
-
-```js
-valid: [
-    {
-        code: "var foo = <div>{bar}</div>",
-        parserOptions: { ecmaFeatures: { jsx: true } }
-    }
-]
-```
-
-To use a different parser:
-
-```js
-valid: [
-    {
-        code: "var foo = <div>{bar}</div>",
-        parser: "my-custom-parser"
-    }
-]
-```
+Each bundled rule for ESLint core must have a set of unit tests submitted with it to be accepted. The test file is named the same as the source file but lives in `tests/lib/`. For example, if the rule source file is `lib/rules/foo.js` then the test file should be `tests/lib/rules/foo.js`.
 
-The options available and the expected syntax for `parserOptions` is the same as those used in [configuration](../user-guide/configuring#specifying-parser-options).
+ESLint provides the [`RuleTester`](/docs/developer-guide/nodejs-api#ruletester) utility to make it easy to write tests for rules.
 
 ## Performance Testing
 
@@ -683,7 +437,7 @@ To keep the linting process efficient and unobtrusive, it is useful to verify th
 
 ### Overall Performance
 
-The `npm run perf` command gives a high-level overview of ESLint running time with default rules (`eslint:recommended`) enabled.
+When developing in the ESLint core repository, the `npm run perf` command gives a high-level overview of ESLint running time with all core rules enabled.
 
 ```bash
 $ git checkout master
diff --git a/docs/rules/array-bracket-newline.md b/docs/rules/array-bracket-newline.md
index b4fd7cb63..0b381f3c9 100644
--- a/docs/rules/array-bracket-newline.md
+++ b/docs/rules/array-bracket-newline.md
@@ -10,8 +10,8 @@ This rule enforces line breaks after opening and before closing array brackets.
 
 This rule has either a string option:
 
-* `"always"` requires line breaks inside braces
-* `"never"` disallows line breaks inside braces
+* `"always"` requires line breaks inside brackets
+* `"never"` disallows line breaks inside brackets
 
 Or an object option (Requires line breaks if any of properties is satisfied. Otherwise, disallows line breaks):
 
diff --git a/docs/rules/id-length.md b/docs/rules/id-length.md
index b63a88b08..a28407fec 100644
--- a/docs/rules/id-length.md
+++ b/docs/rules/id-length.md
@@ -192,7 +192,7 @@ data["y"] = 3;  // excused because of calculated property access
 Examples of **incorrect** code for this rule with the `{ "max": 10 }` option:
 
 ```js
-/*eslint id-length: ["error", { "max": "10" }]*/
+/*eslint id-length: ["error", { "max": 10 }]*/
 /*eslint-env es6*/
 
 var reallyLongVarName = 5;
@@ -210,7 +210,7 @@ try {
 Examples of **correct** code for this rule with the `{ "max": 10 }` option:
 
 ```js
-/*eslint id-length: ["error", { "max": "10" }]*/
+/*eslint id-length: ["error", { "max": 10 }]*/
 /*eslint-env es6*/
 
 var varName = 5;
diff --git a/docs/rules/no-restricted-globals.md b/docs/rules/no-restricted-globals.md
index c9df9d339..e3d612412 100644
--- a/docs/rules/no-restricted-globals.md
+++ b/docs/rules/no-restricted-globals.md
@@ -77,7 +77,7 @@ Examples of **incorrect** code for a sample `"event"` global variable name, alon
 
 ```js
 /*global event*/
-/* eslint no-restricted-globals: [{ name: "error", message: "Use local parameter instead." }] */
+/* eslint no-restricted-globals: ["error", { name: "error", message: "Use local parameter instead." }] */
 
 function onClick() {
     console.log(event);    // Unexpected global variable 'event'. Use local parameter instead.
diff --git a/docs/user-guide/command-line-interface.md b/docs/user-guide/command-line-interface.md
index 8e8b6206e..fb0152967 100644
--- a/docs/user-guide/command-line-interface.md
+++ b/docs/user-guide/command-line-interface.md
@@ -359,7 +359,7 @@ The resulting configuration file will be created in the current directory.
 This option instructs ESLint to try to fix as many issues as possible. The fixes are made to the actual files themselves and only the remaining unfixed issues are output. Not all problems are fixable using this option, and the option does not work in these situations:
 
 1. This option throws an error when code is piped to ESLint.
-1. This option has no effect on code that uses processors.
+1. This option has no effect on code that uses a processor, unless the processor opts into allowing autofixes.
 
 #### `--debug`
 
diff --git a/lib/ast-utils.js b/lib/ast-utils.js
index e65284ff7..47cc71990 100644
--- a/lib/ast-utils.js
+++ b/lib/ast-utils.js
@@ -626,6 +626,9 @@ module.exports = {
                 //     // setup...
                 //     return function foo() { ... };
                 //   })();
+                //   obj.foo = (() =>
+                //     function foo() { ... }
+                //   )();
                 case "ReturnStatement": {
                     const func = getUpperFunction(parent);
 
@@ -635,6 +638,12 @@ module.exports = {
                     node = func.parent;
                     break;
                 }
+                case "ArrowFunctionExpression":
+                    if (node !== parent.body || !isCallee(parent)) {
+                        return true;
+                    }
+                    node = parent.parent;
+                    break;
 
                 // e.g.
                 //   var obj = { foo() { ... } };
diff --git a/lib/cli-engine.js b/lib/cli-engine.js
index 1abc1fd2c..054048038 100644
--- a/lib/cli-engine.js
+++ b/lib/cli-engine.js
@@ -142,15 +142,9 @@ function calculateStatsPerRun(results) {
  * @private
  */
 function processText(text, configHelper, filename, fix, allowInlineConfig, linter) {
-
-    // clear all existing settings for a new file
-    linter.reset();
-
     let filePath,
-        messages,
         fileExtension,
-        processor,
-        fixedResult;
+        processor;
 
     if (filename) {
         filePath = path.resolve(filename);
@@ -174,51 +168,28 @@ function processText(text, configHelper, filename, fix, allowInlineConfig, linte
         }
     }
 
-    if (processor) {
-        debug("Using processor");
-        const parsedBlocks = processor.preprocess(text, filename);
-        const unprocessedMessages = [];
-
-        parsedBlocks.forEach(block => {
-            unprocessedMessages.push(linter.verify(block, config, {
-                filename,
-                allowInlineConfig
-            }));
-        });
-
-        // TODO(nzakas): Figure out how fixes might work for processors
-
-        messages = processor.postprocess(unprocessedMessages, filename);
-
-    } else {
+    const autofixingEnabled = typeof fix !== "undefined" && (!processor || processor.supportsAutofix);
 
-        if (fix) {
-            fixedResult = linter.verifyAndFix(text, config, {
-                filename,
-                allowInlineConfig,
-                fix
-            });
-            messages = fixedResult.messages;
-        } else {
-            messages = linter.verify(text, config, {
-                filename,
-                allowInlineConfig
-            });
-        }
-    }
+    const fixedResult = linter.verifyAndFix(text, config, {
+        filename,
+        allowInlineConfig,
+        fix: !!autofixingEnabled && fix,
+        preprocess: processor && (rawText => processor.preprocess(rawText, filename)),
+        postprocess: processor && (problemLists => processor.postprocess(problemLists, filename))
+    });
 
-    const stats = calculateStatsPerFile(messages);
+    const stats = calculateStatsPerFile(fixedResult.messages);
 
     const result = {
         filePath: filename,
-        messages,
+        messages: fixedResult.messages,
         errorCount: stats.errorCount,
         warningCount: stats.warningCount,
         fixableErrorCount: stats.fixableErrorCount,
         fixableWarningCount: stats.fixableWarningCount
     };
 
-    if (fixedResult && fixedResult.fixed) {
+    if (fixedResult.fixed) {
         result.output = fixedResult.output;
     }
 
@@ -372,6 +343,8 @@ function getCacheFile(cacheFile, cwd) {
     return resolvedCacheFile;
 }
 
+const configHashCache = new WeakMap();
+
 //------------------------------------------------------------------------------
 // Public Interface
 //------------------------------------------------------------------------------
@@ -421,9 +394,13 @@ class CLIEngine {
             });
         }
 
-        Object.keys(this.options.rules || {}).forEach(name => {
-            validator.validateRuleOptions(name, this.options.rules[name], "CLI", this.linter.rules);
-        });
+        if (this.options.rules && Object.keys(this.options.rules).length) {
+            const loadedRules = this.linter.getRules();
+
+            Object.keys(this.options.rules).filter(ruleId => loadedRules.has(ruleId)).forEach(name => {
+                validator.validateRuleOptions(loadedRules.get(name), name, this.options.rules[name], "CLI");
+            });
+        }
 
         this.config = new Config(this.options, this.linter);
     }
@@ -493,11 +470,9 @@ class CLIEngine {
      * @returns {Object} The results for all files that were linted.
      */
     executeOnFiles(patterns) {
-        const results = [],
-            options = this.options,
+        const options = this.options,
             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)) {
@@ -512,42 +487,18 @@ class CLIEngine {
         function hashOfConfigFor(filename) {
             const config = configHelper.getConfig(filename);
 
-            if (!prevConfig) {
-                prevConfig = {};
+            if (!configHashCache.has(config)) {
+                configHashCache.set(config, hash(`${pkg.version}_${stringify(config)}`));
             }
 
-            // reuse the previously hashed config if the config hasn't changed
-            if (prevConfig.config !== config) {
-
-                /*
-                 * config changed so we need to calculate the hash of the config
-                 * and the hash of the plugins being used
-                 */
-                prevConfig.config = config;
-
-                const eslintVersion = pkg.version;
-
-                prevConfig.hash = hash(`${eslintVersion}_${stringify(config)}`);
-            }
-
-            return prevConfig.hash;
+            return configHashCache.get(config);
         }
 
-        /**
-         * Executes the linter on a file defined by the `filename`. Skips
-         * unsupported file extensions and any files that are already linted.
-         * @param {string} filename The resolved filename of the file to be linted
-         * @param {boolean} warnIgnored always warn when a file is ignored
-         * @param {Linter} linter Linter context
-         * @returns {void}
-         */
-        function executeOnFile(filename, warnIgnored, linter) {
-            let hashOfConfig,
-                descriptor;
-
-            if (warnIgnored) {
-                results.push(createIgnoreResult(filename, options.cwd));
-                return;
+        const startTime = Date.now();
+        const fileList = globUtil.listFilesToProcess(this.resolveFileGlobPatterns(patterns), options);
+        const results = fileList.map(fileInfo => {
+            if (fileInfo.ignored) {
+                return createIgnoreResult(fileInfo.filename, options.cwd);
             }
 
             if (options.cache) {
@@ -557,15 +508,12 @@ class CLIEngine {
                  * with the metadata and the flag that determines if
                  * the file has changed
                  */
-                descriptor = fileCache.getFileDescriptor(filename);
-                const meta = descriptor.meta || {};
-
-                hashOfConfig = hashOfConfigFor(filename);
-
-                const changed = descriptor.changed || meta.hashOfConfig !== hashOfConfig;
+                const descriptor = fileCache.getFileDescriptor(fileInfo.filename);
+                const hashOfConfig = hashOfConfigFor(fileInfo.filename);
+                const changed = descriptor.changed || descriptor.meta.hashOfConfig !== hashOfConfig;
 
                 if (!changed) {
-                    debug(`Skipping file since hasn't changed: ${filename}`);
+                    debug(`Skipping file since hasn't changed: ${fileInfo.filename}`);
 
                     /*
                      * Add the the cached results (always will be 0 error and
@@ -573,63 +521,45 @@ class CLIEngine {
                      * failed, in order to guarantee that next execution will
                      * process those files as well.
                      */
-                    results.push(descriptor.meta.results);
-
-                    // move to the next file
-                    return;
+                    return descriptor.meta.results;
                 }
             }
 
-            debug(`Processing ${filename}`);
+            debug(`Processing ${fileInfo.filename}`);
 
-            const res = processFile(filename, configHelper, options, linter);
-
-            if (options.cache) {
+            return processFile(fileInfo.filename, configHelper, options, this.linter);
+        });
 
-                /*
-                 * if a file contains errors or warnings we don't want to
-                 * store the file in the cache so we can guarantee that
-                 * next execution will also operate on this file
-                 */
-                if (res.errorCount > 0 || res.warningCount > 0) {
-                    debug(`File has problems, skipping it: ${filename}`);
+        if (options.cache) {
+            results.forEach(result => {
+                if (result.messages.length) {
 
-                    // remove the entry from the cache
-                    fileCache.removeEntry(filename);
+                    /*
+                    * if a file contains errors or warnings we don't want to
+                    * store the file in the cache so we can guarantee that
+                    * next execution will also operate on this file
+                    */
+                    fileCache.removeEntry(result.filePath);
                 } else {
 
                     /*
                      * since the file passed we store the result here
-                     * TODO: check this as we might not need to store the
-                     * successful runs as it will always should be 0 errors and
-                     * 0 warnings.
+                     * TODO: it might not be necessary to store the results list in the cache,
+                     * since it should always be 0 errors/warnings
                      */
-                    descriptor.meta.hashOfConfig = hashOfConfig;
-                    descriptor.meta.results = res;
-                }
-            }
+                    const descriptor = fileCache.getFileDescriptor(result.filePath);
 
-            results.push(res);
-        }
-
-        const startTime = Date.now();
-
-
-        patterns = this.resolveFileGlobPatterns(patterns);
-        const fileList = globUtil.listFilesToProcess(patterns, options);
-
-        fileList.forEach(fileInfo => {
-            executeOnFile(fileInfo.filename, fileInfo.ignored, this.linter);
-        });
-
-        const stats = calculateStatsPerRun(results);
-
-        if (options.cache) {
+                    descriptor.meta.hashOfConfig = hashOfConfigFor(result.filePath);
+                    descriptor.meta.results = result;
+                }
+            });
 
             // persist the cache to disk
             fileCache.reconcile();
         }
 
+        const stats = calculateStatsPerRun(results);
+
         debug(`Linting complete in: ${Date.now() - startTime}ms`);
 
         return {
@@ -713,7 +643,6 @@ class CLIEngine {
      */
     getFormatter(format) {
 
-        let formatterPath;
 
         // default is stylish
         format = format || "stylish";
@@ -724,6 +653,8 @@ class CLIEngine {
             // replace \ with / for Windows compatibility
             format = format.replace(/\\/g, "/");
 
+            let formatterPath;
+
             // if there's a slash, then it's a file
             if (format.indexOf("/") > -1) {
                 const cwd = this.options ? this.options.cwd : process.cwd();
diff --git a/lib/code-path-analysis/code-path-state.js b/lib/code-path-analysis/code-path-state.js
index 7d9ee55ab..719a12443 100644
--- a/lib/code-path-analysis/code-path-state.js
+++ b/lib/code-path-analysis/code-path-state.js
@@ -843,15 +843,14 @@ class CodePathState {
          * This segment will leave at the end of this finally block.
          */
         const segments = forkContext.makeNext(-1, -1);
-        let j;
 
         for (let i = 0; i < forkContext.count; ++i) {
             const prevSegsOfLeavingSegment = [headOfLeavingSegments[i]];
 
-            for (j = 0; j < returned.segmentsList.length; ++j) {
+            for (let j = 0; j < returned.segmentsList.length; ++j) {
                 prevSegsOfLeavingSegment.push(returned.segmentsList[j][i]);
             }
-            for (j = 0; j < thrown.segmentsList.length; ++j) {
+            for (let j = 0; j < thrown.segmentsList.length; ++j) {
                 prevSegsOfLeavingSegment.push(thrown.segmentsList[j][i]);
             }
 
@@ -985,7 +984,6 @@ class CodePathState {
 
         const forkContext = this.forkContext;
         const brokenForkContext = this.popBreakContext().brokenForkContext;
-        let choiceContext;
 
         // Creates a looped path.
         switch (context.type) {
@@ -1000,7 +998,7 @@ class CodePathState {
                 break;
 
             case "DoWhileStatement": {
-                choiceContext = this.popChoiceContext();
+                const choiceContext = this.popChoiceContext();
 
                 if (!choiceContext.processed) {
                     choiceContext.trueForkContext.add(forkContext.head);
diff --git a/lib/code-path-analysis/code-path.js b/lib/code-path-analysis/code-path.js
index 5fc5d22b0..709a11118 100644
--- a/lib/code-path-analysis/code-path.js
+++ b/lib/code-path-analysis/code-path.js
@@ -206,7 +206,7 @@ class CodePath {
 
                 // Call the callback when the first time.
                 if (!skippedSegment) {
-                    callback.call(this, segment, controller); // eslint-disable-line callback-return
+                    callback.call(this, segment, controller);
                     if (segment === lastSegment) {
                         controller.skip();
                     }
diff --git a/lib/config/autoconfig.js b/lib/config/autoconfig.js
index 88204a7a4..6614a1bc4 100644
--- a/lib/config/autoconfig.js
+++ b/lib/config/autoconfig.js
@@ -269,10 +269,8 @@ class Registry {
      * @returns {Registry}              New registry with errorCount populated
      */
     lintSourceCode(sourceCodes, config, cb) {
-        let ruleSetIdx,
-            lintedRegistry;
+        let lintedRegistry = new Registry();
 
-        lintedRegistry = new Registry();
         lintedRegistry.rules = Object.assign({}, this.rules);
 
         const ruleSets = lintedRegistry.buildRuleSets();
@@ -287,7 +285,7 @@ class Registry {
         filenames.forEach(filename => {
             debug(`Linting file: ${filename}`);
 
-            ruleSetIdx = 0;
+            let ruleSetIdx = 0;
 
             ruleSets.forEach(ruleSet => {
                 const lintConfig = Object.assign({}, config, { rules: ruleSet });
diff --git a/lib/config/config-file.js b/lib/config/config-file.js
index 87412dd2a..e74fd4640 100644
--- a/lib/config/config-file.js
+++ b/lib/config/config-file.js
@@ -546,7 +546,7 @@ function loadFromDisk(resolvedPath, configContext) {
         }
 
         // validate the configuration before continuing
-        validator.validate(config, resolvedPath.configFullName, configContext.linterContext.rules, configContext.linterContext.environments);
+        validator.validate(config, resolvedPath.configFullName, configContext.linterContext.getRules(), configContext.linterContext.environments);
 
         /*
          * If an `extends` property is defined, it represents a configuration file to use as
diff --git a/lib/config/config-initializer.js b/lib/config/config-initializer.js
index 11d441029..47139e06e 100644
--- a/lib/config/config-initializer.js
+++ b/lib/config/config-initializer.js
@@ -382,7 +382,6 @@ function hasESLintVersionConflict(answers) {
  * @returns {Promise} The promise with the result of the prompt
  */
 function promptUser() {
-    let config;
 
     return inquirer.prompt([
         {
@@ -469,7 +468,8 @@ function promptUser() {
                 earlyAnswers.styleguide = "airbnb-base";
             }
 
-            config = getConfigForStyleGuide(earlyAnswers.styleguide, earlyAnswers.installESLint);
+            const config = getConfigForStyleGuide(earlyAnswers.styleguide, earlyAnswers.installESLint);
+
             writeFile(config, earlyAnswers.format);
 
             return void 0;
@@ -529,7 +529,8 @@ function promptUser() {
             if (earlyAnswers.source === "auto") {
                 const combinedAnswers = Object.assign({}, earlyAnswers, secondAnswers);
 
-                config = processAnswers(combinedAnswers);
+                const config = processAnswers(combinedAnswers);
+
                 installModules(config);
                 writeFile(config, earlyAnswers.format);
 
@@ -575,7 +576,8 @@ function promptUser() {
             ]).then(answers => {
                 const totalAnswers = Object.assign({}, earlyAnswers, secondAnswers, answers);
 
-                config = processAnswers(totalAnswers);
+                const config = processAnswers(totalAnswers);
+
                 installModules(config);
                 writeFile(config, answers.format);
             });
diff --git a/lib/config/config-validator.js b/lib/config/config-validator.js
index 22bc1efbd..c06354e77 100644
--- a/lib/config/config-validator.js
+++ b/lib/config/config-validator.js
@@ -14,9 +14,7 @@ const ajv = require("../util/ajv"),
     configSchema = require("../../conf/config-schema.js"),
     util = require("util");
 
-const validators = {
-    rules: Object.create(null)
-};
+const ruleValidators = new WeakMap();
 
 //------------------------------------------------------------------------------
 // Private
@@ -25,13 +23,11 @@ let validateSchema;
 
 /**
  * Gets a complete options schema for a rule.
- * @param {string} id The rule's unique name.
- * @param {Rules} rulesContext Rule context
+ * @param {{create: Function, schema: (Array|null)}} rule A new-style rule object
  * @returns {Object} JSON Schema for the rule's options.
  */
-function getRuleOptionsSchema(id, rulesContext) {
-    const rule = rulesContext.get(id),
-        schema = rule && rule.schema || rule && rule.meta && rule.meta.schema;
+function getRuleOptionsSchema(rule) {
+    const schema = rule.schema || rule.meta && rule.meta.schema;
 
     // Given a tuple of schemas, insert warning level at the beginning
     if (Array.isArray(schema)) {
@@ -72,19 +68,20 @@ function validateRuleSeverity(options) {
 
 /**
 * Validates the non-severity options passed to a rule, based on its schema.
-* @param {string} id The rule's unique name
+* @param {{create: Function}} rule The rule to validate
 * @param {array} localOptions The options for the rule, excluding severity
-* @param {Rules} rulesContext Rule context
 * @returns {void}
 */
-function validateRuleSchema(id, localOptions, rulesContext) {
-    const schema = getRuleOptionsSchema(id, rulesContext);
+function validateRuleSchema(rule, localOptions) {
+    if (!ruleValidators.has(rule)) {
+        const schema = getRuleOptionsSchema(rule);
 
-    if (!validators.rules[id] && schema) {
-        validators.rules[id] = ajv.compile(schema);
+        if (schema) {
+            ruleValidators.set(rule, ajv.compile(schema));
+        }
     }
 
-    const validateRule = validators.rules[id];
+    const validateRule = ruleValidators.get(rule);
 
     if (validateRule) {
         validateRule(localOptions);
@@ -96,21 +93,21 @@ function validateRuleSchema(id, localOptions, rulesContext) {
 
 /**
  * Validates a rule's options against its schema.
- * @param {string} id The rule's unique name.
+ * @param {{create: Function}} rule The rule that the config is being validated for
+ * @param {string} ruleId The rule's unique name.
  * @param {array|number} options The given options for the rule.
  * @param {string} source The name of the configuration source to report in any errors.
- * @param {Rules} rulesContext Rule context
  * @returns {void}
  */
-function validateRuleOptions(id, options, source, rulesContext) {
+function validateRuleOptions(rule, ruleId, options, source) {
     try {
         const severity = validateRuleSeverity(options);
 
         if (severity !== 0 && !(typeof severity === "string" && severity.toLowerCase() === "off")) {
-            validateRuleSchema(id, Array.isArray(options) ? options.slice(1) : [], rulesContext);
+            validateRuleSchema(rule, Array.isArray(options) ? options.slice(1) : []);
         }
     } catch (err) {
-        throw new Error(`${source}:\n\tConfiguration for rule "${id}" is invalid:\n${err.message}`);
+        throw new Error(`${source}:\n\tConfiguration for rule "${ruleId}" is invalid:\n${err.message}`);
     }
 }
 
@@ -141,16 +138,16 @@ function validateEnvironment(environment, source, envContext) {
  * Validates a rules config object
  * @param {Object} rulesConfig The rules config object to validate.
  * @param {string} source The name of the configuration source to report in any errors.
- * @param {Rules} rulesContext Rule context
+ * @param {Map<string, {create: Function}>} rulesMap A map from strings to loaded rules
  * @returns {void}
  */
-function validateRules(rulesConfig, source, rulesContext) {
+function validateRules(rulesConfig, source, rulesMap) {
     if (!rulesConfig) {
         return;
     }
 
-    Object.keys(rulesConfig).forEach(id => {
-        validateRuleOptions(id, rulesConfig[id], source, rulesContext);
+    Object.keys(rulesConfig).filter(id => rulesMap.has(id)).forEach(id => {
+        validateRuleOptions(rulesMap.get(id), id, rulesConfig[id], source);
     });
 }
 
@@ -221,13 +218,13 @@ function validateConfigSchema(config, source) {
  * Validates an entire config object.
  * @param {Object} config The config object to validate.
  * @param {string} source The name of the configuration source to report in any errors.
- * @param {Rules} rulesContext The rules context
+ * @param {Map<string, {create: Function}>} ruleMap A map from rule IDs to defined rules
  * @param {Environments} envContext The env context
  * @returns {void}
  */
-function validate(config, source, rulesContext, envContext) {
+function validate(config, source, ruleMap, envContext) {
     validateConfigSchema(config, source);
-    validateRules(config.rules, source, rulesContext);
+    validateRules(config.rules, source, ruleMap);
     validateEnvironment(config.env, source, envContext);
 }
 
diff --git a/lib/formatters/codeframe.js b/lib/formatters/codeframe.js
index ed50ae608..0b97a0d81 100644
--- a/lib/formatters/codeframe.js
+++ b/lib/formatters/codeframe.js
@@ -47,7 +47,7 @@ function formatFilePath(filePath, line, column) {
  */
 function formatMessage(message, parentResult) {
     const type = (message.fatal || message.severity === 2) ? chalk.red("error") : chalk.yellow("warning");
-    const msg = `${chalk.bold(message.message.replace(/\.$/, ""))}`;
+    const msg = `${chalk.bold(message.message.replace(/([^ ])\.$/, "$1"))}`;
     const ruleId = message.fatal ? "" : chalk.dim(`(${message.ruleId})`);
     const filePath = formatFilePath(parentResult.filePath, message.line, message.column);
     const sourceCode = parentResult.output ? parentResult.output : parentResult.source;
diff --git a/lib/formatters/stylish.js b/lib/formatters/stylish.js
index 023ebc1dc..e586fe857 100644
--- a/lib/formatters/stylish.js
+++ b/lib/formatters/stylish.js
@@ -65,7 +65,7 @@ module.exports = function(results) {
                     message.line || 0,
                     message.column || 0,
                     messageType,
-                    message.message.replace(/\.$/, ""),
+                    message.message.replace(/([^ ])\.$/, "$1"),
                     chalk.dim(message.ruleId || "")
                 ];
             }),
diff --git a/lib/linter.js b/lib/linter.js
index a9bf2bf25..dedd7e1f3 100755
--- a/lib/linter.js
+++ b/lib/linter.js
@@ -12,6 +12,7 @@
 const EventEmitter = require("events").EventEmitter,
     eslintScope = require("eslint-scope"),
     levn = require("levn"),
+    lodash = require("lodash"),
     blankScriptAST = require("../conf/blank-script.json"),
     defaultConfig = require("../conf/default-config-options.js"),
     CodePathAnalyzer = require("./code-path-analysis/code-path-analyzer"),
@@ -276,7 +277,7 @@ function createDisableDirectives(type, loc, value) {
  * @param {string} filename The file being checked.
  * @param {ASTNode} ast The top node of the AST.
  * @param {Object} config The existing configuration data.
- * @param {Linter} linterContext Linter context object
+ * @param {Map<string, {create: Function}>} ruleMap A map from rule IDs to defined rules
  * @returns {{
  *      config: Object,
  *      problems: Problem[],
@@ -289,9 +290,9 @@ function createDisableDirectives(type, loc, value) {
  * }} Modified config object, along with any problems encountered
  * while parsing config comments
  */
-function modifyConfigsFromComments(filename, ast, config, linterContext) {
+function modifyConfigsFromComments(filename, ast, config, ruleMap) {
 
-    let commentConfig = {
+    const commentConfig = {
         exported: {},
         astGlobals: {},
         rules: {},
@@ -339,7 +340,9 @@ function modifyConfigsFromComments(filename, ast, config, linterContext) {
                             Object.keys(parseResult.config).forEach(name => {
                                 const ruleValue = parseResult.config[name];
 
-                                validator.validateRuleOptions(name, ruleValue, `${filename} line ${comment.loc.start.line}`, linterContext.rules);
+                                if (ruleMap.has(name)) {
+                                    validator.validateRuleOptions(ruleMap.get(name), name, ruleValue, `${filename} line ${comment.loc.start.line}`);
+                                }
                                 commentRules[name] = ruleValue;
                             });
                         } else {
@@ -361,14 +364,6 @@ function modifyConfigsFromComments(filename, ast, config, linterContext) {
         }
     });
 
-    // apply environment configs
-    Object.keys(commentConfig.env).forEach(name => {
-        const env = linterContext.environments.get(name);
-
-        if (env) {
-            commentConfig = ConfigOps.merge(commentConfig, env);
-        }
-    });
     Object.assign(commentConfig.rules, commentRules);
 
     return {
@@ -594,6 +589,71 @@ function parse(text, providedParserOptions, parserName, filePath) {
     }
 }
 
+/**
+ * Gets the scope for the current node
+ * @param {ScopeManager} scopeManager The scope manager for this AST
+ * @param {ASTNode} currentNode The node to get the scope of
+ * @param {number} ecmaVersion The `ecmaVersion` setting that this code was parsed with
+ * @returns {eslint-scope.Scope} The scope information for this node
+ */
+function getScope(scopeManager, currentNode, ecmaVersion) {
+    let initialNode;
+
+    // if current node introduces a scope, add it to the list
+    if (
+        ["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"].indexOf(currentNode.type) >= 0 ||
+        ecmaVersion >= 6 && ["BlockStatement", "SwitchStatement", "CatchClause"].indexOf(currentNode.type) >= 0
+    ) {
+        initialNode = currentNode;
+    } else {
+        initialNode = currentNode.parent;
+    }
+
+    // Ascend the current node's parents
+    for (let node = initialNode; node; node = node.parent) {
+
+        // Get the innermost scope
+        const scope = scopeManager.acquire(node, true);
+
+        if (scope) {
+            if (scope.type === "function-expression-name") {
+                return scope.childScopes[0];
+            }
+            return scope;
+        }
+    }
+
+    return scopeManager.scopes[0];
+}
+
+/**
+ * Marks a variable as used in the current scope
+ * @param {ScopeManager} scopeManager The scope manager for this AST. The scope may be mutated by this function.
+ * @param {ASTNode} currentNode The node currently being traversed
+ * @param {Object} parserOptions The options used to parse this text
+ * @param {string} name The name of the variable that should be marked as used.
+ * @returns {boolean} True if the variable was found and marked as used, false if not.
+ */
+function markVariableAsUsed(scopeManager, currentNode, parserOptions, name) {
+    const hasGlobalReturn = parserOptions.ecmaFeatures && parserOptions.ecmaFeatures.globalReturn;
+    const specialScope = hasGlobalReturn || parserOptions.sourceType === "module";
+    const currentScope = getScope(scopeManager, currentNode, parserOptions.ecmaVersion);
+
+    // Special Node.js scope means we need to start one level deeper
+    const initialScope = currentScope.type === "global" && specialScope ? currentScope.childScopes[0] : currentScope;
+
+    for (let scope = initialScope; scope; scope = scope.upper) {
+        const variable = scope.variables.find(scopeVar => scopeVar.name === name);
+
+        if (variable) {
+            variable.eslintUsed = true;
+            return true;
+        }
+    }
+
+    return false;
+}
+
 // methods that exist on SourceCode object
 const DEPRECATED_SOURCECODE_PASSTHROUGHS = {
     getSource: "getText",
@@ -640,12 +700,9 @@ const BASE_TRAVERSAL_CONTEXT = Object.freeze(
  * Object that is responsible for verifying JavaScript text
  * @name eslint
  */
-class Linter {
+module.exports = class Linter {
 
     constructor() {
-        this.currentConfig = null;
-        this.scopeManager = null;
-        this.traverser = null;
         this.sourceCode = null;
         this.version = pkg.version;
 
@@ -653,17 +710,6 @@ class Linter {
         this.environments = new Environments();
     }
 
-    /**
-     * Resets the internal state of the object.
-     * @returns {void}
-     */
-    reset() {
-        this.currentConfig = null;
-        this.scopeManager = null;
-        this.traverser = null;
-        this.sourceCode = null;
-    }
-
     /**
      * Configuration object for the `verify` API. A JS representation of the eslintrc files.
      * @typedef {Object} ESLintConfig
@@ -676,46 +722,38 @@ class Linter {
      */
 
     /**
-     * Verifies the text against the rules specified by the second argument.
+     * Same as linter.verify, except without support for processors.
      * @param {string|SourceCode} textOrSourceCode The text to parse or a SourceCode object.
      * @param {ESLintConfig} config An ESLintConfig instance to configure everything.
      * @param {(string|Object)} [filenameOrOptions] The optional filename of the file being checked.
      *      If this is not set, the filename will default to '<input>' in the rule context. If
      *      an object, then it has "filename", "saveState", and "allowInlineConfig" properties.
-     * @param {boolean} [saveState] Indicates if the state from the last run should be saved.
-     *      Mostly useful for testing purposes.
      * @param {boolean} [filenameOrOptions.allowInlineConfig] Allow/disallow inline comments' ability to change config once it is set. Defaults to true if not supplied.
      *      Useful if you want to validate JS without comments overriding rules.
      * @returns {Object[]} The results as an array of messages or null if no messages.
      */
-    verify(textOrSourceCode, config, filenameOrOptions, saveState) {
+    _verifyWithoutProcessors(textOrSourceCode, config, filenameOrOptions) {
         let text,
             parserServices,
-            allowInlineConfig;
-
-        if (typeof textOrSourceCode === "string") {
-            this.sourceCode = null;
-            text = textOrSourceCode;
-        } else {
-            this.sourceCode = textOrSourceCode;
-            text = this.sourceCode.text;
-        }
-
-        let providedFilename;
+            allowInlineConfig,
+            providedFilename;
 
         // evaluate arguments
         if (typeof filenameOrOptions === "object") {
             providedFilename = filenameOrOptions.filename;
             allowInlineConfig = filenameOrOptions.allowInlineConfig;
-            saveState = filenameOrOptions.saveState;
         } else {
             providedFilename = filenameOrOptions;
         }
 
         const filename = typeof providedFilename === "string" ? providedFilename : "<input>";
 
-        if (!saveState) {
-            this.reset();
+        if (typeof textOrSourceCode === "string") {
+            this.sourceCode = null;
+            text = textOrSourceCode;
+        } else {
+            this.sourceCode = textOrSourceCode;
+            text = this.sourceCode.text;
         }
 
         // search and apply "eslint-env *".
@@ -765,7 +803,7 @@ class Linter {
 
         // parse global comments and modify config
         if (allowInlineConfig !== false) {
-            const modifyConfigResult = modifyConfigsFromComments(filename, sourceCode.ast, config, this);
+            const modifyConfigResult = modifyConfigsFromComments(filename, sourceCode.ast, config, this.getRules());
 
             config = modifyConfigResult.config;
             modifyConfigResult.problems.forEach(problem => problems.push(problem));
@@ -775,6 +813,17 @@ class Linter {
         }
 
         const emitter = new EventEmitter().setMaxListeners(Infinity);
+        const traverser = new Traverser();
+        const ecmaFeatures = config.parserOptions.ecmaFeatures || {};
+        const ecmaVersion = config.parserOptions.ecmaVersion || 5;
+        const scopeManager = eslintScope.analyze(sourceCode.ast, {
+            ignoreEval: true,
+            nodejsScope: ecmaFeatures.globalReturn,
+            impliedStrict: ecmaFeatures.impliedStrict,
+            ecmaVersion,
+            sourceType: config.parserOptions.sourceType || "script",
+            fallback: Traverser.getKeys
+        });
 
         /*
          * Create a frozen object with the ruleContext properties and methods that are shared by all rules.
@@ -785,12 +834,12 @@ class Linter {
             Object.assign(
                 Object.create(BASE_TRAVERSAL_CONTEXT),
                 {
-                    getAncestors: () => this.traverser.parents(),
-                    getDeclaredVariables: this.getDeclaredVariables.bind(this),
+                    getAncestors: () => traverser.parents(),
+                    getDeclaredVariables: scopeManager.getDeclaredVariables.bind(scopeManager),
                     getFilename: () => filename,
-                    getScope: this.getScope.bind(this),
+                    getScope: () => getScope(scopeManager, traverser.current(), config.parserOptions.ecmaVersion),
                     getSourceCode: () => sourceCode,
-                    markVariableAsUsed: this.markVariableAsUsed.bind(this),
+                    markVariableAsUsed: name => markVariableAsUsed(scopeManager, traverser.current(), config.parserOptions, name),
                     parserOptions: config.parserOptions,
                     parserPath: config.parser,
                     parserServices,
@@ -890,25 +939,8 @@ class Linter {
             }
         });
 
-        // save config so rules can access as necessary
-        this.currentConfig = config;
-        this.traverser = new Traverser();
-
-        const ecmaFeatures = this.currentConfig.parserOptions.ecmaFeatures || {};
-        const ecmaVersion = this.currentConfig.parserOptions.ecmaVersion || 5;
-
-        // gather scope data that may be needed by the rules
-        this.scopeManager = eslintScope.analyze(sourceCode.ast, {
-            ignoreEval: true,
-            nodejsScope: ecmaFeatures.globalReturn,
-            impliedStrict: ecmaFeatures.impliedStrict,
-            ecmaVersion,
-            sourceType: this.currentConfig.parserOptions.sourceType || "script",
-            fallback: Traverser.getKeys
-        });
-
         // augment global scope with declared global variables
-        addDeclaredGlobals(sourceCode.ast, this.scopeManager.scopes[0], this.currentConfig, this.environments);
+        addDeclaredGlobals(sourceCode.ast, scopeManager.scopes[0], config, this.environments);
 
         const eventGenerator = new CodePathAnalyzer(new NodeEventGenerator(emitter));
 
@@ -918,7 +950,7 @@ class Linter {
          * automatically be informed that this type of node has been found
          * and react accordingly.
          */
-        this.traverser.traverse(sourceCode.ast, {
+        traverser.traverse(sourceCode.ast, {
             enter(node, parent) {
                 node.parent = parent;
                 eventGenerator.enterNode(node);
@@ -935,87 +967,40 @@ class Linter {
     }
 
     /**
-     * Gets the SourceCode object representing the parsed source.
-     * @returns {SourceCode} The SourceCode object.
-     */
-    getSourceCode() {
-        return this.sourceCode;
-    }
-
-    /**
-     * Gets the scope for the current node.
-     * @returns {Object} An object representing the current node's scope.
+     * Verifies the text against the rules specified by the second argument.
+     * @param {string|SourceCode} textOrSourceCode The text to parse or a SourceCode object.
+     * @param {ESLintConfig} config An ESLintConfig instance to configure everything.
+     * @param {(string|Object)} [filenameOrOptions] The optional filename of the file being checked.
+     *      If this is not set, the filename will default to '<input>' in the rule context. If
+     *      an object, then it has "filename", "saveState", and "allowInlineConfig" properties.
+     * @param {boolean} [saveState] Indicates if the state from the last run should be saved.
+     *      Mostly useful for testing purposes.
+     * @param {boolean} [filenameOrOptions.allowInlineConfig] Allow/disallow inline comments' ability to change config once it is set. Defaults to true if not supplied.
+     *      Useful if you want to validate JS without comments overriding rules.
+     * @param {function(string): string[]} [filenameOrOptions.preprocess] preprocessor for source text. If provided,
+     *      this should accept a string of source text, and return an array of code blocks to lint.
+     * @param {function(Array<Object[]>): Object[]} [filenameOrOptions.postprocess] postprocessor for report messages. If provided,
+     *      this should accept an array of the message lists for each code block returned from the preprocessor,
+     *      apply a mapping to the messages as appropriate, and return a one-dimensional array of messages
+     * @returns {Object[]} The results as an array of messages or null if no messages.
      */
-    getScope() {
-        const parents = this.traverser.parents();
-
-        // Don't do this for Program nodes - they have no parents
-        if (parents.length) {
-
-            // if current node introduces a scope, add it to the list
-            const current = this.traverser.current();
+    verify(textOrSourceCode, config, filenameOrOptions) {
+        const preprocess = filenameOrOptions && filenameOrOptions.preprocess || (rawText => [rawText]);
+        const postprocess = filenameOrOptions && filenameOrOptions.postprocess || lodash.flatten;
 
-            if (this.currentConfig.parserOptions.ecmaVersion >= 6) {
-                if (["BlockStatement", "SwitchStatement", "CatchClause", "FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"].indexOf(current.type) >= 0) {
-                    parents.push(current);
-                }
-            } else {
-                if (["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"].indexOf(current.type) >= 0) {
-                    parents.push(current);
-                }
-            }
-
-            // Ascend the current node's parents
-            for (let i = parents.length - 1; i >= 0; --i) {
-
-                // Get the innermost scope
-                const scope = this.scopeManager.acquire(parents[i], true);
-
-                if (scope) {
-                    if (scope.type === "function-expression-name") {
-                        return scope.childScopes[0];
-                    }
-                    return scope;
-
-                }
-
-            }
-
-        }
-
-        return this.scopeManager.scopes[0];
+        return postprocess(
+            preprocess(textOrSourceCode).map(
+                textBlock => this._verifyWithoutProcessors(textBlock, config, filenameOrOptions)
+            )
+        );
     }
 
     /**
-     * Record that a particular variable has been used in code
-     * @param {string} name The name of the variable to mark as used
-     * @returns {boolean} True if the variable was found and marked as used,
-     *      false if not.
+     * Gets the SourceCode object representing the parsed source.
+     * @returns {SourceCode} The SourceCode object.
      */
-    markVariableAsUsed(name) {
-        const hasGlobalReturn = this.currentConfig.parserOptions.ecmaFeatures && this.currentConfig.parserOptions.ecmaFeatures.globalReturn,
-            specialScope = hasGlobalReturn || this.currentConfig.parserOptions.sourceType === "module";
-        let scope = this.getScope(),
-            i,
-            len;
-
-        // Special Node.js scope means we need to start one level deeper
-        if (scope.type === "global" && specialScope) {
-            scope = scope.childScopes[0];
-        }
-
-        do {
-            const variables = scope.variables;
-
-            for (i = 0, len = variables.length; i < len; i++) {
-                if (variables[i].name === name) {
-                    variables[i].eslintUsed = true;
-                    return true;
-                }
-            }
-        } while ((scope = scope.upper));
-
-        return false;
+    getSourceCode() {
+        return this.sourceCode;
     }
 
     /**
@@ -1047,29 +1032,6 @@ class Linter {
         return this.rules.getAllLoadedRules();
     }
 
-    /**
-     * Gets variables that are declared by a specified node.
-     *
-     * The variables are its `defs[].node` or `defs[].parent` is same as the specified node.
-     * Specifically, below:
-     *
-     * - `VariableDeclaration` - variables of its all declarators.
-     * - `VariableDeclarator` - variables.
-     * - `FunctionDeclaration`/`FunctionExpression` - its function name and parameters.
-     * - `ArrowFunctionExpression` - its parameters.
-     * - `ClassDeclaration`/`ClassExpression` - its class name.
-     * - `CatchClause` - variables of its exception.
-     * - `ImportDeclaration` - variables of  its all specifiers.
-     * - `ImportSpecifier`/`ImportDefaultSpecifier`/`ImportNamespaceSpecifier` - a variable.
-     * - others - always an empty array.
-     *
-     * @param {ASTNode} node A node to get.
-     * @returns {eslint-scope.Variable[]} Variables that are declared by the node.
-     */
-    getDeclaredVariables(node) {
-        return (this.scopeManager && this.scopeManager.getDeclaredVariables(node)) || [];
-    }
-
     /**
      * Performs multiple autofix passes over the text until as many fixes as possible
      * have been applied.
@@ -1080,6 +1042,11 @@ class Linter {
      * @param {boolean} options.allowInlineConfig Flag indicating if inline comments
      *      should be allowed.
      * @param {boolean|Function} options.fix Determines whether fixes should be applied
+     * @param {Function} options.preprocess preprocessor for source text. If provided, this should
+     *      accept a string of source text, and return an array of code blocks to lint.
+     * @param {Function} options.postprocess postprocessor for report messages. If provided,
+     *      this should accept an array of the message lists for each code block returned from the preprocessor,
+     *      apply a mapping to the messages as appropriate, and return a one-dimensional array of messages
      * @returns {Object} The result of the fix operation as returned from the
      *      SourceCodeFixer.
      */
@@ -1140,23 +1107,4 @@ class Linter {
 
         return fixedResult;
     }
-}
-
-Object.keys(DEPRECATED_SOURCECODE_PASSTHROUGHS).forEach(methodName => {
-    const exMethodName = DEPRECATED_SOURCECODE_PASSTHROUGHS[methodName];
-
-    // Applies the SourceCode methods to the Linter prototype
-    Object.defineProperty(Linter.prototype, methodName, {
-        value() {
-            if (this.sourceCode) {
-                return this.sourceCode[exMethodName].apply(this.sourceCode, arguments);
-            }
-            return null;
-        },
-        configurable: true,
-        writable: true,
-        enumerable: false
-    });
-});
-
-module.exports = Linter;
+};
diff --git a/lib/rules/indent-legacy.js b/lib/rules/indent-legacy.js
index 301217e4a..5534944e6 100644
--- a/lib/rules/indent-legacy.js
+++ b/lib/rules/indent-legacy.js
@@ -1040,7 +1040,7 @@ module.exports = {
 
                 const checkNodes = [node.property];
 
-                const dot = context.getTokenBefore(node.property);
+                const dot = sourceCode.getTokenBefore(node.property);
 
                 if (dot.type === "Punctuator" && dot.value === ".") {
                     checkNodes.push(dot);
diff --git a/lib/rules/indent.js b/lib/rules/indent.js
index 07aa1e830..0f6468a7e 100644
--- a/lib/rules/indent.js
+++ b/lib/rules/indent.js
@@ -950,12 +950,12 @@ module.exports = {
         }
 
         /**
-         * Check whether the given token is the first token of a statement.
+         * Check whether the given token is on the first line of a statement.
          * @param {Token} token The token to check.
          * @param {ASTNode} leafNode The expression node that the token belongs directly.
-         * @returns {boolean} `true` if the token is the first token of a statement.
+         * @returns {boolean} `true` if the token is on the first line of a statement.
          */
-        function isFirstTokenOfStatement(token, leafNode) {
+        function isOnFirstLineOfStatement(token, leafNode) {
             let node = leafNode;
 
             while (node.parent && !node.parent.type.endsWith("Statement") && !node.parent.type.endsWith("Declaration")) {
@@ -963,7 +963,7 @@ module.exports = {
             }
             node = node.parent;
 
-            return !node || node.range[0] === token.range[0];
+            return !node || node.loc.start.line === token.loc.start.line;
         }
 
         const baseOffsetListeners = {
@@ -1076,7 +1076,7 @@ module.exports = {
                 //     /*else*/ qiz ;
                 if (!options.flatTernaryExpressions ||
                     !astUtils.isTokenOnSameLine(node.test, node.consequent) ||
-                    isFirstTokenOfStatement(firstToken, node)
+                    isOnFirstLineOfStatement(firstToken, node)
                 ) {
                     const questionMarkToken = sourceCode.getFirstTokenBetween(node.test, node.consequent, token => token.type === "Punctuator" && token.value === "?");
                     const colonToken = sourceCode.getFirstTokenBetween(node.consequent, node.alternate, token => token.type === "Punctuator" && token.value === ":");
diff --git a/lib/rules/key-spacing.js b/lib/rules/key-spacing.js
index 77bab172b..a7b825072 100644
--- a/lib/rules/key-spacing.js
+++ b/lib/rules/key-spacing.js
@@ -420,7 +420,6 @@ module.exports = {
                 isExtra = diff > 0,
                 diffAbs = Math.abs(diff),
                 spaces = Array(diffAbs + 1).join(" ");
-            let fix;
 
             if ((
                 diff && mode === "strict" ||
@@ -428,6 +427,8 @@ module.exports = {
                 diff > 0 && !expected && mode === "minimum") &&
                 !(expected && containsLineTerminator(whitespace))
             ) {
+                let fix;
+
                 if (isExtra) {
                     let range;
 
diff --git a/lib/rules/newline-per-chained-call.js b/lib/rules/newline-per-chained-call.js
index 613429d13..356c4baf3 100644
--- a/lib/rules/newline-per-chained-call.js
+++ b/lib/rules/newline-per-chained-call.js
@@ -19,7 +19,7 @@ module.exports = {
             category: "Stylistic Issues",
             recommended: false
         },
-
+        fixable: "whitespace",
         schema: [{
             type: "object",
             properties: {
@@ -40,6 +40,18 @@ module.exports = {
 
         const sourceCode = context.getSourceCode();
 
+        /**
+         * Get the prefix of a given MemberExpression node.
+         * If the MemberExpression node is a computed value it returns a
+         * left bracket. If not it returns a period.
+         *
+         * @param  {ASTNode} node - A MemberExpression node to get
+         * @returns {string} The prefix of the node.
+         */
+        function getPrefix(node) {
+            return node.computed ? "[" : ".";
+        }
+
         /**
          * Gets the property text of a given MemberExpression node.
          * If the text is multiline, this returns only the first line.
@@ -48,7 +60,7 @@ module.exports = {
          * @returns {string} The property text of the node.
          */
         function getPropertyText(node) {
-            const prefix = node.computed ? "[" : ".";
+            const prefix = getPrefix(node);
             const lines = sourceCode.getText(node.property).split(astUtils.LINEBREAK_MATCHER);
             const suffix = node.computed && lines.length === 1 ? "]" : "";
 
@@ -70,13 +82,18 @@ module.exports = {
                     parent = parent.callee.object;
                 }
 
-                if (depth > ignoreChainWithDepth && callee.property.loc.start.line === callee.object.loc.end.line) {
+                if (depth > ignoreChainWithDepth && astUtils.isTokenOnSameLine(callee.object, callee.property)) {
                     context.report({
                         node: callee.property,
                         loc: callee.property.loc.start,
                         message: "Expected line break before `{{callee}}`.",
                         data: {
                             callee: getPropertyText(callee)
+                        },
+                        fix(fixer) {
+                            const firstTokenAfterObject = sourceCode.getTokenAfter(callee.object, astUtils.isNotClosingParenToken);
+
+                            return fixer.insertTextBefore(firstTokenAfterObject, "\n");
                         }
                     });
                 }
diff --git a/lib/rules/no-extra-parens.js b/lib/rules/no-extra-parens.js
index 7e350d29e..9c8fe70f0 100644
--- a/lib/rules/no-extra-parens.js
+++ b/lib/rules/no-extra-parens.js
@@ -336,6 +336,23 @@ module.exports = {
             }
         }
 
+        /**
+         * Check if a member expression contains a call expression
+         * @param {ASTNode} node MemberExpression node to evaluate
+         * @returns {boolean} true if found, false if not
+         */
+        function doesMemberExpressionContainCallExpression(node) {
+            let currentNode = node.object;
+            let currentNodeType = node.object.type;
+
+            while (currentNodeType === "MemberExpression") {
+                currentNode = currentNode.object;
+                currentNodeType = currentNode.type;
+            }
+
+            return currentNodeType === "CallExpression";
+        }
+
         /**
          * Evaluate a new call
          * @param {ASTNode} node node to evaluate
@@ -343,10 +360,21 @@ module.exports = {
          * @private
          */
         function checkCallNew(node) {
-            if (hasExcessParens(node.callee) && precedence(node.callee) >= precedence(node)) {
-                const hasNewParensException = node.callee.type === "NewExpression" && !isNewExpressionWithParens(node.callee);
+            const callee = node.callee;
 
-                if (hasDoubleExcessParens(node.callee) || !isIIFE(node) && !hasNewParensException) {
+            if (hasExcessParens(callee) && precedence(callee) >= precedence(node)) {
+                const hasNewParensException = callee.type === "NewExpression" && !isNewExpressionWithParens(callee);
+
+                if (
+                    hasDoubleExcessParens(callee) ||
+                    !isIIFE(node) && !hasNewParensException && !(
+
+                        // Allow extra parens around a new expression if
+                        // there are intervening parentheses.
+                        callee.type === "MemberExpression" &&
+                        doesMemberExpressionContainCallExpression(callee)
+                    )
+                ) {
                     report(node.callee);
                 }
             }
@@ -574,8 +602,10 @@ module.exports = {
             LogicalExpression: checkBinaryLogical,
 
             MemberExpression(node) {
+                const nodeObjHasExcessParens = hasExcessParens(node.object);
+
                 if (
-                    hasExcessParens(node.object) &&
+                    nodeObjHasExcessParens &&
                     precedence(node.object) >= precedence(node) &&
                     (
                         node.computed ||
@@ -589,6 +619,13 @@ module.exports = {
                 ) {
                     report(node.object);
                 }
+
+                if (nodeObjHasExcessParens &&
+                  node.object.type === "CallExpression" &&
+                  node.parent.type !== "NewExpression") {
+                    report(node.object);
+                }
+
                 if (node.computed && hasExcessParens(node.property)) {
                     report(node.property);
                 }
diff --git a/lib/rules/no-tabs.js b/lib/rules/no-tabs.js
index 0757a6906..4bab96f38 100644
--- a/lib/rules/no-tabs.js
+++ b/lib/rules/no-tabs.js
@@ -27,7 +27,7 @@ module.exports = {
     create(context) {
         return {
             Program(node) {
-                context.getSourceLines().forEach((line, index) => {
+                context.getSourceCode().getLines().forEach((line, index) => {
                     const match = regex.exec(line);
 
                     if (match) {
diff --git a/lib/rules/no-undef-init.js b/lib/rules/no-undef-init.js
index 9df40e9ce..7e58f55a6 100644
--- a/lib/rules/no-undef-init.js
+++ b/lib/rules/no-undef-init.js
@@ -43,6 +43,10 @@ module.exports = {
                         message: "It's not necessary to initialize '{{name}}' to undefined.",
                         data: { name },
                         fix(fixer) {
+                            if (node.parent.kind === "var") {
+                                return null;
+                            }
+
                             if (node.id.type === "ArrayPattern" || node.id.type === "ObjectPattern") {
 
                                 // Don't fix destructuring assignment to `undefined`.
diff --git a/lib/rules/no-unmodified-loop-condition.js b/lib/rules/no-unmodified-loop-condition.js
index dbf35baed..49dff0d0c 100644
--- a/lib/rules/no-unmodified-loop-condition.js
+++ b/lib/rules/no-unmodified-loop-condition.js
@@ -213,13 +213,13 @@ function getEncloseFunctionDeclaration(reference) {
  * @returns {void}
  */
 function updateModifiedFlag(conditions, modifiers) {
-    let funcNode, funcVar;
 
     for (let i = 0; i < conditions.length; ++i) {
         const condition = conditions[i];
 
         for (let j = 0; !condition.modified && j < modifiers.length; ++j) {
             const modifier = modifiers[j];
+            let funcNode, funcVar;
 
             /*
              * Besides checking for the condition being in the loop, we want to
diff --git a/lib/rules/quote-props.js b/lib/rules/quote-props.js
index 1dcdd461b..305a1b41b 100644
--- a/lib/rules/quote-props.js
+++ b/lib/rules/quote-props.js
@@ -135,13 +135,14 @@ module.exports = {
          */
         function checkUnnecessaryQuotes(node) {
             const key = node.key;
-            let tokens;
 
             if (node.method || node.computed || node.shorthand) {
                 return;
             }
 
             if (key.type === "Literal" && typeof key.value === "string") {
+                let tokens;
+
                 try {
                     tokens = espree.tokenize(key.value);
                 } catch (e) {
@@ -215,7 +216,6 @@ module.exports = {
 
             node.properties.forEach(property => {
                 const key = property.key;
-                let tokens;
 
                 if (!key || property.method || property.computed || property.shorthand) {
                     return;
@@ -226,6 +226,8 @@ module.exports = {
                     quotedProps.push(property);
 
                     if (checkQuotesRedundancy) {
+                        let tokens;
+
                         try {
                             tokens = espree.tokenize(key.value);
                         } catch (e) {
diff --git a/lib/rules/quotes.js b/lib/rules/quotes.js
index b1117b85f..914762727 100644
--- a/lib/rules/quotes.js
+++ b/lib/rules/quotes.js
@@ -229,10 +229,9 @@ module.exports = {
             Literal(node) {
                 const val = node.value,
                     rawVal = node.raw;
-                let isValid;
 
                 if (settings && typeof val === "string") {
-                    isValid = (quoteOption === "backtick" && isAllowedAsNonBacktick(node)) ||
+                    let isValid = (quoteOption === "backtick" && isAllowedAsNonBacktick(node)) ||
                         isJSXLiteral(node) ||
                         astUtils.isSurroundedBy(rawVal, settings.quote);
 
diff --git a/lib/rules/space-before-blocks.js b/lib/rules/space-before-blocks.js
index a70136b1f..f50298c9c 100644
--- a/lib/rules/space-before-blocks.js
+++ b/lib/rules/space-before-blocks.js
@@ -82,11 +82,11 @@ module.exports = {
          */
         function checkPrecedingSpace(node) {
             const precedingToken = sourceCode.getTokenBefore(node);
-            let requireSpace;
 
             if (precedingToken && !isConflicted(precedingToken) && astUtils.isTokenOnSameLine(precedingToken, node)) {
                 const hasSpace = sourceCode.isSpaceBetweenTokens(precedingToken, node);
                 const parent = context.getAncestors().pop();
+                let requireSpace;
 
                 if (parent.type === "FunctionExpression" || parent.type === "FunctionDeclaration") {
                     requireSpace = checkFunctions;
diff --git a/lib/rules/valid-jsdoc.js b/lib/rules/valid-jsdoc.js
index ac8ae57c9..d4ee4e073 100644
--- a/lib/rules/valid-jsdoc.js
+++ b/lib/rules/valid-jsdoc.js
@@ -231,11 +231,11 @@ module.exports = {
                 hasConstructor = false,
                 isInterface = false,
                 isOverride = false,
-                isAbstract = false,
-                jsdoc;
+                isAbstract = false;
 
             // make sure only to validate JSDoc comments
             if (jsdocNode) {
+                let jsdoc;
 
                 try {
                     jsdoc = doctrine.parse(jsdocNode.value, {
diff --git a/package.json b/package.json
index dd79d8d27..0fed8f35f 100644
--- a/package.json
+++ b/package.json
@@ -40,7 +40,7 @@
     "chalk": "^2.1.0",
     "concat-stream": "^1.6.0",
     "cross-spawn": "^5.1.0",
-    "debug": "^2.6.8",
+    "debug": "^3.0.1",
     "doctrine": "^2.0.0",
     "eslint-scope": "^3.7.1",
     "espree": "^3.5.0",
@@ -64,7 +64,7 @@
     "natural-compare": "^1.4.0",
     "optionator": "^0.8.2",
     "path-is-inside": "^1.0.2",
-    "pluralize": "^4.0.0",
+    "pluralize": "^7.0.0",
     "progress": "^2.0.0",
     "require-uncached": "^1.0.3",
     "semver": "^5.3.0",
@@ -85,11 +85,11 @@
     "coveralls": "^2.13.1",
     "dateformat": "^2.0.0",
     "ejs": "^2.5.6",
-    "eslint-plugin-eslint-plugin": "^1.0.0",
+    "eslint-plugin-eslint-plugin": "^1.2.0",
     "eslint-plugin-node": "^5.1.0",
     "eslint-release": "^0.10.1",
     "eslump": "1.6.0",
-    "esprima": "^3.1.3",
+    "esprima": "^4.0.0",
     "esprima-fb": "^15001.1001.0-dev-harmony-fb",
     "istanbul": "^0.4.5",
     "jsdoc": "^3.4.3",
@@ -100,7 +100,7 @@
     "karma-phantomjs-launcher": "^1.0.4",
     "leche": "^2.1.2",
     "load-perf": "^0.2.0",
-    "markdownlint": "^0.5.0",
+    "markdownlint": "^0.6.1",
     "mocha": "^3.4.2",
     "mock-fs": "^4.3.0",
     "npm-license": "^0.3.3",
@@ -108,7 +108,7 @@
     "proxyquire": "^1.8.0",
     "shelljs": "^0.7.7",
     "shelljs-nodecli": "~0.1.1",
-    "sinon": "^2.3.2",
+    "sinon": "^3.2.1",
     "temp": "^0.8.3",
     "through": "^2.3.8"
   },


Test patch:
diff --git a/lib/testers/rule-tester.js b/lib/testers/rule-tester.js
index c16f51c6d..98297262b 100644
--- a/lib/testers/rule-tester.js
+++ b/lib/testers/rule-tester.js
@@ -307,9 +307,19 @@ class RuleTester {
                 config.rules[ruleName] = 1;
             }
 
-            linter.defineRule(ruleName, rule);
+            linter.defineRule(ruleName, Object.assign({}, rule, {
 
-            const schema = validator.getRuleOptionsSchema(ruleName, linter.rules);
+                // Create a wrapper rule that freezes the `context` properties.
+                create(context) {
+                    freezeDeeply(context.options);
+                    freezeDeeply(context.settings);
+                    freezeDeeply(context.parserOptions);
+
+                    return (typeof rule === "function" ? rule : rule.create)(context);
+                }
+            }));
+
+            const schema = validator.getRuleOptionsSchema(rule);
 
             if (schema) {
                 ajv.validateSchema(schema);
@@ -325,15 +335,13 @@ class RuleTester {
                 }
             }
 
-            validator.validate(config, "rule-tester", linter.rules, new Environments());
+            validator.validate(config, "rule-tester", linter.getRules(), new Environments());
 
             /*
              * Setup AST getters.
              * The goal is to check whether or not AST was modified when
              * running the rule under test.
              */
-            linter.reset();
-
             linter.defineRule("rule-tester/validate-ast", () => ({
                 Program(node) {
                     beforeAST = cloneDeeplyExcludesParent(node);
@@ -343,35 +351,11 @@ class RuleTester {
                 }
             }));
 
-            // Freezes rule-context properties.
-            const originalGet = linter.rules.get;
-
-            try {
-                linter.rules.get = function(ruleId) {
-                    const originalRule = originalGet.call(linter.rules, ruleId);
-
-                    return {
-                        meta: originalRule.meta,
-                        create(context) {
-                            Object.freeze(context);
-                            freezeDeeply(context.options);
-                            freezeDeeply(context.settings);
-                            freezeDeeply(context.parserOptions);
-
-                            return originalRule.create(context);
-                        }
-                    };
-
-                };
-
-                return {
-                    messages: linter.verify(code, config, filename, true),
-                    beforeAST,
-                    afterAST: cloneDeeplyExcludesParent(afterAST)
-                };
-            } finally {
-                linter.rules.get = originalGet;
-            }
+            return {
+                messages: linter.verify(code, config, filename, true),
+                beforeAST,
+                afterAST: cloneDeeplyExcludesParent(afterAST)
+            };
         }
 
         /**
diff --git a/tests/lib/ast-utils.js b/tests/lib/ast-utils.js
index f4df03844..fa092fa10 100644
--- a/tests/lib/ast-utils.js
+++ b/tests/lib/ast-utils.js
@@ -56,15 +56,13 @@ describe("ast-utils", () => {
                 `Expected ${func.toString()} to be called at least once but it was not called`
             );
         });
-
-        linter.reset();
     });
 
     describe("isTokenOnSameLine", () => {
         it("should return false if the tokens are not on the same line", () => {
-            linter.defineRule("checker", mustCall(() => ({
+            linter.defineRule("checker", mustCall(context => ({
                 BlockStatement: mustCall(node => {
-                    assert.isFalse(astUtils.isTokenOnSameLine(linter.getTokenBefore(node), node));
+                    assert.isFalse(astUtils.isTokenOnSameLine(context.getTokenBefore(node), node));
                 })
             })));
 
@@ -73,9 +71,9 @@ describe("ast-utils", () => {
 
         it("should return true if the tokens are on the same line", () => {
 
-            linter.defineRule("checker", mustCall(() => ({
+            linter.defineRule("checker", mustCall(context => ({
                 BlockStatement: mustCall(node => {
-                    assert.isTrue(astUtils.isTokenOnSameLine(linter.getTokenBefore(node), node));
+                    assert.isTrue(astUtils.isTokenOnSameLine(context.getTokenBefore(node), node));
                 })
             })));
 
@@ -117,9 +115,9 @@ describe("ast-utils", () => {
 
         // catch
         it("should return true if reference is assigned for catch", () => {
-            linter.defineRule("checker", mustCall(() => ({
+            linter.defineRule("checker", mustCall(context => ({
                 CatchClause: mustCall(node => {
-                    const variables = linter.getDeclaredVariables(node);
+                    const variables = context.getDeclaredVariables(node);
 
                     assert.lengthOf(astUtils.getModifyingReferences(variables[0].references), 1);
                 })
@@ -130,9 +128,9 @@ describe("ast-utils", () => {
 
         // const
         it("should return true if reference is assigned for const", () => {
-            linter.defineRule("checker", mustCall(() => ({
+            linter.defineRule("checker", mustCall(context => ({
                 VariableDeclaration: mustCall(node => {
-                    const variables = linter.getDeclaredVariables(node);
+                    const variables = context.getDeclaredVariables(node);
 
                     assert.lengthOf(astUtils.getModifyingReferences(variables[0].references), 1);
                 })
@@ -142,9 +140,9 @@ describe("ast-utils", () => {
         });
 
         it("should return false if reference is not assigned for const", () => {
-            linter.defineRule("checker", mustCall(() => ({
+            linter.defineRule("checker", mustCall(context => ({
                 VariableDeclaration: mustCall(node => {
-                    const variables = linter.getDeclaredVariables(node);
+                    const variables = context.getDeclaredVariables(node);
 
                     assert.lengthOf(astUtils.getModifyingReferences(variables[0].references), 0);
                 })
@@ -155,9 +153,9 @@ describe("ast-utils", () => {
 
         // class
         it("should return true if reference is assigned for class", () => {
-            linter.defineRule("checker", mustCall(() => ({
+            linter.defineRule("checker", mustCall(context => ({
                 ClassDeclaration: mustCall(node => {
-                    const variables = linter.getDeclaredVariables(node);
+                    const variables = context.getDeclaredVariables(node);
 
                     assert.lengthOf(astUtils.getModifyingReferences(variables[0].references), 1);
                     assert.lengthOf(astUtils.getModifyingReferences(variables[1].references), 0);
@@ -168,9 +166,9 @@ describe("ast-utils", () => {
         });
 
         it("should return false if reference is not assigned for class", () => {
-            linter.defineRule("checker", mustCall(() => ({
+            linter.defineRule("checker", mustCall(context => ({
                 ClassDeclaration: mustCall(node => {
-                    const variables = linter.getDeclaredVariables(node);
+                    const variables = context.getDeclaredVariables(node);
 
                     assert.lengthOf(astUtils.getModifyingReferences(variables[0].references), 0);
                 })
diff --git a/tests/lib/cli-engine.js b/tests/lib/cli-engine.js
index ea564ca00..b862062c8 100644
--- a/tests/lib/cli-engine.js
+++ b/tests/lib/cli-engine.js
@@ -2478,6 +2478,94 @@ describe("CLIEngine", () => {
                 assert.equal(report.results[0].messages[0].message, "'b' is defined but never used.");
                 assert.equal(report.results[0].messages[0].ruleId, "post-processed");
             });
+
+            describe("autofixing with processors", () => {
+                const HTML_PROCESSOR = Object.freeze({
+                    preprocess(text) {
+                        return [text.replace(/^<script>/, "").replace(/<\/script>$/, "")];
+                    },
+                    postprocess(problemLists) {
+                        return problemLists[0].map(problem => {
+                            if (problem.fix) {
+                                const updatedFix = Object.assign({}, problem.fix, {
+                                    range: problem.fix.range.map(index => index + "<script>".length)
+                                });
+
+                                return Object.assign({}, problem, { fix: updatedFix });
+                            }
+                            return problem;
+                        });
+                    }
+                });
+
+
+                it("should run in autofix mode when using a processor that supports autofixing", () => {
+                    engine = new CLIEngine({
+                        useEslintrc: false,
+                        plugins: ["test-processor"],
+                        rules: {
+                            semi: 2
+                        },
+                        extensions: ["js", "txt"],
+                        ignore: false,
+                        fix: true
+                    });
+
+                    engine.addPlugin("test-processor", {
+                        processors: {
+                            ".html": Object.assign({ supportsAutofix: true }, HTML_PROCESSOR)
+                        }
+                    });
+
+                    const report = engine.executeOnText("<script>foo</script>", "foo.html");
+
+                    assert.strictEqual(report.results[0].messages.length, 0);
+                    assert.strictEqual(report.results[0].output, "<script>foo;</script>");
+                });
+
+                it("should not run in autofix mode when using a processor that does not support autofixing", () => {
+                    engine = new CLIEngine({
+                        useEslintrc: false,
+                        plugins: ["test-processor"],
+                        rules: {
+                            semi: 2
+                        },
+                        extensions: ["js", "txt"],
+                        ignore: false,
+                        fix: true
+                    });
+
+                    engine.addPlugin("test-processor", { processors: { ".html": HTML_PROCESSOR } });
+
+                    const report = engine.executeOnText("<script>foo</script>", "foo.html");
+
+                    assert.strictEqual(report.results[0].messages.length, 1);
+                    assert.isFalse(Object.prototype.hasOwnProperty.call(report.results[0], "output"));
+                });
+
+                it("should not run in autofix mode when `fix: true` is not provided, even if the processor supports autofixing", () => {
+                    engine = new CLIEngine({
+                        useEslintrc: false,
+                        plugins: ["test-processor"],
+                        rules: {
+                            semi: 2
+                        },
+                        extensions: ["js", "txt"],
+                        ignore: false
+                    });
+
+                    engine.addPlugin("test-processor", {
+                        processors: {
+                            ".html": Object.assign({ supportsAutofix: true }, HTML_PROCESSOR)
+                        }
+                    });
+
+                    const report = engine.executeOnText("<script>foo</script>", "foo.html");
+
+                    assert.strictEqual(report.results[0].messages.length, 1);
+                    assert.isFalse(Object.prototype.hasOwnProperty.call(report.results[0], "output"));
+                });
+            });
         });
     });
 
diff --git a/tests/lib/code-path-analysis/code-path-analyzer.js b/tests/lib/code-path-analysis/code-path-analyzer.js
index ee603ef47..82384b157 100644
--- a/tests/lib/code-path-analysis/code-path-analyzer.js
+++ b/tests/lib/code-path-analysis/code-path-analyzer.js
@@ -54,11 +54,6 @@ function getExpectedDotArrows(source) {
 //------------------------------------------------------------------------------
 
 describe("CodePathAnalyzer", () => {
-
-    afterEach(() => {
-        linter.reset();
-    });
-
     EventGeneratorTester.testEventGeneratorInterface(
         new CodePathAnalyzer(new NodeEventGenerator(new EventEmitter()))
     );
diff --git a/tests/lib/code-path-analysis/code-path.js b/tests/lib/code-path-analysis/code-path.js
index 6bbd1ec48..be0b35f5c 100644
--- a/tests/lib/code-path-analysis/code-path.js
+++ b/tests/lib/code-path-analysis/code-path.js
@@ -26,7 +26,6 @@ const linter = new Linter();
 function parseCodePaths(code) {
     const retv = [];
 
-    linter.reset();
     linter.defineRule("test", () => ({
         onCodePathStart(codePath) {
             retv.push(codePath);
diff --git a/tests/lib/config/config-validator.js b/tests/lib/config/config-validator.js
index c6fec97ae..467035d97 100644
--- a/tests/lib/config/config-validator.js
+++ b/tests/lib/config/config-validator.js
@@ -98,7 +98,7 @@ describe("Validator", () => {
     describe("validate", () => {
 
         it("should do nothing with an empty config", () => {
-            const fn = validator.validate.bind(null, {}, "tests", linter.rules, linter.environments);
+            const fn = validator.validate.bind(null, {}, "tests", linter.getRules(), linter.environments);
 
             assert.doesNotThrow(fn);
         });
@@ -118,7 +118,7 @@ describe("Validator", () => {
                     rules: {}
                 },
                 "tests",
-                linter.rules,
+                linter.getRules(),
                 linter.environments
             );
 
@@ -132,7 +132,7 @@ describe("Validator", () => {
                     foo: true
                 },
                 "tests",
-                linter.rules,
+                linter.getRules(),
                 linter.environments
             );
 
@@ -141,13 +141,13 @@ describe("Validator", () => {
 
         describe("root", () => {
             it("should throw with a string value", () => {
-                const fn = validator.validate.bind(null, { root: "true" }, null, linter.rules, linter.environments);
+                const fn = validator.validate.bind(null, { root: "true" }, null, linter.getRules(), linter.environments);
 
                 assert.throws(fn, "Property \"root\" is the wrong type (expected boolean but got `\"true\"`).");
             });
 
             it("should throw with a numeric value", () => {
-                const fn = validator.validate.bind(null, { root: 0 }, null, linter.rules, linter.environments);
+                const fn = validator.validate.bind(null, { root: 0 }, null, linter.getRules(), linter.environments);
 
                 assert.throws(fn, "Property \"root\" is the wrong type (expected boolean but got `0`).");
             });
@@ -155,13 +155,13 @@ describe("Validator", () => {
 
         describe("globals", () => {
             it("should throw with a string value", () => {
-                const fn = validator.validate.bind(null, { globals: "jQuery" }, null, linter.rules, linter.environments);
+                const fn = validator.validate.bind(null, { globals: "jQuery" }, null, linter.getRules(), linter.environments);
 
                 assert.throws(fn, "Property \"globals\" is the wrong type (expected object but got `\"jQuery\"`).");
             });
 
             it("should throw with an array value", () => {
-                const fn = validator.validate.bind(null, { globals: ["jQuery"] }, null, linter.rules, linter.environments);
+                const fn = validator.validate.bind(null, { globals: ["jQuery"] }, null, linter.getRules(), linter.environments);
 
                 assert.throws(fn, "Property \"globals\" is the wrong type (expected object but got `[\"jQuery\"]`).");
             });
@@ -169,7 +169,7 @@ describe("Validator", () => {
 
         describe("parser", () => {
             it("should not throw with a null value", () => {
-                const fn = validator.validate.bind(null, { parser: null }, null, linter.rules, linter.environments);
+                const fn = validator.validate.bind(null, { parser: null }, null, linter.getRules(), linter.environments);
 
                 assert.doesNotThrow(fn);
             });
@@ -178,31 +178,31 @@ describe("Validator", () => {
         describe("env", () => {
 
             it("should throw with an array environment", () => {
-                const fn = validator.validate.bind(null, { env: [] }, null, linter.rules, linter.environments);
+                const fn = validator.validate.bind(null, { env: [] }, null, linter.getRules(), linter.environments);
 
                 assert.throws(fn, "Property \"env\" is the wrong type (expected object but got `[]`).");
             });
 
             it("should throw with a primitive environment", () => {
-                const fn = validator.validate.bind(null, { env: 1 }, null, linter.rules, linter.environments);
+                const fn = validator.validate.bind(null, { env: 1 }, null, linter.getRules(), linter.environments);
 
                 assert.throws(fn, "Property \"env\" is the wrong type (expected object but got `1`).");
             });
 
             it("should catch invalid environments", () => {
-                const fn = validator.validate.bind(null, { env: { browser: true, invalid: true } }, null, linter.rules, linter.environments);
+                const fn = validator.validate.bind(null, { env: { browser: true, invalid: true } }, null, linter.getRules(), linter.environments);
 
                 assert.throws(fn, "Environment key \"invalid\" is unknown\n");
             });
 
             it("should catch disabled invalid environments", () => {
-                const fn = validator.validate.bind(null, { env: { browser: true, invalid: false } }, null, linter.rules, linter.environments);
+                const fn = validator.validate.bind(null, { env: { browser: true, invalid: false } }, null, linter.getRules(), linter.environments);
 
                 assert.throws(fn, "Environment key \"invalid\" is unknown\n");
             });
 
             it("should do nothing with an undefined environment", () => {
-                const fn = validator.validate.bind(null, {}, null, linter.rules, linter.environments);
+                const fn = validator.validate.bind(null, {}, null, linter.getRules(), linter.environments);
 
                 assert.doesNotThrow(fn);
             });
@@ -211,13 +211,13 @@ describe("Validator", () => {
 
         describe("plugins", () => {
             it("should not throw with an empty array", () => {
-                const fn = validator.validate.bind(null, { plugins: [] }, null, linter.rules, linter.environments);
+                const fn = validator.validate.bind(null, { plugins: [] }, null, linter.getRules(), linter.environments);
 
                 assert.doesNotThrow(fn);
             });
 
             it("should throw with a string", () => {
-                const fn = validator.validate.bind(null, { plugins: "react" }, null, linter.rules, linter.environments);
+                const fn = validator.validate.bind(null, { plugins: "react" }, null, linter.getRules(), linter.environments);
 
                 assert.throws(fn, "Property \"plugins\" is the wrong type (expected array but got `\"react\"`).");
             });
@@ -225,13 +225,13 @@ describe("Validator", () => {
 
         describe("settings", () => {
             it("should not throw with an empty object", () => {
-                const fn = validator.validate.bind(null, { settings: {} }, null, linter.rules, linter.environments);
+                const fn = validator.validate.bind(null, { settings: {} }, null, linter.getRules(), linter.environments);
 
                 assert.doesNotThrow(fn);
             });
 
             it("should throw with an array", () => {
-                const fn = validator.validate.bind(null, { settings: ["foo"] }, null, linter.rules, linter.environments);
+                const fn = validator.validate.bind(null, { settings: ["foo"] }, null, linter.getRules(), linter.environments);
 
                 assert.throws(fn, "Property \"settings\" is the wrong type (expected object but got `[\"foo\"]`).");
             });
@@ -239,19 +239,19 @@ describe("Validator", () => {
 
         describe("extends", () => {
             it("should not throw with an empty array", () => {
-                const fn = validator.validate.bind(null, { extends: [] }, null, linter.rules, linter.environments);
+                const fn = validator.validate.bind(null, { extends: [] }, null, linter.getRules(), linter.environments);
 
                 assert.doesNotThrow(fn);
             });
 
             it("should not throw with a string", () => {
-                const fn = validator.validate.bind(null, { extends: "react" }, null, linter.rules, linter.environments);
+                const fn = validator.validate.bind(null, { extends: "react" }, null, linter.getRules(), linter.environments);
 
                 assert.doesNotThrow(fn);
             });
 
             it("should throw with an object", () => {
-                const fn = validator.validate.bind(null, { extends: {} }, null, linter.rules, linter.environments);
+                const fn = validator.validate.bind(null, { extends: {} }, null, linter.getRules(), linter.environments);
 
                 assert.throws(fn, "Property \"extends\" is the wrong type (expected string/array but got `{}`).");
             });
@@ -259,13 +259,13 @@ describe("Validator", () => {
 
         describe("parserOptions", () => {
             it("should not throw with an empty object", () => {
-                const fn = validator.validate.bind(null, { parserOptions: {} }, null, linter.rules, linter.environments);
+                const fn = validator.validate.bind(null, { parserOptions: {} }, null, linter.getRules(), linter.environments);
 
                 assert.doesNotThrow(fn);
             });
 
             it("should throw with an array", () => {
-                const fn = validator.validate.bind(null, { parserOptions: ["foo"] }, null, linter.rules, linter.environments);
+                const fn = validator.validate.bind(null, { parserOptions: ["foo"] }, null, linter.getRules(), linter.environments);
 
                 assert.throws(fn, "Property \"parserOptions\" is the wrong type (expected object but got `[\"foo\"]`).");
             });
@@ -274,67 +274,67 @@ describe("Validator", () => {
         describe("rules", () => {
 
             it("should do nothing with an empty rules object", () => {
-                const fn = validator.validate.bind(null, { rules: {} }, "tests", linter.rules, linter.environments);
+                const fn = validator.validate.bind(null, { rules: {} }, "tests", linter.getRules(), linter.environments);
 
                 assert.doesNotThrow(fn);
             });
 
             it("should do nothing with a valid config with rules", () => {
-                const fn = validator.validate.bind(null, { rules: { "mock-rule": [2, "second"] } }, "tests", linter.rules, linter.environments);
+                const fn = validator.validate.bind(null, { rules: { "mock-rule": [2, "second"] } }, "tests", linter.getRules(), linter.environments);
 
                 assert.doesNotThrow(fn);
             });
 
             it("should do nothing with a valid config when severity is off", () => {
-                const fn = validator.validate.bind(null, { rules: { "mock-rule": ["off", "second"] } }, "tests", linter.rules, linter.environments);
+                const fn = validator.validate.bind(null, { rules: { "mock-rule": ["off", "second"] } }, "tests", linter.getRules(), linter.environments);
 
                 assert.doesNotThrow(fn);
             });
 
             it("should do nothing with an invalid config when severity is off", () => {
-                const fn = validator.validate.bind(null, { rules: { "mock-required-options-rule": "off" } }, "tests", linter.rules, linter.environments);
+                const fn = validator.validate.bind(null, { rules: { "mock-required-options-rule": "off" } }, "tests", linter.getRules(), linter.environments);
 
                 assert.doesNotThrow(fn);
             });
 
             it("should do nothing with an invalid config when severity is an array with 'off'", () => {
-                const fn = validator.validate.bind(null, { rules: { "mock-required-options-rule": ["off"] } }, "tests", linter.rules, linter.environments);
+                const fn = validator.validate.bind(null, { rules: { "mock-required-options-rule": ["off"] } }, "tests", linter.getRules(), linter.environments);
 
                 assert.doesNotThrow(fn);
             });
 
             it("should do nothing with a valid config when severity is warn", () => {
-                const fn = validator.validate.bind(null, { rules: { "mock-rule": ["warn", "second"] } }, "tests", linter.rules, linter.environments);
+                const fn = validator.validate.bind(null, { rules: { "mock-rule": ["warn", "second"] } }, "tests", linter.getRules(), linter.environments);
 
                 assert.doesNotThrow(fn);
             });
 
             it("should do nothing with a valid config when severity is error", () => {
-                const fn = validator.validate.bind(null, { rules: { "mock-rule": ["error", "second"] } }, "tests", linter.rules, linter.environments);
+                const fn = validator.validate.bind(null, { rules: { "mock-rule": ["error", "second"] } }, "tests", linter.getRules(), linter.environments);
 
                 assert.doesNotThrow(fn);
             });
 
             it("should do nothing with a valid config when severity is Off", () => {
-                const fn = validator.validate.bind(null, { rules: { "mock-rule": ["Off", "second"] } }, "tests", linter.rules, linter.environments);
+                const fn = validator.validate.bind(null, { rules: { "mock-rule": ["Off", "second"] } }, "tests", linter.getRules(), linter.environments);
 
                 assert.doesNotThrow(fn);
             });
 
             it("should do nothing with a valid config when severity is Warn", () => {
-                const fn = validator.validate.bind(null, { rules: { "mock-rule": ["Warn", "second"] } }, "tests", linter.rules, linter.environments);
+                const fn = validator.validate.bind(null, { rules: { "mock-rule": ["Warn", "second"] } }, "tests", linter.getRules(), linter.environments);
 
                 assert.doesNotThrow(fn);
             });
 
             it("should do nothing with a valid config when severity is Error", () => {
-                const fn = validator.validate.bind(null, { rules: { "mock-rule": ["Error", "second"] } }, "tests", linter.rules, linter.environments);
+                const fn = validator.validate.bind(null, { rules: { "mock-rule": ["Error", "second"] } }, "tests", linter.getRules(), linter.environments);
 
                 assert.doesNotThrow(fn);
             });
 
             it("should catch invalid rule options", () => {
-                const fn = validator.validate.bind(null, { rules: { "mock-rule": [3, "third"] } }, "tests", linter.rules, linter.environments);
+                const fn = validator.validate.bind(null, { rules: { "mock-rule": [3, "third"] } }, "tests", linter.getRules(), linter.environments);
 
                 assert.throws(fn, "tests:\n\tConfiguration for rule \"mock-rule\" is invalid:\n\tSeverity should be one of the following: 0 = off, 1 = warn, 2 = error (you passed '3').\n");
             });
@@ -342,7 +342,7 @@ describe("Validator", () => {
             it("should allow for rules with no options", () => {
                 linter.defineRule("mock-no-options-rule", mockNoOptionsRule);
 
-                const fn = validator.validate.bind(null, { rules: { "mock-no-options-rule": 2 } }, "tests", linter.rules, linter.environments);
+                const fn = validator.validate.bind(null, { rules: { "mock-no-options-rule": 2 } }, "tests", linter.getRules(), linter.environments);
 
                 assert.doesNotThrow(fn);
             });
@@ -350,7 +350,7 @@ describe("Validator", () => {
             it("should not allow options for rules with no options", () => {
                 linter.defineRule("mock-no-options-rule", mockNoOptionsRule);
 
-                const fn = validator.validate.bind(null, { rules: { "mock-no-options-rule": [2, "extra"] } }, "tests", linter.rules, linter.environments);
+                const fn = validator.validate.bind(null, { rules: { "mock-no-options-rule": [2, "extra"] } }, "tests", linter.getRules(), linter.environments);
 
                 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");
             });
@@ -358,43 +358,43 @@ describe("Validator", () => {
 
         describe("overrides", () => {
             it("should not throw with an empty overrides array", () => {
-                const fn = validator.validate.bind(null, { overrides: [] }, "tests", linter.rules, linter.environments);
+                const fn = validator.validate.bind(null, { overrides: [] }, "tests", linter.getRules(), 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);
+                const fn = validator.validate.bind(null, { overrides: [{ files: "*", rules: {} }] }, "tests", linter.getRules(), 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);
+                const fn = validator.validate.bind(null, { overrides: [{ rules: {} }] }, "tests", linter.getRules(), linter.environments);
 
                 assert.throws(fn, "ESLint configuration in tests 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);
+                const fn = validator.validate.bind(null, { overrides: [{ files: [] }] }, "tests", linter.getRules(), linter.environments);
 
                 assert.throws(fn, "ESLint configuration in tests 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);
+                const fn = validator.validate.bind(null, { overrides: [{ files: "*", overrides: [{ files: "*", rules: {} }] }] }, "tests", linter.getRules(), linter.environments);
 
                 assert.throws(fn, "ESLint configuration in tests 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);
+                const fn = validator.validate.bind(null, { overrides: [{ files: "*", extends: "eslint-recommended" }] }, "tests", linter.getRules(), linter.environments);
 
                 assert.throws(fn, "ESLint configuration in tests 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);
+                const fn = validator.validate.bind(null, { overrides: [{ files: "*", root: "true" }] }, "tests", linter.getRules(), linter.environments);
 
                 assert.throws(fn, "ESLint configuration in tests is invalid:\n\t- Unexpected top-level property \"overrides[0].root\".\n");
             });
@@ -405,12 +405,12 @@ describe("Validator", () => {
     describe("getRuleOptionsSchema", () => {
 
         it("should return null for a missing rule", () => {
-            assert.equal(validator.getRuleOptionsSchema("non-existent-rule", linter.rules), null);
+            assert.equal(validator.getRuleOptionsSchema(linter.rules.get("non-existent-rule")), null);
         });
 
         it("should not modify object schema", () => {
             linter.defineRule("mock-object-rule", mockObjectRule);
-            assert.deepEqual(validator.getRuleOptionsSchema("mock-object-rule", linter.rules), {
+            assert.deepEqual(validator.getRuleOptionsSchema(linter.rules.get("mock-object-rule")), {
                 enum: ["first", "second"]
             });
         });
@@ -420,43 +420,43 @@ describe("Validator", () => {
     describe("validateRuleOptions", () => {
 
         it("should throw for incorrect warning level number", () => {
-            const fn = validator.validateRuleOptions.bind(null, "mock-rule", 3, "tests", linter.rules);
+            const fn = validator.validateRuleOptions.bind(null, linter.rules.get("mock-rule"), "mock-rule", 3, "tests");
 
             assert.throws(fn, "tests:\n\tConfiguration for rule \"mock-rule\" is invalid:\n\tSeverity should be one of the following: 0 = off, 1 = warn, 2 = error (you passed '3').\n");
         });
 
         it("should throw for incorrect warning level string", () => {
-            const fn = validator.validateRuleOptions.bind(null, "mock-rule", "booya", "tests", linter.rules);
+            const fn = validator.validateRuleOptions.bind(null, linter.rules.get("mock-rule"), "mock-rule", "booya", "tests");
 
             assert.throws(fn, "tests:\n\tConfiguration for rule \"mock-rule\" is invalid:\n\tSeverity should be one of the following: 0 = off, 1 = warn, 2 = error (you passed '\"booya\"').\n");
         });
 
         it("should throw for invalid-type warning level", () => {
-            const fn = validator.validateRuleOptions.bind(null, "mock-rule", [["error"]], "tests", linter.rules);
+            const fn = validator.validateRuleOptions.bind(null, linter.rules.get("mock-rule"), "mock-rule", [["error"]], "tests");
 
             assert.throws(fn, "tests:\n\tConfiguration for rule \"mock-rule\" is invalid:\n\tSeverity should be one of the following: 0 = off, 1 = warn, 2 = error (you passed '[ \"error\" ]').\n");
         });
 
         it("should only check warning level for nonexistent rules", () => {
-            const fn = validator.validateRuleOptions.bind(null, "non-existent-rule", [3, "foobar"], "tests", linter.rules);
+            const fn = validator.validateRuleOptions.bind(null, linter.rules.get("non-existent-rule"), "non-existent-rule", [3, "foobar"], "tests");
 
             assert.throws(fn, "tests:\n\tConfiguration for rule \"non-existent-rule\" is invalid:\n\tSeverity should be one of the following: 0 = off, 1 = warn, 2 = error (you passed '3').\n");
         });
 
         it("should only check warning level for plugin rules", () => {
-            const fn = validator.validateRuleOptions.bind(null, "plugin/rule", 3, "tests", linter.rules);
+            const fn = validator.validateRuleOptions.bind(null, linter.rules.get("plugin/rule"), "plugin/rule", 3, "tests");
 
             assert.throws(fn, "tests:\n\tConfiguration for rule \"plugin/rule\" is invalid:\n\tSeverity should be one of the following: 0 = off, 1 = warn, 2 = error (you passed '3').\n");
         });
 
         it("should throw for incorrect configuration values", () => {
-            const fn = validator.validateRuleOptions.bind(null, "mock-rule", [2, "frist"], "tests", linter.rules);
+            const fn = validator.validateRuleOptions.bind(null, linter.rules.get("mock-rule"), "mock-rule", [2, "frist"], "tests");
 
             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);
+            const fn = validator.validateRuleOptions.bind(null, linter.rules.get("mock-rule"), "mock-rule", [2, "first", "second"], "tests");
 
             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/formatters/codeframe.js b/tests/lib/formatters/codeframe.js
index 35dc12adf..08260fe20 100644
--- a/tests/lib/formatters/codeframe.js
+++ b/tests/lib/formatters/codeframe.js
@@ -170,6 +170,26 @@ describe("formatter:codeframe", () => {
         });
     });
 
+    describe("when passed a message that ends with ' .'", () => {
+        const code = [{
+            filePath: "foo.js",
+            messages: [{
+                ruleId: "foo",
+                message: "Unexpected .",
+                severity: 2,
+                source: "foo"
+            }],
+            errorCount: 1,
+            warningCount: 0
+        }];
+
+        it("should return a string in the correct format (retaining the ' .')", () => {
+            const result = formatter(code);
+
+            assert.equal(stripAnsi(result), "error: Unexpected . (foo) at foo.js\n\n\n1 error found.");
+        });
+    });
+
     describe("when passed multiple messages", () => {
         const code = [{
             filePath: "foo.js",
diff --git a/tests/lib/formatters/stylish.js b/tests/lib/formatters/stylish.js
index 67a653dc3..8fc035c83 100644
--- a/tests/lib/formatters/stylish.js
+++ b/tests/lib/formatters/stylish.js
@@ -152,6 +152,31 @@ describe("formatter:stylish", () => {
         });
     });
 
+    describe("when passed a message that ends with ' .'", () => {
+        const code = [{
+            filePath: "foo.js",
+            errorCount: 0,
+            warningCount: 1,
+            fixableErrorCount: 0,
+            fixableWarningCount: 0,
+            messages: [{
+                message: "Unexpected .",
+                severity: 1,
+                line: 5,
+                column: 10,
+                ruleId: "foo"
+            }]
+        }];
+
+        it("should return a string in the correct format (retaining the ' .')", () => {
+            const result = formatter(code);
+
+            assert.equal(result, "\nfoo.js\n  5:10  warning  Unexpected .  foo\n\n\u2716 1 problem (0 errors, 1 warning)\n");
+            assert.equal(chalkStub.yellow.bold.callCount, 1);
+            assert.equal(chalkStub.red.bold.callCount, 0);
+        });
+    });
+
     describe("when passed a fatal error message", () => {
         const code = [{
             filePath: "foo.js",
diff --git a/tests/lib/linter.js b/tests/lib/linter.js
index d3f665315..bd91c4ff8 100644
--- a/tests/lib/linter.js
+++ b/tests/lib/linter.js
@@ -102,61 +102,66 @@ describe("Linter", () => {
         });
     });
 
-    describe("getSourceLines()", () => {
+    describe("context.getSourceLines()", () => {
 
         it("should get proper lines when using \\n as a line break", () => {
             const code = "a;\nb;";
+            const spy = sandbox.spy(context => {
+                assert.deepEqual(context.getSourceLines(), ["a;", "b;"]);
+                return {};
+            });
 
-            linter.verify(code, {}, filename, true);
-
-            const lines = linter.getSourceLines();
-
-            assert.equal(lines[0], "a;");
-            assert.equal(lines[1], "b;");
+            linter.defineRule("checker", spy);
+            linter.verify(code, { rules: { checker: "error" } });
+            assert(spy.calledOnce);
         });
 
         it("should get proper lines when using \\r\\n as a line break", () => {
             const code = "a;\r\nb;";
+            const spy = sandbox.spy(context => {
+                assert.deepEqual(context.getSourceLines(), ["a;", "b;"]);
+                return {};
+            });
 
-            linter.verify(code, {}, filename, true);
-
-            const lines = linter.getSourceLines();
-
-            assert.equal(lines[0], "a;");
-            assert.equal(lines[1], "b;");
+            linter.defineRule("checker", spy);
+            linter.verify(code, { rules: { checker: "error" } });
+            assert(spy.calledOnce);
         });
 
         it("should get proper lines when using \\r as a line break", () => {
             const code = "a;\rb;";
+            const spy = sandbox.spy(context => {
+                assert.deepEqual(context.getSourceLines(), ["a;", "b;"]);
+                return {};
+            });
 
-            linter.verify(code, {}, filename, true);
-
-            const lines = linter.getSourceLines();
-
-            assert.equal(lines[0], "a;");
-            assert.equal(lines[1], "b;");
+            linter.defineRule("checker", spy);
+            linter.verify(code, { rules: { checker: "error" } });
+            assert(spy.calledOnce);
         });
 
         it("should get proper lines when using \\u2028 as a line break", () => {
             const code = "a;\u2028b;";
+            const spy = sandbox.spy(context => {
+                assert.deepEqual(context.getSourceLines(), ["a;", "b;"]);
+                return {};
+            });
 
-            linter.verify(code, {}, filename, true);
-
-            const lines = linter.getSourceLines();
-
-            assert.equal(lines[0], "a;");
-            assert.equal(lines[1], "b;");
+            linter.defineRule("checker", spy);
+            linter.verify(code, { rules: { checker: "error" } });
+            assert(spy.calledOnce);
         });
 
         it("should get proper lines when using \\u2029 as a line break", () => {
             const code = "a;\u2029b;";
+            const spy = sandbox.spy(context => {
+                assert.deepEqual(context.getSourceLines(), ["a;", "b;"]);
+                return {};
+            });
 
-            linter.verify(code, {}, filename, true);
-
-            const lines = linter.getSourceLines();
-
-            assert.equal(lines[0], "a;");
-            assert.equal(lines[1], "b;");
+            linter.defineRule("checker", spy);
+            linter.verify(code, { rules: { checker: "error" } });
+            assert(spy.calledOnce);
         });
 
 
@@ -166,7 +171,6 @@ describe("Linter", () => {
         const code = TEST_CODE;
 
         it("should retrieve SourceCode object after reset", () => {
-            linter.reset();
             linter.verify(code, {}, filename, true);
 
             const sourceCode = linter.getSourceCode();
@@ -177,7 +181,6 @@ describe("Linter", () => {
         });
 
         it("should retrieve SourceCode object without reset", () => {
-            linter.reset();
             linter.verify(code, {}, filename);
 
             const sourceCode = linter.getSourceCode();
@@ -189,115 +192,113 @@ describe("Linter", () => {
 
     });
 
-    describe("getSource()", () => {
+    describe("context.getSource()", () => {
         const code = TEST_CODE;
 
         it("should retrieve all text when used without parameters", () => {
 
-            const config = { rules: { checker: "error" } },
-                spy = sandbox.spy(() => {
-                    const source = linter.getSource();
+            const config = { rules: { checker: "error" } };
+            let spy;
 
-                    assert.equal(source, TEST_CODE);
+            linter.defineRule("checker", context => {
+                spy = sandbox.spy(() => {
+                    assert.equal(context.getSource(), TEST_CODE);
                 });
+                return { Program: spy };
+            });
 
-            linter.defineRule("checker", () => ({ Program: spy }));
-
-            linter.verify(code, config, filename, true);
-            assert(spy.calledOnce);
+            linter.verify(code, config);
+            assert(spy && spy.calledOnce);
         });
 
         it("should retrieve all text for root node", () => {
             const config = { rules: { checker: "error" } };
-            const spy = sandbox.spy(node => {
-                const source = linter.getSource(node);
+            let spy;
 
-                assert.equal(source, TEST_CODE);
+            linter.defineRule("checker", context => {
+                spy = sandbox.spy(node => {
+                    assert.equal(context.getSource(node), TEST_CODE);
+                });
+                return { Program: spy };
             });
 
-            linter.defineRule("checker", () => ({ Program: spy }));
-
-            linter.verify(code, config, filename, true);
-            assert(spy.calledOnce);
+            linter.verify(code, config);
+            assert(spy && spy.calledOnce);
         });
 
         it("should clamp to valid range when retrieving characters before start of source", () => {
+            const config = { rules: { checker: "error" } };
+            let spy;
 
-            /**
-             * Callback handler
-             * @param {ASTNode} node node to examine
-             * @returns {void}
-             */
-            function handler(node) {
-                const source = linter.getSource(node, 2, 0);
-
-                assert.equal(source, TEST_CODE);
-            }
-
-            const config = { rules: { checker: "error" } },
-                spy = sandbox.spy(handler);
-
-            linter.defineRule("checker", () => ({ Program: spy }));
+            linter.defineRule("checker", context => {
+                spy = sandbox.spy(node => {
+                    assert.equal(context.getSource(node, 2, 0), TEST_CODE);
+                });
+                return { Program: spy };
+            });
 
-            linter.verify(code, config, filename, true);
-            assert(spy.calledOnce);
+            linter.verify(code, config);
+            assert(spy && spy.calledOnce);
         });
 
         it("should retrieve all text for binary expression", () => {
-            const spy = sandbox.spy(node => {
-                const source = linter.getSource(node);
+            const config = { rules: { checker: "error" } };
+            let spy;
 
-                assert.equal(source, "6 * 7");
+            linter.defineRule("checker", context => {
+                spy = sandbox.spy(node => {
+                    assert.equal(context.getSource(node), "6 * 7");
+                });
+                return { BinaryExpression: spy };
             });
 
-            const config = { rules: { checker: "error" } };
-
-            linter.defineRule("checker", () => ({ BinaryExpression: spy }));
-            linter.verify(code, config, filename, true);
-            assert(spy.calledOnce);
+            linter.verify(code, config);
+            assert(spy && spy.calledOnce);
         });
 
         it("should retrieve all text plus two characters before for binary expression", () => {
             const config = { rules: { checker: "error" } };
+            let spy;
 
-            const spy = sandbox.spy(node => {
-                const source = linter.getSource(node, 2);
-
-                assert.equal(source, "= 6 * 7");
+            linter.defineRule("checker", context => {
+                spy = sandbox.spy(node => {
+                    assert.equal(context.getSource(node, 2), "= 6 * 7");
+                });
+                return { BinaryExpression: spy };
             });
 
-            linter.defineRule("checker", () => ({ BinaryExpression: spy }));
-
-            linter.verify(code, config, filename, true);
-            assert(spy.calledOnce);
+            linter.verify(code, config);
+            assert(spy && spy.calledOnce);
         });
 
         it("should retrieve all text plus one character after for binary expression", () => {
             const config = { rules: { checker: "error" } };
-            const spy = sandbox.spy(node => {
-                const source = linter.getSource(node, 0, 1);
+            let spy;
 
-                assert.strictEqual(source, "6 * 7;");
+            linter.defineRule("checker", context => {
+                spy = sandbox.spy(node => {
+                    assert.strictEqual(context.getSource(node, 0, 1), "6 * 7;");
+                });
+                return { BinaryExpression: spy };
             });
 
-            linter.defineRule("checker", () => ({ BinaryExpression: spy }));
-
-            linter.verify(code, config, filename, true);
-            assert(spy.calledOnce);
+            linter.verify(code, config);
+            assert(spy && spy.calledOnce);
         });
 
         it("should retrieve all text plus two characters before and one character after for binary expression", () => {
             const config = { rules: { checker: "error" } };
-            const spy = sandbox.spy(node => {
-                const source = linter.getSource(node, 2, 1);
+            let spy;
 
-                assert.equal(source, "= 6 * 7;");
+            linter.defineRule("checker", context => {
+                spy = sandbox.spy(node => {
+                    assert.equal(context.getSource(node, 2, 1), "= 6 * 7;");
+                });
+                return { BinaryExpression: spy };
             });
 
-            linter.defineRule("checker", () => ({ BinaryExpression: spy }));
-
-            linter.verify(code, config, filename, true);
-            assert(spy.calledOnce);
+            linter.verify(code, config);
+            assert(spy && spy.calledOnce);
         });
 
     });
@@ -342,126 +343,128 @@ describe("Linter", () => {
         });
     });
 
-    describe("when calling getNodeByRangeIndex", () => {
+    describe("when calling context.getNodeByRangeIndex", () => {
         const code = TEST_CODE;
 
         it("should retrieve a node starting at the given index", () => {
             const config = { rules: { checker: "error" } };
-            const spy = sandbox.spy(() => {
-                const node = linter.getNodeByRangeIndex(4);
-
-                assert.equal(node.type, "Identifier");
+            const spy = sandbox.spy(context => {
+                assert.equal(context.getNodeByRangeIndex(4).type, "Identifier");
+                return {};
             });
 
-            linter.defineRule("checker", () => ({ Program: spy }));
-
+            linter.defineRule("checker", spy);
             linter.verify(code, config);
             assert(spy.calledOnce);
         });
 
         it("should retrieve a node containing the given index", () => {
             const config = { rules: { checker: "error" } };
-            const spy = sandbox.spy(() => {
-                const node = linter.getNodeByRangeIndex(6);
-
-                assert.equal(node.type, "Identifier");
+            const spy = sandbox.spy(context => {
+                assert.equal(context.getNodeByRangeIndex(6).type, "Identifier");
+                return {};
             });
 
-            linter.defineRule("checker", () => ({ Program: spy }));
-
+            linter.defineRule("checker", spy);
             linter.verify(code, config);
             assert(spy.calledOnce);
         });
 
         it("should retrieve a node that is exactly the given index", () => {
             const config = { rules: { checker: "error" } };
-            const spy = sandbox.spy(() => {
-                const node = linter.getNodeByRangeIndex(13);
+            const spy = sandbox.spy(context => {
+                const node = context.getNodeByRangeIndex(13);
 
                 assert.equal(node.type, "Literal");
                 assert.equal(node.value, 6);
+                return {};
             });
 
-            linter.defineRule("checker", () => ({ Program: spy }));
-
+            linter.defineRule("checker", spy);
             linter.verify(code, config);
             assert(spy.calledOnce);
         });
 
         it("should retrieve a node ending with the given index", () => {
             const config = { rules: { checker: "error" } };
-            const spy = sandbox.spy(() => {
-                const node = linter.getNodeByRangeIndex(9);
-
-                assert.equal(node.type, "Identifier");
+            const spy = sandbox.spy(context => {
+                assert.equal(context.getNodeByRangeIndex(9).type, "Identifier");
+                return {};
             });
 
-            linter.defineRule("checker", () => ({ Program: spy }));
-
+            linter.defineRule("checker", spy);
             linter.verify(code, config);
             assert(spy.calledOnce);
         });
 
         it("should retrieve the deepest node containing the given index", () => {
             const config = { rules: { checker: "error" } };
-            const spy = sandbox.spy(() => {
-                let node = linter.getNodeByRangeIndex(14);
+            const spy = sandbox.spy(context => {
+                const node1 = context.getNodeByRangeIndex(14);
+
+                assert.equal(node1.type, "BinaryExpression");
 
-                assert.equal(node.type, "BinaryExpression");
-                node = linter.getNodeByRangeIndex(3);
-                assert.equal(node.type, "VariableDeclaration");
+                const node2 = context.getNodeByRangeIndex(3);
+
+                assert.equal(node2.type, "VariableDeclaration");
+                return {};
             });
 
-            linter.defineRule("checker", () => ({ Program: spy }));
+            linter.defineRule("checker", spy);
             linter.verify(code, config);
             assert(spy.calledOnce);
         });
 
         it("should return null if the index is outside the range of any node", () => {
             const config = { rules: { checker: "error" } };
-            const spy = sandbox.spy(() => {
-                let node = linter.getNodeByRangeIndex(-1);
+            const spy = sandbox.spy(context => {
+                const node1 = context.getNodeByRangeIndex(-1);
+
+                assert.isNull(node1);
+
+                const node2 = context.getNodeByRangeIndex(-99);
 
-                assert.isNull(node);
-                node = linter.getNodeByRangeIndex(-99);
-                assert.isNull(node);
+                assert.isNull(node2);
+                return {};
             });
 
-            linter.defineRule("checker", () => ({ Program: spy }));
+            linter.defineRule("checker", spy);
             linter.verify(code, config);
             assert(spy.calledOnce);
         });
 
         it("should attach the node's parent", () => {
             const config = { rules: { checker: "error" } };
-            const spy = sandbox.spy(() => {
-                const node = linter.getNodeByRangeIndex(14);
+            const spy = sandbox.spy(context => {
+                const node = context.getNodeByRangeIndex(14);
 
                 assert.property(node, "parent");
                 assert.equal(node.parent.type, "VariableDeclarator");
+                return {};
             });
 
-            linter.defineRule("checker", () => ({ Program: spy }));
-
+            linter.defineRule("checker", spy);
             linter.verify(code, config);
             assert(spy.calledOnce);
         });
 
         it("should not modify the node when attaching the parent", () => {
             const config = { rules: { checker: "error" } };
-            const spy = sandbox.spy(() => {
-                let node = linter.getNodeByRangeIndex(10);
+            const spy = sandbox.spy(context => {
+                const node1 = context.getNodeByRangeIndex(10);
 
-                assert.equal(node.type, "VariableDeclarator");
-                node = linter.getNodeByRangeIndex(4);
-                assert.equal(node.type, "Identifier");
-                assert.property(node, "parent");
-                assert.equal(node.parent.type, "VariableDeclarator");
-                assert.notProperty(node.parent, "parent");
-            });
+                assert.equal(node1.type, "VariableDeclarator");
 
-            linter.defineRule("checker", () => ({ Program: spy }));
+                const node2 = context.getNodeByRangeIndex(4);
 
+                assert.equal(node2.type, "Identifier");
+                assert.property(node2, "parent");
+                assert.equal(node2.parent.type, "VariableDeclarator");
+                assert.notProperty(node2.parent, "parent");
+                return {};
+            });
+
+            linter.defineRule("checker", spy);
             linter.verify(code, config);
             assert(spy.calledOnce);
         });
@@ -469,279 +472,353 @@ describe("Linter", () => {
     });
 
 
-    describe("when calling getScope", () => {
+    describe("when calling context.getScope", () => {
         const code = "function foo() { q: for(;;) { break q; } } function bar () { var q = t; } var baz = (() => { return 1; });";
 
         it("should retrieve the global scope correctly from a Program", () => {
             const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } };
-            const spy = sandbox.spy(() => {
-                const scope = linter.getScope();
+            let spy;
 
-                assert.equal(scope.type, "global");
-            });
+            linter.defineRule("checker", context => {
+                spy = sandbox.spy(() => {
+                    const scope = context.getScope();
 
-            linter.defineRule("checker", () => ({ Program: spy }));
+                    assert.equal(scope.type, "global");
+                });
+                return { Program: spy };
+            });
 
             linter.verify(code, config);
-            assert(spy.calledOnce);
+            assert(spy && spy.calledOnce);
         });
 
         it("should retrieve the function scope correctly from a FunctionDeclaration", () => {
             const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } };
-            const spy = sandbox.spy(() => {
-                const scope = linter.getScope();
+            let spy;
 
-                assert.equal(scope.type, "function");
-            });
+            linter.defineRule("checker", context => {
+                spy = sandbox.spy(() => {
+                    const scope = context.getScope();
 
-            linter.defineRule("checker", () => ({ FunctionDeclaration: spy }));
+                    assert.equal(scope.type, "function");
+                });
+                return { FunctionDeclaration: spy };
+            });
 
             linter.verify(code, config);
-            assert(spy.calledTwice);
+            assert(spy && spy.calledTwice);
         });
 
         it("should retrieve the function scope correctly from a LabeledStatement", () => {
             const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } };
-            const spy = sandbox.spy(() => {
-                const scope = linter.getScope();
+            let spy;
 
-                assert.equal(scope.type, "function");
-                assert.equal(scope.block.id.name, "foo");
-            });
+            linter.defineRule("checker", context => {
+                spy = sandbox.spy(() => {
+                    const scope = context.getScope();
 
-            linter.defineRule("checker", () => ({ LabeledStatement: spy }));
+                    assert.equal(scope.type, "function");
+                    assert.equal(scope.block.id.name, "foo");
+                });
+                return { LabeledStatement: spy };
+            });
 
             linter.verify(code, config);
-            assert(spy.calledOnce);
+            assert(spy && spy.calledOnce);
         });
 
         it("should retrieve the function scope correctly from within an ArrowFunctionExpression", () => {
             const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } };
-            const spy = sandbox.spy(() => {
-                const scope = linter.getScope();
+            let spy;
 
-                assert.equal(scope.type, "function");
-                assert.equal(scope.block.type, "ArrowFunctionExpression");
-            });
+            linter.defineRule("checker", context => {
+                spy = sandbox.spy(() => {
+                    const scope = context.getScope();
+
+                    assert.equal(scope.type, "function");
+                    assert.equal(scope.block.type, "ArrowFunctionExpression");
+                });
 
-            linter.defineRule("checker", () => ({ ReturnStatement: spy }));
+                return { ReturnStatement: spy };
+            });
 
             linter.verify(code, config);
-            assert(spy.calledOnce);
+            assert(spy && spy.calledOnce);
         });
 
         it("should retrieve the function scope correctly from within an SwitchStatement", () => {
             const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } };
-            const spy = sandbox.spy(() => {
-                const scope = linter.getScope();
+            let spy;
 
-                assert.equal(scope.type, "switch");
-                assert.equal(scope.block.type, "SwitchStatement");
-            });
+            linter.defineRule("checker", context => {
+                spy = sandbox.spy(() => {
+                    const scope = context.getScope();
+
+                    assert.equal(scope.type, "switch");
+                    assert.equal(scope.block.type, "SwitchStatement");
+                });
 
-            linter.defineRule("checker", () => ({ SwitchStatement: spy }));
+                return { SwitchStatement: spy };
+            });
 
             linter.verify("switch(foo){ case 'a': var b = 'foo'; }", config);
-            assert(spy.calledOnce);
+            assert(spy && spy.calledOnce);
         });
 
         it("should retrieve the function scope correctly from within a BlockStatement", () => {
             const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } };
-            const spy = sandbox.spy(() => {
-                const scope = linter.getScope();
+            let spy;
 
-                assert.equal(scope.type, "block");
-                assert.equal(scope.block.type, "BlockStatement");
-            });
+            linter.defineRule("checker", context => {
+                spy = sandbox.spy(() => {
+                    const scope = context.getScope();
 
-            linter.defineRule("checker", () => ({ BlockStatement: spy }));
+                    assert.equal(scope.type, "block");
+                    assert.equal(scope.block.type, "BlockStatement");
+                });
+
+                return { BlockStatement: spy };
+            });
 
             linter.verify("var x; {let y = 1}", config);
-            assert(spy.calledOnce);
+            assert(spy && spy.calledOnce);
         });
 
         it("should retrieve the function scope correctly from within a nested block statement", () => {
             const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } };
-            const spy = sandbox.spy(() => {
-                const scope = linter.getScope();
+            let spy;
+
+            linter.defineRule("checker", context => {
+                spy = sandbox.spy(() => {
+                    const scope = context.getScope();
 
-                assert.equal(scope.type, "block");
-                assert.equal(scope.block.type, "BlockStatement");
+                    assert.equal(scope.type, "block");
+                    assert.equal(scope.block.type, "BlockStatement");
+                });
+
+                return { BlockStatement: spy };
             });
 
-            linter.defineRule("checker", () => ({ BlockStatement: spy }));
             linter.verify("if (true) { let x = 1 }", config);
-            assert(spy.calledOnce);
+            assert(spy && spy.calledOnce);
         });
 
         it("should retrieve the function scope correctly from within a FunctionDeclaration", () => {
             const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } };
-            const spy = sandbox.spy(() => {
-                const scope = linter.getScope();
+            let spy;
+
+            linter.defineRule("checker", context => {
+                spy = sandbox.spy(() => {
+                    const scope = context.getScope();
 
-                assert.equal(scope.type, "function");
-                assert.equal(scope.block.type, "FunctionDeclaration");
+                    assert.equal(scope.type, "function");
+                    assert.equal(scope.block.type, "FunctionDeclaration");
+                });
+
+                return { FunctionDeclaration: spy };
             });
 
-            linter.defineRule("checker", () => ({ FunctionDeclaration: spy }));
             linter.verify("function foo() {}", config);
-            assert(spy.calledOnce);
+            assert(spy && spy.calledOnce);
         });
 
         it("should retrieve the function scope correctly from within a FunctionExpression", () => {
             const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } };
-            const spy = sandbox.spy(() => {
-                const scope = linter.getScope();
+            let spy;
+
+            linter.defineRule("checker", context => {
+                spy = sandbox.spy(() => {
+                    const scope = context.getScope();
 
-                assert.equal(scope.type, "function");
-                assert.equal(scope.block.type, "FunctionExpression");
+                    assert.equal(scope.type, "function");
+                    assert.equal(scope.block.type, "FunctionExpression");
+                });
+
+                return { FunctionExpression: spy };
             });
 
-            linter.defineRule("checker", () => ({ FunctionExpression: spy }));
             linter.verify("(function foo() {})();", config);
-            assert(spy.calledOnce);
+            assert(spy && spy.calledOnce);
         });
 
         it("should retrieve the catch scope correctly from within a CatchClause", () => {
             const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } };
-            const spy = sandbox.spy(() => {
-                const scope = linter.getScope();
+            let spy;
+
+            linter.defineRule("checker", context => {
+                spy = sandbox.spy(() => {
+                    const scope = context.getScope();
 
-                assert.equal(scope.type, "catch");
-                assert.equal(scope.block.type, "CatchClause");
+                    assert.equal(scope.type, "catch");
+                    assert.equal(scope.block.type, "CatchClause");
+                });
+
+                return { CatchClause: spy };
             });
 
-            linter.defineRule("checker", () => ({ CatchClause: spy }));
             linter.verify("try {} catch (err) {}", config);
-            assert(spy.calledOnce);
+            assert(spy && spy.calledOnce);
         });
 
         it("should retrieve module scope correctly from an ES6 module", () => {
             const config = { rules: { checker: "error" }, parserOptions: { sourceType: "module" } };
-            const spy = sandbox.spy(() => {
-                const scope = linter.getScope();
+            let spy;
 
-                assert.equal(scope.type, "module");
-            });
+            linter.defineRule("checker", context => {
+                spy = sandbox.spy(() => {
+                    const scope = context.getScope();
 
-            linter.defineRule("checker", () => ({ AssignmentExpression: spy }));
+                    assert.equal(scope.type, "module");
+                });
+
+                return { AssignmentExpression: spy };
+            });
 
             linter.verify("var foo = {}; foo.bar = 1;", config);
-            assert(spy.calledOnce);
+            assert(spy && spy.calledOnce);
         });
 
         it("should retrieve function scope correctly when globalReturn is true", () => {
             const config = { rules: { checker: "error" }, parserOptions: { ecmaFeatures: { globalReturn: true } } };
-            const spy = sandbox.spy(() => {
-                const scope = linter.getScope();
+            let spy;
 
-                assert.equal(scope.type, "function");
-            });
+            linter.defineRule("checker", context => {
+                spy = sandbox.spy(() => {
+                    const scope = context.getScope();
+
+                    assert.equal(scope.type, "function");
+                });
 
-            linter.defineRule("checker", () => ({ AssignmentExpression: spy }));
+                return { AssignmentExpression: spy };
+            });
 
             linter.verify("var foo = {}; foo.bar = 1;", config);
-            assert(spy.calledOnce);
+            assert(spy && spy.calledOnce);
         });
     });
 
     describe("marking variables as used", () => {
         it("should mark variables in current scope as used", () => {
             const code = "var a = 1, b = 2;";
-            const spy = sandbox.spy(() => {
-                assert.isTrue(linter.markVariableAsUsed("a"));
+            let spy;
+
+            linter.defineRule("checker", context => {
+                spy = sandbox.spy(() => {
+                    assert.isTrue(context.markVariableAsUsed("a"));
 
-                const scope = linter.getScope();
+                    const scope = context.getScope();
+
+                    assert.isTrue(getVariable(scope, "a").eslintUsed);
+                    assert.notOk(getVariable(scope, "b").eslintUsed);
+                });
 
-                assert.isTrue(getVariable(scope, "a").eslintUsed);
-                assert.notOk(getVariable(scope, "b").eslintUsed);
+                return { "Program:exit": spy };
             });
 
-            linter.defineRule("checker", () => ({ "Program:exit": spy }));
             linter.verify(code, { rules: { checker: "error" } });
-            assert(spy.calledOnce);
+            assert(spy && spy.calledOnce);
         });
         it("should mark variables in function args as used", () => {
             const code = "function abc(a, b) { return 1; }";
-            const spy = sandbox.spy(() => {
-                assert.isTrue(linter.markVariableAsUsed("a"));
+            let spy;
 
-                const scope = linter.getScope();
+            linter.defineRule("checker", context => {
+                spy = sandbox.spy(() => {
+                    assert.isTrue(context.markVariableAsUsed("a"));
 
-                assert.isTrue(getVariable(scope, "a").eslintUsed);
-                assert.notOk(getVariable(scope, "b").eslintUsed);
-            });
+                    const scope = context.getScope();
 
-            linter.defineRule("checker", () => ({ ReturnStatement: spy }));
+                    assert.isTrue(getVariable(scope, "a").eslintUsed);
+                    assert.notOk(getVariable(scope, "b").eslintUsed);
+                });
 
+                return { ReturnStatement: spy };
+            });
 
             linter.verify(code, { rules: { checker: "error" } });
-            assert(spy.calledOnce);
+            assert(spy && spy.calledOnce);
         });
         it("should mark variables in higher scopes as used", () => {
             const code = "var a, b; function abc() { return 1; }";
-            const returnSpy = sandbox.spy(() => {
-                assert.isTrue(linter.markVariableAsUsed("a"));
-            });
-            const exitSpy = sandbox.spy(() => {
-                const scope = linter.getScope();
+            let returnSpy, exitSpy;
 
-                assert.isTrue(getVariable(scope, "a").eslintUsed);
-                assert.notOk(getVariable(scope, "b").eslintUsed);
-            });
+            linter.defineRule("checker", context => {
+                returnSpy = sandbox.spy(() => {
+                    assert.isTrue(context.markVariableAsUsed("a"));
+                });
+                exitSpy = sandbox.spy(() => {
+                    const scope = context.getScope();
+
+                    assert.isTrue(getVariable(scope, "a").eslintUsed);
+                    assert.notOk(getVariable(scope, "b").eslintUsed);
+                });
 
-            linter.defineRule("checker", () => ({ ReturnStatement: returnSpy, "Program:exit": exitSpy }));
+                return { ReturnStatement: returnSpy, "Program:exit": exitSpy };
+            });
 
             linter.verify(code, { rules: { checker: "error" } });
-            assert(returnSpy.calledOnce);
-            assert(exitSpy.calledOnce);
+            assert(returnSpy && returnSpy.calledOnce);
+            assert(exitSpy && exitSpy.calledOnce);
         });
 
         it("should mark variables in Node.js environment as used", () => {
             const code = "var a = 1, b = 2;";
-            const spy = sandbox.spy(() => {
-                const globalScope = linter.getScope(),
-                    childScope = globalScope.childScopes[0];
+            let spy;
+
+            linter.defineRule("checker", context => {
+                spy = sandbox.spy(() => {
+                    const globalScope = context.getScope(),
+                        childScope = globalScope.childScopes[0];
 
-                assert.isTrue(linter.markVariableAsUsed("a"));
+                    assert.isTrue(context.markVariableAsUsed("a"));
 
-                assert.isTrue(getVariable(childScope, "a").eslintUsed);
-                assert.isUndefined(getVariable(childScope, "b").eslintUsed);
-            });
+                    assert.isTrue(getVariable(childScope, "a").eslintUsed);
+                    assert.isUndefined(getVariable(childScope, "b").eslintUsed);
+                });
 
-            linter.defineRule("checker", () => ({ "Program:exit": spy }));
+                return { "Program:exit": spy };
+            });
 
             linter.verify(code, { rules: { checker: "error" }, env: { node: true } });
-            assert(spy.calledOnce);
+            assert(spy && spy.calledOnce);
         });
 
         it("should mark variables in modules as used", () => {
             const code = "var a = 1, b = 2;";
-            const spy = sandbox.spy(() => {
-                const globalScope = linter.getScope(),
-                    childScope = globalScope.childScopes[0];
+            let spy;
+
+            linter.defineRule("checker", context => {
+                spy = sandbox.spy(() => {
+                    const globalScope = context.getScope(),
+                        childScope = globalScope.childScopes[0];
 
-                assert.isTrue(linter.markVariableAsUsed("a"));
+                    assert.isTrue(context.markVariableAsUsed("a"));
+
+                    assert.isTrue(getVariable(childScope, "a").eslintUsed);
+                    assert.isUndefined(getVariable(childScope, "b").eslintUsed);
+                });
 
-                assert.isTrue(getVariable(childScope, "a").eslintUsed);
-                assert.isUndefined(getVariable(childScope, "b").eslintUsed);
+                return { "Program:exit": spy };
             });
 
-            linter.defineRule("checker", () => ({ "Program:exit": spy }));
             linter.verify(code, { rules: { checker: "error" }, parserOptions: { sourceType: "module" } }, filename, true);
-            assert(spy.calledOnce);
+            assert(spy && spy.calledOnce);
         });
 
         it("should return false if the given variable is not found", () => {
             const code = "var a = 1, b = 2;";
-            const spy = sandbox.spy(() => {
-                assert.isFalse(linter.markVariableAsUsed("c"));
-            });
+            let spy;
+
+            linter.defineRule("checker", context => {
+                spy = sandbox.spy(() => {
+                    assert.isFalse(context.markVariableAsUsed("c"));
+                });
 
-            linter.defineRule("checker", () => ({ "Program:exit": spy }));
+                return { "Program:exit": spy };
+            });
 
             linter.verify(code, { rules: { checker: "error" } });
-            assert(spy.calledOnce);
+            assert(spy && spy.calledOnce);
         });
     });
 
@@ -781,7 +858,6 @@ describe("Linter", () => {
         const code = "test-rule";
 
         it("should pass settings to all rules", () => {
-            linter.reset();
             linter.defineRule(code, context => ({
                 Literal(node) {
                     context.report(node, context.settings.info);
@@ -799,7 +875,6 @@ describe("Linter", () => {
         });
 
         it("should not have any settings if they were not passed in", () => {
-            linter.reset();
             linter.defineRule(code, context => ({
                 Literal(node) {
                     if (Object.getOwnPropertyNames(context.settings).length !== 0) {
@@ -829,7 +904,6 @@ describe("Linter", () => {
                 }
             };
 
-            linter.reset();
             linter.defineRule("test-rule", sandbox.mock().withArgs(
                 sinon.match({ parserOptions })
             ).returns({}));
@@ -843,7 +917,6 @@ describe("Linter", () => {
 
             const parserOptions = {};
 
-            linter.reset();
             linter.defineRule("test-rule", sandbox.mock().withArgs(
                 sinon.match({ parserOptions })
             ).returns({}));
@@ -863,7 +936,6 @@ describe("Linter", () => {
 
                 const alternateParser = "esprima-fb";
 
-                linter.reset();
                 linter.defineRule("test-rule", sandbox.mock().withArgs(
                     sinon.match({ parserPath: alternateParser })
                 ).returns({}));
@@ -876,9 +948,6 @@ describe("Linter", () => {
             it("should use parseForESLint() in custom parser when custom parser is specified", () => {
 
                 const alternateParser = path.resolve(__dirname, "../fixtures/parsers/enhanced-parser.js");
-
-                linter.reset();
-
                 const config = { rules: {}, parser: alternateParser };
                 const messages = linter.verify("0", config, filename);
 
@@ -889,7 +958,6 @@ describe("Linter", () => {
 
                 const alternateParser = path.resolve(__dirname, "../fixtures/parsers/enhanced-parser.js");
 
-                linter.reset();
                 linter.defineRule("test-service-rule", context => ({
                     Literal(node) {
                         context.report({
@@ -928,7 +996,6 @@ describe("Linter", () => {
                 config = { rules: {} };
 
             config.rules[rule] = 1;
-            linter.reset();
 
             const messages = linter.verify(code, config, filename, true);
 
@@ -941,7 +1008,6 @@ describe("Linter", () => {
                 config = { rules: {} };
 
             config.rules[rule] = "warn";
-            linter.reset();
 
             const messages = linter.verify(code, config, filename, true);
 
@@ -955,7 +1021,6 @@ describe("Linter", () => {
                 config = { rules: {} };
 
             config.rules[rule] = [1];
-            linter.reset();
 
             const messages = linter.verify(code, config, filename, true);
 
@@ -968,7 +1033,6 @@ describe("Linter", () => {
                 config = { rules: {} };
 
             config.rules[rule] = ["warn"];
-            linter.reset();
 
             const messages = linter.verify(code, config, filename, true);
 
@@ -982,7 +1046,6 @@ describe("Linter", () => {
                 config = { rules: {} };
 
             config.rules[rule] = "1";
-            linter.reset();
 
             const messages = linter.verify(code, config, filename, true);
 
@@ -991,39 +1054,9 @@ describe("Linter", () => {
 
         it("should process empty config", () => {
             const config = {};
-
-            linter.reset();
-            const messages = linter.verify(code, config, filename, true);
-
-            assert.equal(messages.length, 0);
-        });
-    });
-
-    describe("after calling reset()", () => {
-        const code = TEST_CODE;
-
-        it("text should not be available", () => {
-            const config = { rules: {} };
-
-            linter.reset();
             const messages = linter.verify(code, config, filename, true);
 
-            linter.reset();
-
-            assert.equal(messages.length, 0);
-            assert.isNull(linter.getSource());
-        });
-
-        it("source for nodes should not be available", () => {
-            const config = { rules: {} };
-
-            linter.reset();
-            const messages = linter.verify(code, config, filename, true);
-
-            linter.reset();
-
             assert.equal(messages.length, 0);
-            assert.isNull(linter.getSource({}));
         });
     });
 
@@ -1032,26 +1065,31 @@ describe("Linter", () => {
 
         it("variables should be available in global scope", () => {
             const config = { rules: { checker: "error" } };
-            const spy = sandbox.spy(() => {
-                const scope = linter.getScope();
-                const a = getVariable(scope, "a"),
-                    b = getVariable(scope, "b"),
-                    c = getVariable(scope, "c"),
-                    d = getVariable(scope, "d");
-
-                assert.equal(a.name, "a");
-                assert.equal(a.writeable, false);
-                assert.equal(b.name, "b");
-                assert.equal(b.writeable, true);
-                assert.equal(c.name, "c");
-                assert.equal(c.writeable, false);
-                assert.equal(d.name, "d");
-                assert.equal(d.writeable, true);
-            });
-
-            linter.defineRule("checker", () => ({ Program: spy }));
+            let spy;
+
+            linter.defineRule("checker", context => {
+                spy = sandbox.spy(() => {
+                    const scope = context.getScope();
+                    const a = getVariable(scope, "a"),
+                        b = getVariable(scope, "b"),
+                        c = getVariable(scope, "c"),
+                        d = getVariable(scope, "d");
+
+                    assert.equal(a.name, "a");
+                    assert.equal(a.writeable, false);
+                    assert.equal(b.name, "b");
+                    assert.equal(b.writeable, true);
+                    assert.equal(c.name, "c");
+                    assert.equal(c.writeable, false);
+                    assert.equal(d.name, "d");
+                    assert.equal(d.writeable, true);
+                });
+
+                return { Program: spy };
+            });
+
             linter.verify(code, config);
-            assert(spy.calledOnce);
+            assert(spy && spy.calledOnce);
         });
     });
 
@@ -1060,23 +1098,28 @@ describe("Linter", () => {
 
         it("variables should be available in global scope", () => {
             const config = { rules: { checker: "error" } };
-            const spy = sandbox.spy(() => {
-                const scope = linter.getScope(),
-                    a = getVariable(scope, "a"),
-                    b = getVariable(scope, "b"),
-                    c = getVariable(scope, "c");
+            let spy;
+
+            linter.defineRule("checker", context => {
+                spy = sandbox.spy(() => {
+                    const scope = context.getScope(),
+                        a = getVariable(scope, "a"),
+                        b = getVariable(scope, "b"),
+                        c = getVariable(scope, "c");
+
+                    assert.equal(a.name, "a");
+                    assert.equal(a.writeable, false);
+                    assert.equal(b.name, "b");
+                    assert.equal(b.writeable, true);
+                    assert.equal(c.name, "c");
+                    assert.equal(c.writeable, false);
+                });
 
-                assert.equal(a.name, "a");
-                assert.equal(a.writeable, false);
-                assert.equal(b.name, "b");
-                assert.equal(b.writeable, true);
-                assert.equal(c.name, "c");
-                assert.equal(c.writeable, false);
+                return { Program: spy };
             });
 
-            linter.defineRule("checker", () => ({ Program: spy }));
             linter.verify(code, config);
-            assert(spy.calledOnce);
+            assert(spy && spy.calledOnce);
         });
     });
 
@@ -1084,18 +1127,23 @@ describe("Linter", () => {
         it("variables should be available in global scope", () => {
             const code = "/*eslint-env node*/ function f() {} /*eslint-env browser, foo*/";
             const config = { rules: { checker: "error" } };
-            const spy = sandbox.spy(() => {
-                const scope = linter.getScope(),
-                    exports = getVariable(scope, "exports"),
-                    window = getVariable(scope, "window");
+            let spy;
+
+            linter.defineRule("checker", context => {
+                spy = sandbox.spy(() => {
+                    const scope = context.getScope(),
+                        exports = getVariable(scope, "exports"),
+                        window = getVariable(scope, "window");
 
-                assert.equal(exports.writeable, true);
-                assert.equal(window.writeable, false);
+                    assert.equal(exports.writeable, true);
+                    assert.equal(window.writeable, false);
+                });
+
+                return { Program: spy };
             });
 
-            linter.defineRule("checker", () => ({ Program: spy }));
             linter.verify(code, config);
-            assert(spy.calledOnce);
+            assert(spy && spy.calledOnce);
         });
     });
 
@@ -1104,18 +1152,23 @@ describe("Linter", () => {
 
         it("variables should be available in global scope", () => {
             const config = { rules: { checker: "error" } };
-            const spy = sandbox.spy(() => {
-                const scope = linter.getScope(),
-                    exports = getVariable(scope, "exports"),
-                    window = getVariable(scope, "window");
+            let spy;
+
+            linter.defineRule("checker", context => {
+                spy = sandbox.spy(() => {
+                    const scope = context.getScope(),
+                        exports = getVariable(scope, "exports"),
+                        window = getVariable(scope, "window");
+
+                    assert.equal(exports.writeable, true);
+                    assert.equal(window, null);
+                });
 
-                assert.equal(exports.writeable, true);
-                assert.equal(window, null);
+                return { Program: spy };
             });
 
-            linter.defineRule("checker", () => ({ Program: spy }));
             linter.verify(code, config);
-            assert(spy.calledOnce);
+            assert(spy && spy.calledOnce);
         });
     });
 
@@ -1131,77 +1184,101 @@ describe("Linter", () => {
         it("variables should be exported", () => {
             const code = "/* exported horse */\n\nvar horse = 'circus'";
             const config = { rules: { checker: "error" } };
-            const spy = sandbox.spy(() => {
-                const scope = linter.getScope(),
-                    horse = getVariable(scope, "horse");
+            let spy;
 
-                assert.equal(horse.eslintUsed, true);
-            });
+            linter.defineRule("checker", context => {
+                spy = sandbox.spy(() => {
+                    const scope = context.getScope(),
+                        horse = getVariable(scope, "horse");
+
+                    assert.equal(horse.eslintUsed, true);
+                });
 
-            linter.defineRule("checker", () => ({ Program: spy }));
+                return { Program: spy };
+            });
 
             linter.verify(code, config);
-            assert(spy.calledOnce);
+            assert(spy && spy.calledOnce);
         });
 
         it("undefined variables should not be exported", () => {
             const code = "/* exported horse */\n\nhorse = 'circus'";
             const config = { rules: { checker: "error" } };
-            const spy = sandbox.spy(() => {
-                const scope = linter.getScope(),
-                    horse = getVariable(scope, "horse");
+            let spy;
+
+            linter.defineRule("checker", context => {
+                spy = sandbox.spy(() => {
+                    const scope = context.getScope(),
+                        horse = getVariable(scope, "horse");
 
-                assert.equal(horse, null);
+                    assert.equal(horse, null);
+                });
+
+                return { Program: spy };
             });
 
-            linter.defineRule("checker", () => ({ Program: spy }));
             linter.verify(code, config);
-            assert(spy.calledOnce);
+            assert(spy && spy.calledOnce);
         });
 
         it("variables should be exported in strict mode", () => {
             const code = "/* exported horse */\n'use strict';\nvar horse = 'circus'";
             const config = { rules: { checker: "error" } };
-            const spy = sandbox.spy(() => {
-                const scope = linter.getScope(),
-                    horse = getVariable(scope, "horse");
+            let spy;
+
+            linter.defineRule("checker", context => {
+                spy = sandbox.spy(() => {
+                    const scope = context.getScope(),
+                        horse = getVariable(scope, "horse");
+
+                    assert.equal(horse.eslintUsed, true);
+                });
 
-                assert.equal(horse.eslintUsed, true);
+                return { Program: spy };
             });
 
-            linter.defineRule("checker", () => ({ Program: spy }));
             linter.verify(code, config);
-            assert(spy.calledOnce);
+            assert(spy && spy.calledOnce);
         });
 
         it("variables should not be exported in the es6 module environment", () => {
             const code = "/* exported horse */\nvar horse = 'circus'";
             const config = { rules: { checker: "error" }, parserOptions: { sourceType: "module" } };
-            const spy = sandbox.spy(() => {
-                const scope = linter.getScope(),
-                    horse = getVariable(scope, "horse");
+            let spy;
+
+            linter.defineRule("checker", context => {
+                spy = sandbox.spy(() => {
+                    const scope = context.getScope(),
+                        horse = getVariable(scope, "horse");
+
+                    assert.equal(horse, null); // there is no global scope at all
+                });
 
-                assert.equal(horse, null); // there is no global scope at all
+                return { Program: spy };
             });
 
-            linter.defineRule("checker", () => ({ Program: spy }));
             linter.verify(code, config);
-            assert(spy.calledOnce);
+            assert(spy && spy.calledOnce);
         });
 
         it("variables should not be exported when in the node environment", () => {
             const code = "/* exported horse */\nvar horse = 'circus'";
             const config = { rules: { checker: "error" }, env: { node: true } };
-            const spy = sandbox.spy(() => {
-                const scope = linter.getScope(),
-                    horse = getVariable(scope, "horse");
+            let spy;
+
+            linter.defineRule("checker", context => {
+                spy = sandbox.spy(() => {
+                    const scope = context.getScope(),
+                        horse = getVariable(scope, "horse");
+
+                    assert.equal(horse, null); // there is no global scope at all
+                });
 
-                assert.equal(horse, null); // there is no global scope at all
+                return { Program: spy };
             });
 
-            linter.defineRule("checker", () => ({ Program: spy }));
             linter.verify(code, config);
-            assert(spy.calledOnce);
+            assert(spy && spy.calledOnce);
         });
     });
 
@@ -1210,15 +1287,20 @@ describe("Linter", () => {
 
         it("should not introduce a global variable", () => {
             const config = { rules: { checker: "error" } };
-            const spy = sandbox.spy(() => {
-                const scope = linter.getScope();
+            let spy;
+
+            linter.defineRule("checker", context => {
+                spy = sandbox.spy(() => {
+                    const scope = context.getScope();
 
-                assert.equal(getVariable(scope, "a"), null);
+                    assert.equal(getVariable(scope, "a"), null);
+                });
+
+                return { Program: spy };
             });
 
-            linter.defineRule("checker", () => ({ Program: spy }));
             linter.verify(code, config);
-            assert(spy.calledOnce);
+            assert(spy && spy.calledOnce);
         });
     });
 
@@ -1227,18 +1309,23 @@ describe("Linter", () => {
 
         it("should not introduce a global variable", () => {
             const config = { rules: { checker: "error" } };
-            const spy = sandbox.spy(() => {
-                const scope = linter.getScope();
+            let spy;
+
+            linter.defineRule("checker", context => {
+                spy = sandbox.spy(() => {
+                    const scope = context.getScope();
+
+                    assert.equal(getVariable(scope, "a"), null);
+                    assert.equal(getVariable(scope, "b"), null);
+                    assert.equal(getVariable(scope, "foo"), null);
+                    assert.equal(getVariable(scope, "c"), null);
+                });
 
-                assert.equal(getVariable(scope, "a"), null);
-                assert.equal(getVariable(scope, "b"), null);
-                assert.equal(getVariable(scope, "foo"), null);
-                assert.equal(getVariable(scope, "c"), null);
+                return { Program: spy };
             });
 
-            linter.defineRule("checker", () => ({ Program: spy }));
             linter.verify(code, config);
-            assert(spy.calledOnce);
+            assert(spy && spy.calledOnce);
         });
     });
 
@@ -1247,58 +1334,62 @@ describe("Linter", () => {
 
         it("builtin global variables should be available in the global scope", () => {
             const config = { rules: { checker: "error" } };
-            const spy = sandbox.spy(() => {
-                const scope = linter.getScope();
+            let spy;
+
+            linter.defineRule("checker", context => {
+                spy = sandbox.spy(() => {
+                    const scope = context.getScope();
+
+                    assert.notEqual(getVariable(scope, "Object"), null);
+                    assert.notEqual(getVariable(scope, "Array"), null);
+                    assert.notEqual(getVariable(scope, "undefined"), null);
+                });
 
-                assert.notEqual(getVariable(scope, "Object"), null);
-                assert.notEqual(getVariable(scope, "Array"), null);
-                assert.notEqual(getVariable(scope, "undefined"), null);
+                return { Program: spy };
             });
 
-            linter.defineRule("checker", () => ({ Program: spy }));
             linter.verify(code, config);
-            assert(spy.calledOnce);
+            assert(spy && spy.calledOnce);
         });
 
         it("ES6 global variables should not be available by default", () => {
             const config = { rules: { checker: "error" } };
-            const spy = sandbox.spy(() => {
-                const scope = linter.getScope();
+            let spy;
 
-                assert.equal(getVariable(scope, "Promise"), null);
-                assert.equal(getVariable(scope, "Symbol"), null);
-                assert.equal(getVariable(scope, "WeakMap"), null);
+            linter.defineRule("checker", context => {
+                spy = sandbox.spy(() => {
+                    const scope = context.getScope();
+
+                    assert.equal(getVariable(scope, "Promise"), null);
+                    assert.equal(getVariable(scope, "Symbol"), null);
+                    assert.equal(getVariable(scope, "WeakMap"), null);
+                });
+
+                return { Program: spy };
             });
 
-            linter.defineRule("checker", () => ({ Program: spy }));
             linter.verify(code, config);
-            assert(spy.calledOnce);
+            assert(spy && spy.calledOnce);
         });
 
         it("ES6 global variables should be available in the es6 environment", () => {
             const config = { rules: { checker: "error" }, env: { es6: true } };
-            const spy = sandbox.spy(() => {
-                const scope = linter.getScope();
+            let spy;
 
-                assert.notEqual(getVariable(scope, "Promise"), null);
-                assert.notEqual(getVariable(scope, "Symbol"), null);
-                assert.notEqual(getVariable(scope, "WeakMap"), null);
-            });
+            linter.defineRule("checker", context => {
+                spy = sandbox.spy(() => {
+                    const scope = context.getScope();
 
-            linter.defineRule("checker", () => ({ Program: spy }));
-            linter.verify(code, config);
-            assert(spy.calledOnce);
-        });
-    });
+                    assert.notEqual(getVariable(scope, "Promise"), null);
+                    assert.notEqual(getVariable(scope, "Symbol"), null);
+                    assert.notEqual(getVariable(scope, "WeakMap"), null);
+                });
 
-    describe("when evaluating empty code", () => {
-        const code = "",
-            config = { rules: {} };
+                return { Program: spy };
+            });
 
-        it("getSource() should return an empty string", () => {
-            linter.reset();
-            linter.verify(code, config, filename, true);
-            assert.equal(linter.getSource(), "");
+            linter.verify(code, config);
+            assert(spy && spy.calledOnce);
         });
     });
 
@@ -1306,7 +1397,6 @@ describe("Linter", () => {
         const code = "new-rule";
 
         it("can add a rule dynamically", () => {
-            linter.reset();
             linter.defineRule(code, context => ({
                 Literal(node) {
                     context.report(node, "message");
@@ -1329,7 +1419,6 @@ describe("Linter", () => {
         const code = ["new-rule-0", "new-rule-1"];
 
         it("can add multiple rules dynamically", () => {
-            linter.reset();
             const config = { rules: {} };
             const newRules = {};
 
@@ -1361,7 +1450,6 @@ describe("Linter", () => {
         const code = "filename-rule";
 
         it("has access to the filename", () => {
-            linter.reset();
             linter.defineRule(code, context => ({
                 Literal(node) {
                     context.report(node, context.getFilename());
@@ -1378,7 +1466,6 @@ describe("Linter", () => {
         });
 
         it("defaults filename to '<input>'", () => {
-            linter.reset();
             linter.defineRule(code, context => ({
                 Literal(node) {
                     context.report(node, context.getFilename());
@@ -1413,8 +1500,6 @@ describe("Linter", () => {
             const config = { rules: { strict: 2 } };
             const codeA = "/*eslint strict: 0*/ function bar() { return 2; }";
             const codeB = "function foo() { return 1; }";
-
-            linter.reset();
             let messages = linter.verify(codeA, config, filename, false);
 
             assert.equal(messages.length, 0);
@@ -1427,8 +1512,6 @@ describe("Linter", () => {
             const config = { rules: { quotes: [2, "double"] } };
             const codeA = "/*eslint quotes: 0*/ function bar() { return '2'; }";
             const codeB = "function foo() { return '1'; }";
-
-            linter.reset();
             let messages = linter.verify(codeA, config, filename, false);
 
             assert.equal(messages.length, 0);
@@ -1441,8 +1524,6 @@ describe("Linter", () => {
             const config = { rules: { quotes: [2, "double"] } };
             const codeA = "/*eslint quotes: [0, \"single\"]*/ function bar() { return '2'; }";
             const codeB = "function foo() { return '1'; }";
-
-            linter.reset();
             let messages = linter.verify(codeA, config, filename, false);
 
             assert.equal(messages.length, 0);
@@ -1455,8 +1536,6 @@ describe("Linter", () => {
             const config = { rules: { "no-unused-vars": [2, { vars: "all" }] } };
             const codeA = "/*eslint no-unused-vars: [0, {\"vars\": \"local\"}]*/ var a = 44;";
             const codeB = "var b = 55;";
-
-            linter.reset();
             let messages = linter.verify(codeA, config, filename, false);
 
             assert.equal(messages.length, 0);
@@ -1582,8 +1661,6 @@ describe("Linter", () => {
             const config = { rules: { "test-plugin/test-rule": 2 } };
             const codeA = "/*eslint test-plugin/test-rule: 0*/ var a = \"trigger violation\";";
             const codeB = "var a = \"trigger violation\";";
-
-            linter.reset();
             let messages = linter.verify(codeA, config, filename, false);
 
             assert.equal(messages.length, 0);
@@ -2308,14 +2385,15 @@ describe("Linter", () => {
 
         it("should have a comment with the shebang in it", () => {
             const config = { rules: { checker: "error" } };
-            const spy = sandbox.spy(() => {
-                const comments = linter.getAllComments();
+            const spy = sandbox.spy(context => {
+                const comments = context.getAllComments();
 
                 assert.equal(comments.length, 1);
                 assert.equal(comments[0].type, "Shebang");
+                return {};
             });
 
-            linter.defineRule("checker", () => ({ Program: spy }));
+            linter.defineRule("checker", spy);
             linter.verify(code, config);
             assert(spy.calledOnce);
         });
@@ -2735,13 +2813,17 @@ describe("Linter", () => {
         it("should comment hashbang without breaking offset", () => {
             const code = "#!/usr/bin/env node\n'123';";
             const config = { rules: { checker: "error" } };
-            const spy = sandbox.spy(node => {
-                assert.equal(linter.getSource(node), "'123';");
+            let spy;
+
+            linter.defineRule("checker", context => {
+                spy = sandbox.spy(node => {
+                    assert.equal(context.getSource(node), "'123';");
+                });
+                return { ExpressionStatement: spy };
             });
 
-            linter.defineRule("checker", () => ({ ExpressionStatement: spy }));
             linter.verify(code, config);
-            assert(spy.calledOnce);
+            assert(spy && spy.calledOnce);
         });
     });
 
@@ -2793,16 +2875,6 @@ describe("Linter", () => {
             });
         });
 
-        describe("saveState", () => {
-            it("should save the state when saveState is passed as an option", () => {
-
-                const spy = sinon.spy(linter, "reset");
-
-                linter.verify("foo;", {}, { saveState: true });
-                assert.equal(spy.callCount, 0);
-            });
-        });
-
         it("should report warnings in order by line and column when called", () => {
 
             const code = "foo()\n    alert('test')";
@@ -3088,6 +3160,29 @@ describe("Linter", () => {
             linter.verify(linter.getSourceCode(), { parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } } });
         });
 
+        it("should reuse the SourceCode object", () => {
+            let ast1 = null,
+                ast2 = null;
+
+            linter.defineRule("save-ast1", () => ({
+                Program(node) {
+                    ast1 = node;
+                }
+            }));
+            linter.defineRule("save-ast2", () => ({
+                Program(node) {
+                    ast2 = node;
+                }
+            }));
+
+            linter.verify("function render() { return <div className='test'>{hello}</div> }", { parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } }, rules: { "save-ast1": 2 } });
+            linter.verify(linter.getSourceCode(), { parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } }, rules: { "save-ast2": 2 } });
+
+            assert(ast1 !== null);
+            assert(ast2 !== null);
+            assert(ast1 === ast2);
+        });
+
         it("should allow 'await' as a property name in modules", () => {
             const result = linter.verify(
                 "obj.await",
@@ -3220,19 +3315,10 @@ describe("Linter", () => {
         });
     });
 
-    describe("getDeclaredVariables(node)", () => {
-
-        /**
-         * Assert `eslint.getDeclaredVariables(node)` is empty.
-         * @param {ASTNode} node - A node to check.
-         * @returns {void}
-         */
-        function checkEmpty(node) {
-            assert.equal(0, linter.getDeclaredVariables(node).length);
-        }
+    describe("context.getDeclaredVariables(node)", () => {
 
         /**
-         * Assert `eslint.getDeclaredVariables(node)` is valid.
+         * Assert `context.getDeclaredVariables(node)` is valid.
          * @param {string} code - A code to check.
          * @param {string} type - A type string of ASTNode. This method checks variables on the node of the type.
          * @param {Array<Array<string>>} expectedNamesList - An array of expected variable names. The expected variable names is an array of string.
@@ -3241,6 +3327,15 @@ describe("Linter", () => {
         function verify(code, type, expectedNamesList) {
             linter.defineRules({
                 test(context) {
+
+                    /**
+                     * Assert `context.getDeclaredVariables(node)` is empty.
+                     * @param {ASTNode} node - A node to check.
+                     * @returns {void}
+                     */
+                    function checkEmpty(node) {
+                        assert.equal(0, context.getDeclaredVariables(node).length);
+                    }
                     const rule = {
                         Program: checkEmpty,
                         EmptyStatement: checkEmpty,
@@ -3495,6 +3590,136 @@ describe("Linter", () => {
         });
     });
 
+    describe("processors", () => {
+        beforeEach(() => {
+
+            // A rule that always reports the AST with a message equal to the source text
+            linter.defineRule("report-original-text", context => ({
+                Program(ast) {
+                    context.report({ node: ast, message: context.getSourceCode().text });
+                }
+            }));
+        });
+
+        describe("preprocessors", () => {
+            it("should apply a preprocessor to the code, and lint each code sample separately", () => {
+                const code = "foo bar baz";
+                const problems = linter.verify(
+                    code,
+                    { rules: { "report-original-text": "error" } },
+                    {
+
+                        // Apply a preprocessor that splits the source text into spaces and lints each word individually
+                        preprocess(input) {
+                            assert.strictEqual(input, code);
+                            assert.strictEqual(arguments.length, 1);
+                            return input.split(" ");
+                        }
+                    }
+                );
+
+                assert.strictEqual(problems.length, 3);
+                assert.deepEqual(problems.map(problem => problem.message), ["foo", "bar", "baz"]);
+            });
+        });
+
+        describe("postprocessors", () => {
+            it("should apply a postprocessor to the reported messages", () => {
+                const code = "foo bar baz";
+
+                const problems = linter.verify(
+                    code,
+                    { rules: { "report-original-text": "error" } },
+                    {
+                        preprocess: input => input.split(" "),
+
+                        /*
+                         * Apply a postprocessor that updates the locations of the reported problems
+                         * to make sure they correspond to the locations in the original text.
+                         */
+                        postprocess(problemLists) {
+                            assert.strictEqual(problemLists.length, 3);
+                            assert.strictEqual(arguments.length, 1);
+
+                            problemLists.forEach(problemList => assert.strictEqual(problemList.length, 1));
+                            return problemLists.reduce(
+                                (combinedList, problemList, index) =>
+                                    combinedList.concat(
+                                        problemList.map(
+                                            problem =>
+                                                Object.assign(
+                                                    {},
+                                                    problem,
+                                                    {
+                                                        message: problem.message.toUpperCase(),
+                                                        column: problem.column + index * 4
+                                                    }
+                                                )
+                                        )
+                                    ),
+                                []
+                            );
+                        }
+                    }
+                );
+
+                assert.strictEqual(problems.length, 3);
+                assert.deepEqual(problems.map(problem => problem.message), ["FOO", "BAR", "BAZ"]);
+                assert.deepEqual(problems.map(problem => problem.column), [1, 5, 9]);
+            });
+
+            it("should use postprocessed problem ranges when applying autofixes", () => {
+                const code = "foo bar baz";
+
+                linter.defineRule("capitalize-identifiers", context => ({
+                    Identifier(node) {
+                        if (node.name !== node.name.toUpperCase()) {
+                            context.report({
+                                node,
+                                message: "Capitalize this identifier",
+                                fix: fixer => fixer.replaceText(node, node.name.toUpperCase())
+                            });
+                        }
+                    }
+                }));
+
+                const fixResult = linter.verifyAndFix(
+                    code,
+                    { rules: { "capitalize-identifiers": "error" } },
+                    {
+
+                        /*
+                         * Apply a postprocessor that updates the locations of autofixes
+                         * to make sure they correspond to locations in the original text.
+                         */
+                        preprocess: input => input.split(" "),
+                        postprocess(problemLists) {
+                            return problemLists.reduce(
+                                (combinedProblems, problemList, blockIndex) =>
+                                    combinedProblems.concat(
+                                        problemList.map(problem =>
+                                            Object.assign(problem, {
+                                                fix: {
+                                                    text: problem.fix.text,
+                                                    range: problem.fix.range.map(
+                                                        rangeIndex => rangeIndex + blockIndex * 4
+                                                    )
+                                                }
+                                            }))
+                                    ),
+                                []
+                            );
+                        }
+                    }
+                );
+
+                assert.strictEqual(fixResult.fixed, true);
+                assert.strictEqual(fixResult.messages.length, 0);
+                assert.strictEqual(fixResult.output, "FOO BAR BAZ");
+            });
+        });
+    });
+
     describe("verifyAndFix", () => {
         it("Fixes the code", () => {
             const messages = linter.verifyAndFix("var a", {
diff --git a/tests/lib/rules/indent.js b/tests/lib/rules/indent.js
index 1339c16f6..006d7f1dd 100644
--- a/tests/lib/rules/indent.js
+++ b/tests/lib/rules/indent.js
@@ -3591,6 +3591,31 @@ ruleTester.run("indent", rule, {
             `,
             options: [4, { flatTernaryExpressions: true }]
         },
+        {
+            code: unIndent`
+                function foo() {
+                    return foo ? bar :
+                        baz
+                }
+            `,
+            options: [4, { flatTernaryExpressions: true }]
+        },
+        {
+            code: unIndent`
+                throw foo ? bar :
+                    baz
+            `,
+            options: [4, { flatTernaryExpressions: true }]
+        },
+        {
+            code: unIndent`
+                foo(
+                    bar
+                ) ? baz :
+                    qux
+            `,
+            options: [4, { flatTernaryExpressions: true }]
+        },
         unIndent`
                 foo
                     [
diff --git a/tests/lib/rules/newline-per-chained-call.js b/tests/lib/rules/newline-per-chained-call.js
index 232aafed6..ada18a70b 100644
--- a/tests/lib/rules/newline-per-chained-call.js
+++ b/tests/lib/rules/newline-per-chained-call.js
@@ -28,6 +28,7 @@ ruleTester.run("newline-per-chained-call", rule, {
     }],
     invalid: [{
         code: "_\n.chain({}).map(foo).filter(bar).value();",
+        output: "_\n.chain({}).map(foo)\n.filter(bar)\n.value();",
         errors: [{
             message: "Expected line break before `.filter`."
         }, {
@@ -35,26 +36,31 @@ ruleTester.run("newline-per-chained-call", rule, {
         }]
     }, {
         code: "_\n.chain({})\n.map(foo)\n.filter(bar).value();",
+        output: "_\n.chain({})\n.map(foo)\n.filter(bar)\n.value();",
         errors: [{
             message: "Expected line break before `.value`."
         }]
     }, {
         code: "a().b().c().e.d()",
+        output: "a().b()\n.c().e.d()",
         errors: [{
             message: "Expected line break before `.c`."
         }]
     }, {
         code: "a.b.c().e().d()",
+        output: "a.b.c().e()\n.d()",
         errors: [{
             message: "Expected line break before `.d`."
         }]
     }, {
         code: "_.chain({}).map(a).value(); ",
+        output: "_.chain({}).map(a)\n.value(); ",
         errors: [{
             message: "Expected line break before `.value`."
         }]
     }, {
         code: "var a = m1.m2();\n var b = m1.m2().m3().m4().m5();",
+        output: "var a = m1.m2();\n var b = m1.m2().m3()\n.m4()\n.m5();",
         errors: [{
             message: "Expected line break before `.m4`."
         }, {
@@ -62,11 +68,13 @@ ruleTester.run("newline-per-chained-call", rule, {
         }]
     }, {
         code: "var a = m1.m2();\n var b = m1.m2().m3()\n.m4().m5();",
+        output: "var a = m1.m2();\n var b = m1.m2().m3()\n.m4()\n.m5();",
         errors: [{
             message: "Expected line break before `.m5`."
         }]
     }, {
         code: "var a = m1().m2\n.m3().m4().m5().m6().m7();",
+        output: "var a = m1().m2\n.m3().m4().m5()\n.m6()\n.m7();",
         options: [{
             ignoreChainWithDepth: 3
         }],
@@ -105,6 +113,37 @@ ruleTester.run("newline-per-chained-call", rule, {
             "    // Do something with error.",
             "}).end();"
         ].join("\n"),
+        output: [
+            "http.request({",
+            "    // Param",
+            "    // Param",
+            "    // Param",
+            "}).on('response', function(response) {",
+            "    // Do something with response.",
+            "    // Do something with response.",
+            "    // Do something with response.",
+            "    // Do something with response.",
+            "    // Do something with response.",
+            "    // Do something with response.",
+            "    // Do something with response.",
+            "    // Do something with response.",
+            "    // Do something with response.",
+            "    // Do something with response.",
+            "})",
+            ".on('error', function(error) {",
+            "    // Do something with error.",
+            "    // Do something with error.",
+            "    // Do something with error.",
+            "    // Do something with error.",
+            "    // Do something with error.",
+            "    // Do something with error.",
+            "    // Do something with error.",
+            "    // Do something with error.",
+            "    // Do something with error.",
+            "    // Do something with error.",
+            "})",
+            ".end();"
+        ].join("\n"),
         errors: [{
             message: "Expected line break before `.on`."
         }, {
@@ -116,6 +155,13 @@ ruleTester.run("newline-per-chained-call", rule, {
             "    'method3' :",
             "    'method4']()"
         ].join("\n"),
+        output: [
+            "anObject.method1().method2()",
+            "['method' + n]()",
+            "[aCondition ?",
+            "    'method3' :",
+            "    'method4']()"
+        ].join("\n"),
         errors: [{
             message: "Expected line break before `['method' + n]`."
         }, {
@@ -123,8 +169,28 @@ ruleTester.run("newline-per-chained-call", rule, {
         }]
     }, {
         code: "foo.bar()['foo' + \u2029 + 'bar']()",
+        output: "foo.bar()\n['foo' + \u2029 + 'bar']()",
         options: [{ ignoreChainWithDepth: 1 }],
         errors: [{ message: "Expected line break before `['foo' + `." }]
+    }, {
+        code: "foo.bar()[(biz)]()",
+        output: "foo.bar()\n[(biz)]()",
+        options: [{ ignoreChainWithDepth: 1 }],
+        errors: [{ message: "Expected line break before `[biz]`." }]
+    }, {
+        code: "(foo).bar().biz()",
+        output: "(foo).bar()\n.biz()",
+        options: [{ ignoreChainWithDepth: 1 }],
+        errors: [{ message: "Expected line break before `.biz`." }]
+    }, {
+        code: "foo.bar(). /* comment */ biz()",
+        output: "foo.bar()\n. /* comment */ biz()",
+        options: [{ ignoreChainWithDepth: 1 }],
+        errors: [{ message: "Expected line break before `.biz`." }]
+    }, {
+        code: "foo.bar() /* comment */ .biz()",
+        output: "foo.bar() /* comment */ \n.biz()",
+        options: [{ ignoreChainWithDepth: 1 }],
+        errors: [{ message: "Expected line break before `.biz`." }]
     }]
-
 });
diff --git a/tests/lib/rules/no-extra-parens.js b/tests/lib/rules/no-extra-parens.js
index 4958fb6ac..7753c7d66 100644
--- a/tests/lib/rules/no-extra-parens.js
+++ b/tests/lib/rules/no-extra-parens.js
@@ -89,6 +89,12 @@ ruleTester.run("no-extra-parens", rule, {
         "a(b = c, (d, e))",
         "(++a)(b); (c++)(d);",
         "new (A())",
+        "new (foo.Baz().foo)",
+        "new (foo.baz.bar().foo.baz)",
+        "new ({}.baz.bar.foo().baz)",
+        "new (doSomething().baz.bar().foo)",
+        "new ([][0].baz.foo().bar.foo)",
+        "new (foo\n.baz\n.bar()\n.foo.baz)",
         "new A()()",
         "(new A)()",
         "(new (Foo || Bar))()",
@@ -516,11 +522,16 @@ ruleTester.run("no-extra-parens", rule, {
         invalid("new (\nfunction(){}\n)", "new \nfunction(){}\n", "FunctionExpression", 1),
         invalid("((function foo() {return 1;}))()", "(function foo() {return 1;})()", "FunctionExpression"),
         invalid("((function(){ return bar(); })())", "(function(){ return bar(); })()", "CallExpression"),
+        invalid("(foo()).bar", "foo().bar", "CallExpression"),
+        invalid("(foo.bar()).baz", "foo.bar().baz", "CallExpression"),
+        invalid("(foo\n.bar())\n.baz", "foo\n.bar()\n.baz", "CallExpression"),
 
         invalid("new (A)", "new A", "Identifier"),
         invalid("(new A())()", "new A()()", "NewExpression"),
         invalid("(new A(1))()", "new A(1)()", "NewExpression"),
         invalid("((new A))()", "(new A)()", "NewExpression"),
+        invalid("new (foo\n.baz\n.bar\n.foo.baz)", "new foo\n.baz\n.bar\n.foo.baz", "MemberExpression"),
+        invalid("new (foo.baz.bar.baz)", "new foo.baz.bar.baz", "MemberExpression"),
 
         invalid("0, (_ => 0)", "0, _ => 0", "ArrowFunctionExpression", 1),
         invalid("(_ => 0), 0", "_ => 0, 0", "ArrowFunctionExpression", 1),
diff --git a/tests/lib/rules/no-invalid-this.js b/tests/lib/rules/no-invalid-this.js
index d975a385a..a87a9aa44 100644
--- a/tests/lib/rules/no-invalid-this.js
+++ b/tests/lib/rules/no-invalid-this.js
@@ -308,6 +308,26 @@ const patterns = [
         valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES],
         invalid: []
     },
+    {
+        code: "obj.foo = (() => function() { console.log(this); z(x => console.log(x, this)); })();",
+        parserOptions: { ecmaVersion: 6 },
+        valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES],
+        invalid: []
+    },
+    {
+        code: "obj.foo = (function() { return () => { console.log(this); z(x => console.log(x, this)); }; })();",
+        parserOptions: { ecmaVersion: 6 },
+        valid: [NORMAL],
+        invalid: [USE_STRICT, IMPLIED_STRICT, MODULES],
+        errors
+    },
+    {
+        code: "obj.foo = (() => () => { console.log(this); z(x => console.log(x, this)); })();",
+        parserOptions: { ecmaVersion: 6 },
+        valid: [NORMAL],
+        invalid: [USE_STRICT, IMPLIED_STRICT, MODULES],
+        errors
+    },
 
     // Class Instance Methods.
     {
diff --git a/tests/lib/rules/no-undef-init.js b/tests/lib/rules/no-undef-init.js
index 2d44e1656..dfd45e008 100644
--- a/tests/lib/rules/no-undef-init.js
+++ b/tests/lib/rules/no-undef-init.js
@@ -27,17 +27,17 @@ ruleTester.run("no-undef-init", rule, {
     invalid: [
         {
             code: "var a = undefined;",
-            output: "var a;",
+            output: null,
             errors: [{ message: "It's not necessary to initialize 'a' to undefined.", type: "VariableDeclarator" }]
         },
         {
             code: "var a = undefined, b = 1;",
-            output: "var a, b = 1;",
+            output: null,
             errors: [{ message: "It's not necessary to initialize 'a' to undefined.", type: "VariableDeclarator" }]
         },
         {
             code: "var a = 1, b = undefined, c = 5;",
-            output: "var a = 1, b, c = 5;",
+            output: null,
             errors: [{ message: "It's not necessary to initialize 'b' to undefined.", type: "VariableDeclarator" }]
         },
         {
@@ -51,6 +51,47 @@ ruleTester.run("no-undef-init", rule, {
             output: null,
             parserOptions: { ecmaVersion: 6 },
             errors: [{ message: "It's not necessary to initialize '{a}' to undefined.", type: "VariableDeclarator" }]
+        },
+        {
+            code: "for(var i in [1,2,3]){var a = undefined; for(var j in [1,2,3]){}}",
+            output: null,
+            errors: [{ message: "It's not necessary to initialize 'a' to undefined.", type: "VariableDeclarator" }]
+        },
+        {
+            code: "let a = undefined;",
+            output: "let a;",
+            parserOptions: { ecmaVersion: 6 },
+            errors: [{ message: "It's not necessary to initialize 'a' to undefined.", type: "VariableDeclarator" }]
+        },
+        {
+            code: "let a = undefined, b = 1;",
+            output: "let a, b = 1;",
+            parserOptions: { ecmaVersion: 6 },
+            errors: [{ message: "It's not necessary to initialize 'a' to undefined.", type: "VariableDeclarator" }]
+        },
+        {
+            code: "let a = 1, b = undefined, c = 5;",
+            output: "let a = 1, b, c = 5;",
+            parserOptions: { ecmaVersion: 6 },
+            errors: [{ message: "It's not necessary to initialize 'b' to undefined.", type: "VariableDeclarator" }]
+        },
+        {
+            code: "let [a] = undefined;",
+            output: null,
+            parserOptions: { ecmaVersion: 6 },
+            errors: [{ message: "It's not necessary to initialize '[a]' to undefined.", type: "VariableDeclarator" }]
+        },
+        {
+            code: "let {a} = undefined;",
+            output: null,
+            parserOptions: { ecmaVersion: 6 },
+            errors: [{ message: "It's not necessary to initialize '{a}' to undefined.", type: "VariableDeclarator" }]
+        },
+        {
+            code: "for(var i in [1,2,3]){let a = undefined; for(var j in [1,2,3]){}}",
+            output: "for(var i in [1,2,3]){let a; for(var j in [1,2,3]){}}",
+            parserOptions: { ecmaVersion: 6 },
+            errors: [{ message: "It's not necessary to initialize 'a' to undefined.", type: "VariableDeclarator" }]
         }
     ]
 });
diff --git a/tests/lib/util/apply-disable-directives.js b/tests/lib/util/apply-disable-directives.js
index 9b8d26926..f7e7c2085 100644
--- a/tests/lib/util/apply-disable-directives.js
+++ b/tests/lib/util/apply-disable-directives.js
@@ -1,5 +1,5 @@
 /**
- * @fileoverview Tests for filter-by-disable-comments
+ * @fileoverview Tests for apply-disable-directives
  * @author Teddy Katz
  */
 
@@ -8,7 +8,7 @@
 const assert = require("chai").assert;
 const applyDisableDirectives = require("../../../lib/util/apply-disable-directives");
 
-describe("comment-reporting-config", () => {
+describe("apply-disable-directives", () => {
     describe("/* eslint-disable */ comments without rules", () => {
         it("keeps problems before the comment on the same line", () => {
             assert.deepEqual(
diff --git a/tests/lib/util/source-code.js b/tests/lib/util/source-code.js
index 746d78568..ac0cfa79d 100644
--- a/tests/lib/util/source-code.js
+++ b/tests/lib/util/source-code.js
@@ -216,10 +216,6 @@ describe("SourceCode", () => {
 
         const sandbox = sinon.sandbox.create();
 
-        beforeEach(() => {
-            linter.reset();
-        });
-
         afterEach(() => {
             sandbox.verifyAndRestore();
         });
@@ -905,7 +901,6 @@ describe("SourceCode", () => {
 
 
         beforeEach(() => {
-            linter.reset();
             unusedAssertionFuncs = new Set();
         });
 
diff --git a/tests/tools/eslint-fuzzer.js b/tests/tools/eslint-fuzzer.js
index 8ea25e0f5..26047308b 100644
--- a/tests/tools/eslint-fuzzer.js
+++ b/tests/tools/eslint-fuzzer.js
@@ -38,7 +38,6 @@ describe("eslint-fuzzer", function() {
     });
 
     after(() => {
-        linter.reset();
         configRule.createCoreRuleConfigs.restore();
     });