Repository: eslint/eslint
Pull request #9256 (2017-09-08 04:28:51)

Fix: reuse the AST of source code object in verify
<!--
    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 ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/bug-report.md))

**Tell us about your environment**

* **ESLint Version:** `master`
* **Node Version:** 8.4.0
* **npm Version:** 5.4.1

**What parser (default, Babel-ESLint, etc.) are you using?**

- default

**Please show your full configuration:**

- nothing

**What did you do? Please include the actual source code causing the issue.**

Gave a `SourceCode` object to `linter.verify()`.

In more accurate, I resolved conflictions between #8755 and `master`, then [this test](https://github.com/eslint/eslint/pull/8755/files#diff-c6fc98e70cf39f54c23015c3db45f14aR3794) got broken.

**What did you expect to happen?**

It reuses the AST of the `SourceCode` object.

**What actually happened? Please include the actual, raw output from ESLint.**

It re-parsed the source code text.

**What changes did you make? (Give an overview)**

This PR moved the statement `this.sourceCode = textOrSourceCode;` to the next of `this.reset()` because `this.reset()` set `null` to `this.sourceCode`.

**Is there anything you'd like reviewers to focus on?**

Nothing in particular.


eslintbot: LGTM
not-an-aardvark: Oops, thanks for fixing.

Patch:
diff --git a/docs/developer-guide/nodejs-api.md b/docs/developer-guide/nodejs-api.md
index 622d8612b..76aa51e21 100644
--- a/docs/developer-guide/nodejs-api.md
+++ b/docs/developer-guide/nodejs-api.md
@@ -693,6 +693,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..59f4161d4 100644
--- a/docs/developer-guide/working-with-plugins.md
+++ b/docs/developer-guide/working-with-plugins.md
@@ -119,90 +119,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/lib/cli-engine.js b/lib/cli-engine.js
index 1abc1fd2c..6bcab1889 100644
--- a/lib/cli-engine.js
+++ b/lib/cli-engine.js
@@ -372,6 +372,8 @@ function getCacheFile(cacheFile, cwd) {
     return resolvedCacheFile;
 }
 
+const configHashCache = new WeakMap();
+
 //------------------------------------------------------------------------------
 // Public Interface
 //------------------------------------------------------------------------------
@@ -493,11 +495,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 +512,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 +533,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 +546,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}`);
-
-            const res = processFile(filename, configHelper, options, linter);
+            debug(`Processing ${fileInfo.filename}`);
 
-            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;
-                }
-            }
-
-            results.push(res);
-        }
-
-        const startTime = Date.now();
-
+                    const descriptor = fileCache.getFileDescriptor(result.filePath);
 
-        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 +668,6 @@ class CLIEngine {
      */
     getFormatter(format) {
 
-        let formatterPath;
 
         // default is stylish
         format = format || "stylish";
@@ -724,6 +678,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-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/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 e36fa995a..1a4e1fd97 100755
--- a/lib/linter.js
+++ b/lib/linter.js
@@ -301,7 +301,7 @@ function modifyConfigsFromComments(filename, ast, config, linterContext) {
     const problems = [];
     const disableDirectives = [];
 
-    ast.comments.forEach(comment => {
+    ast.comments.filter(token => token.type !== "Shebang").forEach(comment => {
 
         let value = comment.value.trim();
         const match = /^(eslint(-\w+){0,3}|exported|globals?)(\s|$)/.exec(value);
@@ -594,6 +594,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 +705,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;
 
@@ -658,9 +720,6 @@ class Linter {
      * @returns {void}
      */
     reset() {
-        this.currentConfig = null;
-        this.scopeManager = null;
-        this.traverser = null;
         this.sourceCode = null;
     }
 
@@ -691,17 +750,8 @@ class Linter {
     verify(textOrSourceCode, config, filenameOrOptions, saveState) {
         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") {
@@ -718,6 +768,14 @@ class Linter {
             this.reset();
         }
 
+        if (typeof textOrSourceCode === "string") {
+            this.sourceCode = null;
+            text = textOrSourceCode;
+        } else {
+            this.sourceCode = textOrSourceCode;
+            text = this.sourceCode.text;
+        }
+
         // search and apply "eslint-env *".
         const envInFile = findEslintEnv(text);
 
@@ -775,6 +833,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 +854,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 +959,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 +970,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);
@@ -942,82 +994,6 @@ class Linter {
         return this.sourceCode;
     }
 
-    /**
-     * Gets the scope for the current node.
-     * @returns {Object} An object representing the current node's scope.
-     */
-    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();
-
-            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];
-    }
-
-    /**
-     * 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.
-     */
-    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;
-    }
-
     /**
      * Defines a new linting rule.
      * @param {string} ruleId A unique rule identifier
@@ -1047,29 +1023,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.
@@ -1140,23 +1093,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/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/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, {


Test patch:
diff --git a/tests/lib/ast-utils.js b/tests/lib/ast-utils.js
index f4df03844..085bd35c0 100644
--- a/tests/lib/ast-utils.js
+++ b/tests/lib/ast-utils.js
@@ -62,9 +62,9 @@ describe("ast-utils", () => {
 
     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 +73,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 +117,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 +130,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 +142,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 +155,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 +168,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/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 1d432ea3d..2185b39db 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);
         });
 
 
@@ -189,115 +194,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 +345,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");
+
+                const node2 = context.getNodeByRangeIndex(3);
 
-                assert.equal(node.type, "BinaryExpression");
-                node = linter.getNodeByRangeIndex(3);
-                assert.equal(node.type, "VariableDeclaration");
+                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(node);
-                node = linter.getNodeByRangeIndex(-99);
-                assert.isNull(node);
+                assert.isNull(node1);
+
+                const node2 = context.getNodeByRangeIndex(-99);
+
+                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 +474,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();
 
-            linter.defineRule("checker", () => ({ ReturnStatement: spy }));
+                    assert.equal(scope.type, "function");
+                    assert.equal(scope.block.type, "ArrowFunctionExpression");
+                });
+
+                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();
+
+                    assert.equal(scope.type, "module");
+                });
 
-            linter.defineRule("checker", () => ({ AssignmentExpression: spy }));
+                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();
 
-            linter.defineRule("checker", () => ({ AssignmentExpression: spy }));
+                    assert.equal(scope.type, "function");
+                });
+
+                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 = context.getScope();
 
-                const scope = linter.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;
 
-                assert.isTrue(linter.markVariableAsUsed("a"));
+            linter.defineRule("checker", context => {
+                spy = sandbox.spy(() => {
+                    const globalScope = context.getScope(),
+                        childScope = globalScope.childScopes[0];
 
-                assert.isTrue(getVariable(childScope, "a").eslintUsed);
-                assert.isUndefined(getVariable(childScope, "b").eslintUsed);
-            });
+                    assert.isTrue(context.markVariableAsUsed("a"));
 
-            linter.defineRule("checker", () => ({ "Program:exit": spy }));
+                    assert.isTrue(getVariable(childScope, "a").eslintUsed);
+                    assert.isUndefined(getVariable(childScope, "b").eslintUsed);
+                });
+
+                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(context.markVariableAsUsed("a"));
 
-                assert.isTrue(linter.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);
         });
     });
 
@@ -999,59 +1078,36 @@ describe("Linter", () => {
         });
     });
 
-    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({}));
-        });
-    });
-
     describe("when evaluating code containing /*global */ and /*globals */ blocks", () => {
         const code = "/*global a b:true c:false*/ function foo() {} /*globals d:true*/";
 
         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 +1116,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 +1145,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 +1170,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 +1202,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;
 
-                assert.equal(horse, null); // there is no global scope at all
+            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
+                });
+
+                return { Program: spy };
             });
 
-            linter.defineRule("checker", () => ({ Program: spy }));
             linter.verify(code, config);
-            assert(spy.calledOnce);
+            assert(spy && spy.calledOnce);
         });
     });
 
@@ -1210,15 +1305,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 +1327,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 +1352,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;
+
+            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);
+                });
 
-                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);
         });
     });
 
@@ -1869,6 +1978,26 @@ describe("Linter", () => {
                 assert.equal(messages[0].ruleId, "no-console");
             });
 
+            it("should ignore violations of only the specified rule on next line", () => {
+                const code = [
+                    "// eslint-disable-next-line quotes",
+                    "alert(\"test\");",
+                    "console.log('test');"
+                ].join("\n");
+                const config = {
+                    rules: {
+                        "no-alert": 1,
+                        quotes: [1, "single"],
+                        "no-console": 1
+                    }
+                };
+                const messages = linter.verify(code, config, filename);
+
+                assert.equal(messages.length, 2);
+                assert.equal(messages[0].ruleId, "no-alert");
+                assert.equal(messages[1].ruleId, "no-console");
+            });
+
             it("should ignore violations of specified rule on next line only", () => {
                 const code = [
                     "alert('test');",
@@ -1909,7 +2038,7 @@ describe("Linter", () => {
                 assert.equal(messages[0].ruleId, "no-console");
             });
 
-            it("should not report if comment is in block quotes", () => {
+            it("should not ignore violations if comment is in block quotes", () => {
                 const code = [
                     "alert('test');",
                     "/* eslint-disable-next-line no-alert */",
@@ -1929,6 +2058,25 @@ describe("Linter", () => {
                 assert.equal(messages[1].ruleId, "no-alert");
                 assert.equal(messages[2].ruleId, "no-console");
             });
+
+            it("should not ignore violations if comment is of the type Shebang", () => {
+                const code = [
+                    "#! eslint-disable-next-line no-alert",
+                    "alert('test');",
+                    "console.log('test');"
+                ].join("\n");
+                const config = {
+                    rules: {
+                        "no-alert": 1,
+                        "no-console": 1
+                    }
+                };
+                const messages = linter.verify(code, config, filename);
+
+                assert.equal(messages.length, 2);
+                assert.equal(messages[0].ruleId, "no-alert");
+                assert.equal(messages[1].ruleId, "no-console");
+            });
         });
     });
 
@@ -2269,14 +2417,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);
         });
@@ -2696,13 +2845,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);
         });
     });
 
@@ -3049,6 +3202,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",
@@ -3181,19 +3357,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.
@@ -3202,6 +3369,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,
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(