Repository: eslint/eslint
Pull request #8391 (2017-04-02 21:08:32)

Update: fix space-unary-ops behavior with postfix UpdateExpressions
**What is the purpose of this pull request? (put an "X" next to item)**

[x] Bug fix

**Tell us about your environment**

* **ESLint Version:** master
* **Node Version:** 7.8.0
* **npm Version:** 4.2.0

**What parser (default, Babel-ESLint, etc.) are you using?**

default

**Please show your full configuration:**

```yml
rules:
  space-unary-ops: error
```

**What did you do? Please include the actual source code causing the issue.**

```js
foo.bar ++;
```

**What did you expect to happen?**

I expected an error to be reported.

**What actually happened? Please include the actual, raw output from ESLint.**

No error was reported.

<!--
    If the item you've checked above has a template, please paste the template questions below and answer them. (If this pull request is addressing an issue, you can just paste a link to the issue here instead.)
-->

<!--
    Please ensure your pull request is ready:

    - Read the pull request guide (http://eslint.org/docs/developer-guide/contributing/pull-requests)
    - Include tests for this change
    - Update documentation for this change (if appropriate)
-->

<!--
    The following is required for all pull requests:
-->

**What changes did you make? (Give an overview)**

Previously, space-unary-ops would always evaluate the spacing between the first and second tokens of an UpdateExpression (e.g. `foo++`). This worked fine when the argument had one token, but it led to the wrong behavior if the argument had multiple tokens (e.g. in `foo.bar++`, the space between `foo` and `.` would be checked, rather than between `bar` and `++`). This commit updates the rule to check the *last* two tokens of postfix UpdateExpressions.

**Is there anything you'd like reviewers to focus on?**

Nothing in particular


mention-bot: @not-an-aardvark, thanks for your PR! By analyzing the history of the files in this pull request, we identified @qmmr, @gyandeeps and @vitorbal to be potential reviewers.
eslintbot: LGTM

Patch:
diff --git a/Makefile.js b/Makefile.js
index 60f02b58c..dd4baa847 100644
--- a/Makefile.js
+++ b/Makefile.js
@@ -412,7 +412,7 @@ function lintMarkdown(files) {
             // Exclusions for deliberate/widespread violations
             MD001: false, // Header levels should only increment by one level at a time
             MD002: false, // First header should be a h1 header
-            MD007: {      // Unordered list indentation
+            MD007: { // Unordered list indentation
                 indent: 4
             },
             MD012: false, // Multiple consecutive blank lines
@@ -427,7 +427,7 @@ function lintMarkdown(files) {
             MD033: false, // Allow inline HTML
             MD034: false, // Bare URL used
             MD040: false, // Fenced code blocks should have a language specified
-            MD041: false  // First line in file should be a top level header
+            MD041: false // First line in file should be a top level header
         },
         result = markdownlint.sync({
             files,
@@ -767,7 +767,7 @@ target.browserify = function() {
     generateRulesIndex(TEMP_DIR);
 
     // 5. browserify the temp directory
-    nodeCLI.exec("browserify", "-x espree", `${TEMP_DIR}eslint.js`, "-o", `${BUILD_DIR}eslint.js`, "-s eslint", "-t [ babelify --presets [ es2015 ] ]");
+    nodeCLI.exec("browserify", "-x espree", `${TEMP_DIR}eslint.js`, "-o", `${BUILD_DIR}eslint.js`, "-s eslint", "--global-transform [ babelify --presets [ es2015 ] ]");
 
     // 6. Browserify espree
     nodeCLI.exec("browserify", "-r espree", "-o", `${TEMP_DIR}espree.js`);
@@ -1042,7 +1042,7 @@ function runPerformanceTest(title, targets, multiplier, cb) {
     echo("  CPU Speed is %d with multiplier %d", cpuSpeed, multiplier);
 
     time(cmd, 5, 1, [], results => {
-        if (!results || results.length === 0) {  // No results? Something is wrong.
+        if (!results || results.length === 0) { // No results? Something is wrong.
             throw new Error("Performance test failed.");
         }
 
diff --git a/conf/cli-options.js b/conf/cli-options.js
index b377f3da7..291007515 100644
--- a/conf/cli-options.js
+++ b/conf/cli-options.js
@@ -16,7 +16,7 @@ module.exports = {
     extensions: [".js"],
     ignore: true,
     ignorePath: null,
-    parser: "",     // must be empty
+    parser: "", // must be empty
     cache: false,
 
     // in order to honor the cacheFile option if specified
diff --git a/conf/eslint-recommended.js b/conf/eslint-recommended.js
index 63c2fc770..72f0833a8 100755
--- a/conf/eslint-recommended.js
+++ b/conf/eslint-recommended.js
@@ -11,14 +11,13 @@
 
 module.exports = {
     parser: "espree",
-    ecmaFeatures: {},
 
     rules: {
 
         /* eslint-enable sort-keys */
         "accessor-pairs": "off",
         "array-bracket-spacing": "off",
-        "array-callback-return": "off",
+        "array-callback-return": "error",
         "arrow-body-style": "off",
         "arrow-parens": "off",
         "arrow-spacing": "off",
@@ -84,7 +83,7 @@ module.exports = {
         "no-case-declarations": "error",
         "no-catch-shadow": "off",
         "no-class-assign": "error",
-        "no-compare-neg-zero": "off",
+        "no-compare-neg-zero": "error",
         "no-cond-assign": "error",
         "no-confusing-arrow": "off",
         "no-console": "error",
@@ -202,7 +201,7 @@ module.exports = {
         "no-useless-computed-key": "off",
         "no-useless-concat": "off",
         "no-useless-constructor": "off",
-        "no-useless-escape": "off",
+        "no-useless-escape": "error",
         "no-useless-rename": "off",
         "no-useless-return": "off",
         "no-var": "off",
@@ -212,7 +211,7 @@ module.exports = {
         "no-with": "off",
         "nonblock-statement-body-position": "off",
         "object-curly-newline": "off",
-        "object-curly-spacing": ["off", "never"],
+        "object-curly-spacing": "off",
         "object-property-newline": "off",
         "object-shorthand": "off",
         "one-var": "off",
diff --git a/docs/rules/no-multi-spaces.md b/docs/rules/no-multi-spaces.md
index fde45a706..340f951ba 100644
--- a/docs/rules/no-multi-spaces.md
+++ b/docs/rules/no-multi-spaces.md
@@ -54,11 +54,55 @@ a ? b: c
 
 ## Options
 
-To avoid contradictions if some other rules require multiple spaces, this rule has an option to ignore certain node types in the abstract syntax tree (AST) of JavaScript code.
+This rule's configuration consists of an object with the following properties:
+
+* `"ignoreEOLComments": true` (defaults to `false`) ignores multiple spaces before comments that occur at the end of lines
+* `"exceptions": { "Property": true }` (`"Property"` is the only node specified by default) specifies nodes to ignore
+
+### ignoreEOLComments
+
+Examples of **incorrect** code for this rule with the `{ "ignoreEOLComments": false }` (default) option:
+
+```js
+/*eslint no-multi-spaces: ["error", { ignoreEOLComments: false }]*/
+
+var x = 5;      // comment
+var x = 5;      /* multiline
+ * comment
+ */
+```
+
+Examples of **correct** code for this rule with the `{ "ignoreEOLComments": false }` (default) option:
+
+```js
+/*eslint no-multi-spaces: ["error", { ignoreEOLComments: false }]*/
+
+var x = 5; // comment
+var x = 5; /* multiline
+ * comment
+ */
+```
+
+Examples of **correct** code for this rule with the `{ "ignoreEOLComments": true }` option:
+
+```js
+/*eslint no-multi-spaces: ["error", { ignoreEOLComments: true }]*/
+
+var x = 5; // comment
+var x = 5;      // comment
+var x = 5; /* multiline
+ * comment
+ */
+var x = 5;      /* multiline
+ * comment
+ */
+```
 
 ### exceptions
 
-The `exceptions` object expects property names to be AST node types as defined by [ESTree](https://github.com/estree/estree). The easiest way to determine the node types for `exceptions` is to use the [online demo](http://eslint.org/parser).
+To avoid contradictions with other rules that require multiple spaces, this rule has an `exceptions` option to ignore certain nodes.
+
+This option is an object that expects property names to be AST node types as defined by [ESTree](https://github.com/estree/estree). The easiest way to determine the node types for `exceptions` is to use the [online demo](http://eslint.org/parser).
 
 Only the `Property` node type is ignored by default, because for the [key-spacing](key-spacing.md) rule some alignment options require multiple spaces in properties of object literals.
 
diff --git a/lib/ast-utils.js b/lib/ast-utils.js
index 0f2f3d6af..978f19ad2 100644
--- a/lib/ast-utils.js
+++ b/lib/ast-utils.js
@@ -556,9 +556,9 @@ module.exports = {
     /**
      * Finds the variable by a given name in a given scope and its upper scopes.
      *
-     * @param {escope.Scope} initScope - A scope to start find.
+     * @param {eslint-scope.Scope} initScope - A scope to start find.
      * @param {string} name - A variable name to find.
-     * @returns {escope.Variable|null} A found variable or `null`.
+     * @returns {eslint-scope.Variable|null} A found variable or `null`.
      */
     getVariableByName(initScope, name) {
         let scope = initScope;
diff --git a/lib/cli-engine.js b/lib/cli-engine.js
index de875a4d3..227305903 100644
--- a/lib/cli-engine.js
+++ b/lib/cli-engine.js
@@ -432,141 +432,99 @@ function getCacheFile(cacheFile, cwd) {
 // Public Interface
 //------------------------------------------------------------------------------
 
-/**
- * Creates a new instance of the core CLI engine.
- * @param {CLIEngineOptions} options The options for this instance.
- * @constructor
- */
-function CLIEngine(options) {
-
-    options = Object.assign(
-        Object.create(null),
-        defaultOptions,
-        { cwd: process.cwd() },
-        options
-    );
+class CLIEngine {
 
     /**
-     * Stored options for this instance
-     * @type {Object}
+     * Creates a new instance of the core CLI engine.
+     * @param {CLIEngineOptions} options The options for this instance.
+     * @constructor
      */
-    this.options = options;
-
-    const cacheFile = getCacheFile(this.options.cacheLocation || this.options.cacheFile, this.options.cwd);
-
-    /**
-     * Cache used to avoid operating on files that haven't changed since the
-     * last successful execution (e.g., file passed linting with no errors and
-     * no warnings).
-     * @type {Object}
-     */
-    this._fileCache = fileEntryCache.create(cacheFile);
-
-    // load in additional rules
-    if (this.options.rulePaths) {
-        const cwd = this.options.cwd;
-
-        this.options.rulePaths.forEach(rulesdir => {
-            debug(`Loading rules from ${rulesdir}`);
-            rules.load(rulesdir, cwd);
-        });
-    }
-
-    Object.keys(this.options.rules || {}).forEach(name => {
-        validator.validateRuleOptions(name, this.options.rules[name], "CLI");
-    });
-}
-
-/**
- * Returns the formatter representing the given format or null if no formatter
- * with the given name can be found.
- * @param {string} [format] The name of the format to load or the path to a
- *      custom formatter.
- * @returns {Function} The formatter function or null if not found.
- */
-CLIEngine.getFormatter = function(format) {
-
-    let formatterPath;
+    constructor(options) {
 
-    // default is stylish
-    format = format || "stylish";
+        options = Object.assign(
+            Object.create(null),
+            defaultOptions,
+            { cwd: process.cwd() },
+            options
+        );
 
-    // only strings are valid formatters
-    if (typeof format === "string") {
+        /**
+         * Stored options for this instance
+         * @type {Object}
+         */
+        this.options = options;
 
-        // replace \ with / for Windows compatibility
-        format = format.replace(/\\/g, "/");
+        const cacheFile = getCacheFile(this.options.cacheLocation || this.options.cacheFile, this.options.cwd);
 
-        // if there's a slash, then it's a file
-        if (format.indexOf("/") > -1) {
-            const cwd = this.options ? this.options.cwd : process.cwd();
+        /**
+         * Cache used to avoid operating on files that haven't changed since the
+         * last successful execution (e.g., file passed linting with no errors and
+         * no warnings).
+         * @type {Object}
+         */
+        this._fileCache = fileEntryCache.create(cacheFile);
 
-            formatterPath = path.resolve(cwd, format);
-        } else {
-            formatterPath = `./formatters/${format}`;
-        }
+        // load in additional rules
+        if (this.options.rulePaths) {
+            const cwd = this.options.cwd;
 
-        try {
-            return require(formatterPath);
-        } catch (ex) {
-            ex.message = `There was a problem loading formatter: ${formatterPath}\nError: ${ex.message}`;
-            throw ex;
+            this.options.rulePaths.forEach(rulesdir => {
+                debug(`Loading rules from ${rulesdir}`);
+                rules.load(rulesdir, cwd);
+            });
         }
 
-    } else {
-        return null;
+        Object.keys(this.options.rules || {}).forEach(name => {
+            validator.validateRuleOptions(name, this.options.rules[name], "CLI");
+        });
     }
-};
-
-/**
- * Returns results that only contains errors.
- * @param {LintResult[]} results The results to filter.
- * @returns {LintResult[]} The filtered results.
- */
-CLIEngine.getErrorResults = function(results) {
-    const filtered = [];
-
-    results.forEach(result => {
-        const filteredMessages = result.messages.filter(isErrorMessage);
-
-        if (filteredMessages.length > 0) {
-            filtered.push(
-                Object.assign(result, {
-                    messages: filteredMessages,
-                    errorCount: filteredMessages.length,
-                    warningCount: 0
-                })
-            );
-        }
-    });
 
-    return filtered;
-};
+    /**
+     * Returns results that only contains errors.
+     * @param {LintResult[]} results The results to filter.
+     * @returns {LintResult[]} The filtered results.
+     */
+    static getErrorResults(results) {
+        const filtered = [];
+
+        results.forEach(result => {
+            const filteredMessages = result.messages.filter(isErrorMessage);
+
+            if (filteredMessages.length > 0) {
+                filtered.push(
+                    Object.assign(result, {
+                        messages: filteredMessages,
+                        errorCount: filteredMessages.length,
+                        warningCount: 0
+                    })
+                );
+            }
+        });
 
-/**
- * Outputs fixes from the given results to files.
- * @param {Object} report The report object created by CLIEngine.
- * @returns {void}
- */
-CLIEngine.outputFixes = function(report) {
-    report.results.filter(result => result.hasOwnProperty("output")).forEach(result => {
-        fs.writeFileSync(result.filePath, result.output);
-    });
-};
+        return filtered;
+    }
 
-CLIEngine.prototype = {
+    /**
+     * Outputs fixes from the given results to files.
+     * @param {Object} report The report object created by CLIEngine.
+     * @returns {void}
+     */
+    static outputFixes(report) {
+        report.results.filter(result => result.hasOwnProperty("output")).forEach(result => {
+            fs.writeFileSync(result.filePath, result.output);
+        });
+    }
 
-    constructor: CLIEngine,
 
     /**
-     * Add a plugin by passing it's configuration
+     * Add a plugin by passing its configuration
      * @param {string} name Name of the plugin.
      * @param {Object} pluginobject Plugin configuration object.
      * @returns {void}
      */
-    addPlugin(name, pluginobject) {
+    addPlugin(name, pluginobject) { // eslint-disable-line class-methods-use-this
         Plugins.define(name, pluginobject);
-    },
+    }
 
     /**
      * Resolves the patterns passed into executeOnFiles() into glob-based patterns
@@ -576,7 +534,7 @@ CLIEngine.prototype = {
      */
     resolveFileGlobPatterns(patterns) {
         return globUtil.resolveFileGlobPatterns(patterns, this.options);
-    },
+    }
 
     /**
      * Executes the current configuration on an array of file and directory names.
@@ -725,7 +683,7 @@ CLIEngine.prototype = {
             errorCount: stats.errorCount,
             warningCount: stats.warningCount
         };
-    },
+    }
 
     /**
      * Executes the current configuration on text.
@@ -761,7 +719,7 @@ CLIEngine.prototype = {
             errorCount: stats.errorCount,
             warningCount: stats.warningCount
         };
-    },
+    }
 
     /**
      * Returns a configuration object for the given file based on the CLI options.
@@ -774,7 +732,7 @@ CLIEngine.prototype = {
         const configHelper = new Config(this.options);
 
         return configHelper.getConfig(filePath);
-    },
+    }
 
     /**
      * Checks if a given path is ignored by ESLint.
@@ -786,12 +744,51 @@ CLIEngine.prototype = {
         const ignoredPaths = new IgnoredPaths(this.options);
 
         return ignoredPaths.contains(resolvedPath);
-    },
+    }
+
+    /**
+     * Returns the formatter representing the given format or null if no formatter
+     * with the given name can be found.
+     * @param {string} [format] The name of the format to load or the path to a
+     *      custom formatter.
+     * @returns {Function} The formatter function or null if not found.
+     */
+    getFormatter(format) {
+
+        let formatterPath;
 
-    getFormatter: CLIEngine.getFormatter
+        // default is stylish
+        format = format || "stylish";
 
-};
+        // only strings are valid formatters
+        if (typeof format === "string") {
+
+            // replace \ with / for Windows compatibility
+            format = format.replace(/\\/g, "/");
+
+            // if there's a slash, then it's a file
+            if (format.indexOf("/") > -1) {
+                const cwd = this.options ? this.options.cwd : process.cwd();
+
+                formatterPath = path.resolve(cwd, format);
+            } else {
+                formatterPath = `./formatters/${format}`;
+            }
+
+            try {
+                return require(formatterPath);
+            } catch (ex) {
+                ex.message = `There was a problem loading formatter: ${formatterPath}\nError: ${ex.message}`;
+                throw ex;
+            }
+
+        } else {
+            return null;
+        }
+    }
+}
 
 CLIEngine.version = pkg.version;
+CLIEngine.getFormatter = CLIEngine.prototype.getFormatter;
 
 module.exports = CLIEngine;
diff --git a/lib/config/autoconfig.js b/lib/config/autoconfig.js
index 4a50ce25c..6a6fa555b 100644
--- a/lib/config/autoconfig.js
+++ b/lib/config/autoconfig.js
@@ -310,7 +310,7 @@ class Registry {
                 ruleSetIdx += 1;
 
                 if (cb) {
-                    cb(totalFilesLinting);  // eslint-disable-line callback-return
+                    cb(totalFilesLinting); // eslint-disable-line callback-return
                 }
             });
 
diff --git a/lib/config/plugins.js b/lib/config/plugins.js
index e28a77929..aebb3669f 100644
--- a/lib/config/plugins.js
+++ b/lib/config/plugins.js
@@ -75,12 +75,6 @@ module.exports = {
         plugins[shortName] = plugin;
         Environments.importPlugin(plugin, shortName);
         Rules.importPlugin(plugin, shortName);
-
-        // load up environments and rules for the name that '@scope/' was omitted
-        // 3 lines below will be removed by 4.0.0
-        plugins[pluginNameWithoutPrefix] = plugin;
-        Environments.importPlugin(plugin, pluginNameWithoutPrefix);
-        Rules.importPlugin(plugin, pluginNameWithoutPrefix);
     },
 
     /**
diff --git a/lib/eslint.js b/lib/eslint.js
index a9066c4c8..070d36099 100755
--- a/lib/eslint.js
+++ b/lib/eslint.js
@@ -11,7 +11,7 @@
 
 const assert = require("assert"),
     EventEmitter = require("events").EventEmitter,
-    escope = require("escope"),
+    eslintScope = require("eslint-scope"),
     levn = require("levn"),
     blankScriptAST = require("../conf/blank-script.json"),
     DEFAULT_PARSER = require("../conf/eslint-recommended").parser,
@@ -187,7 +187,7 @@ function addDeclaredGlobals(program, globalScope, config) {
         let variable = globalScope.set.get(name);
 
         if (!variable) {
-            variable = new escope.Variable(name, globalScope);
+            variable = new eslintScope.Variable(name, globalScope);
             variable.eslintExplicitGlobal = false;
             globalScope.variables.push(variable);
             globalScope.set.set(name, variable);
@@ -199,7 +199,7 @@ function addDeclaredGlobals(program, globalScope, config) {
         let variable = globalScope.set.get(name);
 
         if (!variable) {
-            variable = new escope.Variable(name, globalScope);
+            variable = new eslintScope.Variable(name, globalScope);
             variable.eslintExplicitGlobal = true;
             variable.eslintExplicitGlobalComment = explicitGlobals[name].comment;
             globalScope.variables.push(variable);
@@ -373,7 +373,7 @@ function modifyConfigsFromComments(filename, ast, config, reportingConfig, messa
 
                     // no default
                 }
-            } else {        // comment.type === "Line"
+            } else { // comment.type === "Line"
                 if (match[1] === "eslint-disable-line") {
                     disableReporting(reportingConfig, { line: comment.loc.start.line, column: 0 }, Object.keys(parseListConfig(value)));
                     enableReporting(reportingConfig, comment.loc.end, Object.keys(parseListConfig(value)));
@@ -889,7 +889,7 @@ module.exports = (function() {
             const ecmaVersion = currentConfig.parserOptions.ecmaVersion || 5;
 
             // gather scope data that may be needed by the rules
-            scopeManager = escope.analyze(ast, {
+            scopeManager = eslintScope.analyze(ast, {
                 ignoreEval: true,
                 nodejsScope: ecmaFeatures.globalReturn,
                 impliedStrict: ecmaFeatures.impliedStrict,
@@ -968,6 +968,8 @@ module.exports = (function() {
             assert.strictEqual(typeof node, "object", "Node must be an object");
         }
 
+        let endLocation;
+
         if (typeof location === "string") {
             assert.ok(node, "Node must be provided when reporting error if location is not provided");
 
@@ -976,11 +978,11 @@ module.exports = (function() {
             opts = message;
             message = location;
             location = node.loc.start;
+            endLocation = node.loc.end;
+        } else {
+            endLocation = location.end;
         }
 
-        // Store end location.
-        const endLocation = location.end;
-
         location = location.start || location;
 
         if (isDisabledByReportingConfig(reportingConfig, ruleId, location)) {
@@ -1003,7 +1005,7 @@ module.exports = (function() {
             severity,
             message,
             line: location.line,
-            column: location.column + 1,   // switch to 1-base instead of 0-base
+            column: location.column + 1, // switch to 1-base instead of 0-base
             nodeType: node && node.type,
             source: sourceCode.lines[location.line - 1] || ""
         };
@@ -1011,7 +1013,7 @@ module.exports = (function() {
         // Define endLine and endColumn if exists.
         if (endLocation) {
             problem.endLine = endLocation.line;
-            problem.endColumn = endLocation.column + 1;   // switch to 1-base instead of 0-base
+            problem.endColumn = endLocation.column + 1; // switch to 1-base instead of 0-base
         }
 
         // ensure there's range and text properties, otherwise it's not a valid fix
@@ -1223,7 +1225,7 @@ module.exports = (function() {
      * - others - always an empty array.
      *
      * @param {ASTNode} node A node to get.
-     * @returns {escope.Variable[]} Variables that are declared by the node.
+     * @returns {eslint-scope.Variable[]} Variables that are declared by the node.
      */
     api.getDeclaredVariables = function(node) {
         return (scopeManager && scopeManager.getDeclaredVariables(node)) || [];
diff --git a/lib/ignored-paths.js b/lib/ignored-paths.js
index cfca7fa4f..f69e1f61f 100644
--- a/lib/ignored-paths.js
+++ b/lib/ignored-paths.js
@@ -179,7 +179,7 @@ class IgnoredPaths {
 
         let result = false;
         const absolutePath = path.resolve(this.options.cwd, filepath);
-        const relativePath = pathUtil.getRelativePath(absolutePath, this.options.cwd);
+        const relativePath = pathUtil.getRelativePath(absolutePath, this.baseDir);
 
         if ((typeof category === "undefined") || (category === "default")) {
             result = result || (this.ig.default.filter([relativePath]).length === 0);
@@ -213,15 +213,7 @@ class IgnoredPaths {
 
         const filter = ig.createFilter();
 
-        /**
-         * TODO
-         * 1.
-         * Actually, it should be `this.options.baseDir`, which is the base dir of `ignore-path`,
-         * as well as Line 177.
-         * But doing this leads to a breaking change and fails tests.
-         * Related to #6759
-         */
-        const base = this.options.cwd;
+        const base = this.baseDir;
 
         return function(absolutePath) {
             const relative = pathUtil.getRelativePath(absolutePath, base);
diff --git a/lib/rules/array-callback-return.js b/lib/rules/array-callback-return.js
index cf64a98e3..05c1a11be 100644
--- a/lib/rules/array-callback-return.js
+++ b/lib/rules/array-callback-return.js
@@ -138,7 +138,7 @@ module.exports = {
         docs: {
             description: "enforce `return` statements in callbacks of array methods",
             category: "Best Practices",
-            recommended: false
+            recommended: true
         },
 
         schema: []
diff --git a/lib/rules/block-scoped-var.js b/lib/rules/block-scoped-var.js
index bb0931a3c..0b4d855fa 100644
--- a/lib/rules/block-scoped-var.js
+++ b/lib/rules/block-scoped-var.js
@@ -41,7 +41,7 @@ module.exports = {
 
         /**
          * Reports a given reference.
-         * @param {escope.Reference} reference - A reference to report.
+         * @param {eslint-scope.Reference} reference - A reference to report.
          * @returns {void}
          */
         function report(reference) {
diff --git a/lib/rules/capitalized-comments.js b/lib/rules/capitalized-comments.js
index b8d5b8cd6..be2cf932a 100644
--- a/lib/rules/capitalized-comments.js
+++ b/lib/rules/capitalized-comments.js
@@ -19,7 +19,7 @@ const ALWAYS_MESSAGE = "Comments should not begin with a lowercase character",
     NEVER_MESSAGE = "Comments should not begin with an uppercase character",
     DEFAULT_IGNORE_PATTERN = astUtils.COMMENTS_IGNORE_PATTERN,
     WHITESPACE = /\s/g,
-    MAYBE_URL = /^\s*[^:/?#\s]+:\/\/[^?#]/,    // TODO: Combine w/ max-len pattern?
+    MAYBE_URL = /^\s*[^:/?#\s]+:\/\/[^?#]/, // TODO: Combine w/ max-len pattern?
     DEFAULTS = {
         ignorePattern: null,
         ignoreInlineComments: false,
@@ -270,7 +270,7 @@ module.exports = {
                     : NEVER_MESSAGE;
 
                 context.report({
-                    node: null,         // Intentionally using loc instead
+                    node: null, // Intentionally using loc instead
                     loc: comment.loc,
                     message,
                     fix(fixer) {
diff --git a/lib/rules/func-names.js b/lib/rules/func-names.js
index e7f950c9b..848ce9757 100644
--- a/lib/rules/func-names.js
+++ b/lib/rules/func-names.js
@@ -13,7 +13,7 @@ const astUtils = require("../ast-utils");
 
 /**
  * Checks whether or not a given variable is a function name.
- * @param {escope.Variable} variable - A variable to check.
+ * @param {eslint-scope.Variable} variable - A variable to check.
  * @returns {boolean} `true` if the variable is a function name.
  */
 function isFunctionName(variable) {
diff --git a/lib/rules/global-require.js b/lib/rules/global-require.js
index 367fe590e..beda8d99f 100644
--- a/lib/rules/global-require.js
+++ b/lib/rules/global-require.js
@@ -17,7 +17,7 @@ const ACCEPTABLE_PARENTS = [
 ];
 
 /**
- * Finds the escope reference in the given scope.
+ * Finds the eslint-scope reference in the given scope.
  * @param {Object} scope The scope to search.
  * @param {ASTNode} node The identifier node.
  * @returns {Reference|null} Returns the found reference or null if none were found.
diff --git a/lib/rules/id-length.js b/lib/rules/id-length.js
index 341f929cd..dad9c4064 100644
--- a/lib/rules/id-length.js
+++ b/lib/rules/id-length.js
@@ -96,7 +96,7 @@ module.exports = {
                 const isLong = name.length > maxLength;
 
                 if (!(isShort || isLong) || exceptions[name]) {
-                    return;  // Nothing to report
+                    return; // Nothing to report
                 }
 
                 const isValidExpression = SUPPORTED_EXPRESSIONS[parent.type];
diff --git a/lib/rules/max-len.js b/lib/rules/max-len.js
index dd5a4e1ef..b0fa95814 100644
--- a/lib/rules/max-len.js
+++ b/lib/rules/max-len.js
@@ -108,7 +108,7 @@ module.exports = {
                     previousTabStopOffset = tabWidth ? totalOffset % tabWidth : 0,
                     spaceCount = tabWidth - previousTabStopOffset;
 
-                extraCharacterCount += spaceCount - 1;  // -1 for the replaced tab
+                extraCharacterCount += spaceCount - 1; // -1 for the replaced tab
             });
             return Array.from(line).length + extraCharacterCount;
         }
diff --git a/lib/rules/new-parens.js b/lib/rules/new-parens.js
index ad37979d5..aa0f7fe93 100644
--- a/lib/rules/new-parens.js
+++ b/lib/rules/new-parens.js
@@ -38,7 +38,7 @@ module.exports = {
         return {
             NewExpression(node) {
                 if (node.arguments.length !== 0) {
-                    return;  // shortcut: if there are arguments, there have to be parens
+                    return; // shortcut: if there are arguments, there have to be parens
                 }
 
                 const lastToken = sourceCode.getLastToken(node);
diff --git a/lib/rules/newline-before-return.js b/lib/rules/newline-before-return.js
index 996039b69..6e64cb766 100644
--- a/lib/rules/newline-before-return.js
+++ b/lib/rules/newline-before-return.js
@@ -121,7 +121,7 @@ module.exports = {
             if (tokenBefore) {
                 lineNumTokenBefore = tokenBefore.loc.end.line;
             } else {
-                lineNumTokenBefore = 0;     // global return at beginning of script
+                lineNumTokenBefore = 0; // global return at beginning of script
             }
 
             return lineNumTokenBefore;
diff --git a/lib/rules/no-alert.js b/lib/rules/no-alert.js
index f2cfc3a87..232fa1444 100644
--- a/lib/rules/no-alert.js
+++ b/lib/rules/no-alert.js
@@ -35,7 +35,7 @@ function report(context, node, identifierName) {
 }
 
 /**
- * Finds the escope reference in the given scope.
+ * Finds the eslint-scope reference in the given scope.
  * @param {Object} scope The scope to search.
  * @param {ASTNode} node The identifier node.
  * @returns {Reference|null} Returns the found reference or null if none were found.
diff --git a/lib/rules/no-compare-neg-zero.js b/lib/rules/no-compare-neg-zero.js
index d93ade5d3..604e22191 100644
--- a/lib/rules/no-compare-neg-zero.js
+++ b/lib/rules/no-compare-neg-zero.js
@@ -13,7 +13,7 @@ module.exports = {
         docs: {
             description: "disallow comparing against -0",
             category: "Possible Errors",
-            recommended: false
+            recommended: true
         },
         fixable: null,
         schema: []
diff --git a/lib/rules/no-console.js b/lib/rules/no-console.js
index 9131a49a0..32bdf6d1f 100644
--- a/lib/rules/no-console.js
+++ b/lib/rules/no-console.js
@@ -48,7 +48,7 @@ module.exports = {
         /**
          * Checks whether the given reference is 'console' or not.
          *
-         * @param {escope.Reference} reference - The reference to check.
+         * @param {eslint-scope.Reference} reference - The reference to check.
          * @returns {boolean} `true` if the reference is 'console'.
          */
         function isConsole(reference) {
@@ -74,7 +74,7 @@ module.exports = {
          * Checks whether the given reference is a member access which is not
          * allowed by options or not.
          *
-         * @param {escope.Reference} reference - The reference to check.
+         * @param {eslint-scope.Reference} reference - The reference to check.
          * @returns {boolean} `true` if the reference is a member access which
          *      is not allowed by options.
          */
@@ -92,7 +92,7 @@ module.exports = {
         /**
          * Reports the given reference as a violation.
          *
-         * @param {escope.Reference} reference - The reference to report.
+         * @param {eslint-scope.Reference} reference - The reference to report.
          * @returns {void}
          */
         function report(reference) {
diff --git a/lib/rules/no-dupe-args.js b/lib/rules/no-dupe-args.js
index cdb38035c..c932be01d 100644
--- a/lib/rules/no-dupe-args.js
+++ b/lib/rules/no-dupe-args.js
@@ -28,7 +28,7 @@ module.exports = {
 
         /**
          * Checks whether or not a given definition is a parameter's.
-         * @param {escope.DefEntry} def - A definition to check.
+         * @param {eslint-scope.DefEntry} def - A definition to check.
          * @returns {boolean} `true` if the definition is a parameter's.
          */
         function isParameter(def) {
diff --git a/lib/rules/no-eval.js b/lib/rules/no-eval.js
index fe1456cba..ee5f577f4 100644
--- a/lib/rules/no-eval.js
+++ b/lib/rules/no-eval.js
@@ -166,7 +166,7 @@ module.exports = {
         /**
          * Reports accesses of `eval` via the global object.
          *
-         * @param {escope.Scope} globalScope - The global scope.
+         * @param {eslint-scope.Scope} globalScope - The global scope.
          * @returns {void}
          */
         function reportAccessingEvalViaGlobalObject(globalScope) {
@@ -200,7 +200,7 @@ module.exports = {
         /**
          * Reports all accesses of `eval` (excludes direct calls to eval).
          *
-         * @param {escope.Scope} globalScope - The global scope.
+         * @param {eslint-scope.Scope} globalScope - The global scope.
          * @returns {void}
          */
         function reportAccessingEval(globalScope) {
diff --git a/lib/rules/no-loop-func.js b/lib/rules/no-loop-func.js
index b8bed9586..df0f17678 100644
--- a/lib/rules/no-loop-func.js
+++ b/lib/rules/no-loop-func.js
@@ -90,7 +90,7 @@ function getTopLoopNode(node, excludedNode) {
  *
  * @param {ASTNode} funcNode - A target function node.
  * @param {ASTNode} loopNode - A containing loop node.
- * @param {escope.Reference} reference - A reference to check.
+ * @param {eslint-scope.Reference} reference - A reference to check.
  * @returns {boolean} `true` if the reference is safe or not.
  */
 function isSafe(funcNode, loopNode, reference) {
@@ -131,7 +131,7 @@ function isSafe(funcNode, loopNode, reference) {
      * - is readonly.
      * - doesn't exist inside a local function and after the border.
      *
-     * @param {escope.Reference} upperRef - A reference to check.
+     * @param {eslint-scope.Reference} upperRef - A reference to check.
      * @returns {boolean} `true` if the reference is safe.
      */
     function isSafeReference(upperRef) {
diff --git a/lib/rules/no-multi-spaces.js b/lib/rules/no-multi-spaces.js
index 41a7f924a..f39b578be 100644
--- a/lib/rules/no-multi-spaces.js
+++ b/lib/rules/no-multi-spaces.js
@@ -33,6 +33,9 @@ module.exports = {
                             }
                         },
                         additionalProperties: false
+                    },
+                    ignoreEOLComments: {
+                        type: "boolean"
                     }
                 },
                 additionalProperties: false
@@ -43,8 +46,10 @@ module.exports = {
     create(context) {
 
         // the index of the last comment that was checked
-        const exceptions = { Property: true },
-            options = context.options[0];
+        const sourceCode = context.getSourceCode(),
+            exceptions = { Property: true },
+            options = context.options[0] || {},
+            ignoreEOLComments = options.ignoreEOLComments;
         let hasExceptions = true,
             lastCommentIndex = 0;
 
@@ -59,6 +64,23 @@ module.exports = {
             hasExceptions = Object.keys(exceptions).length > 0;
         }
 
+        /**
+         * Checks if a given token is the last token of the line or not.
+         * @param {Token} token The token to check.
+         * @returns {boolean} Whether or not a token is at the end of the line it occurs in.
+         * @private
+         */
+        function isLastTokenOfLine(token) {
+            const nextToken = sourceCode.getTokenAfter(token, { includeComments: true });
+
+            // nextToken is null if the comment is the last token in the program.
+            if (!nextToken) {
+                return true;
+            }
+
+            return !astUtils.isTokenOnSameLine(token, nextToken);
+        }
+
         /**
          * Determines if a given source index is in a comment or not by checking
          * the index against the comment range. Since the check goes straight
@@ -73,7 +95,7 @@ module.exports = {
             while (lastCommentIndex < comments.length) {
                 const comment = comments[lastCommentIndex];
 
-                if (comment.range[0] <= index && index < comment.range[1]) {
+                if (comment.range[0] < index && index < comment.range[1]) {
                     return true;
                 } else if (index > comment.range[1]) {
                     lastCommentIndex++;
@@ -85,6 +107,33 @@ module.exports = {
             return false;
         }
 
+        /**
+         * Formats value of given comment token for error message by truncating its length.
+         * @param {Token} token comment token
+         * @returns {string} formatted value
+         * @private
+         */
+        function formatReportedCommentValue(token) {
+            const valueLines = token.value.split("\n");
+            const value = valueLines[0];
+            const formattedValue = `${value.substring(0, 12)}...`;
+
+            return valueLines.length === 1 && value.length <= 12 ? value : formattedValue;
+        }
+
+        /**
+         * Creates a fix function that removes the multiple spaces between the two tokens
+         * @param {Token} leftToken left token
+         * @param {Token} rightToken right token
+         * @returns {Function} fix function
+         * @private
+         */
+        function createFix(leftToken, rightToken) {
+            return function(fixer) {
+                return fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], " ");
+            };
+        }
+
         //--------------------------------------------------------------------------
         // Public
         //--------------------------------------------------------------------------
@@ -92,47 +141,44 @@ module.exports = {
         return {
             Program() {
 
-                const sourceCode = context.getSourceCode(),
-                    source = sourceCode.getText(),
+                const source = sourceCode.getText(),
                     allComments = sourceCode.getAllComments(),
                     JOINED_LINEBEAKS = Array.from(astUtils.LINEBREAKS).join(""),
-                    pattern = new RegExp(String.raw`[^ \t${JOINED_LINEBEAKS}].? {2,}`, "g");  // note: repeating space
+                    pattern = new RegExp(String.raw`[^ \t${JOINED_LINEBEAKS}].? {2,}`, "g"); // note: repeating space
                 let parent;
 
-
-                /**
-                 * Creates a fix function that removes the multiple spaces between the two tokens
-                 * @param {RuleFixer} leftToken left token
-                 * @param {RuleFixer} rightToken right token
-                 * @returns {Function} fix function
-                 * @private
-                 */
-                function createFix(leftToken, rightToken) {
-                    return function(fixer) {
-                        return fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], " ");
-                    };
-                }
-
                 while (pattern.test(source)) {
 
                     // do not flag anything inside of comments
                     if (!isIndexInComment(pattern.lastIndex, allComments)) {
 
-                        const token = sourceCode.getTokenByRangeStart(pattern.lastIndex);
+                        const token = sourceCode.getTokenByRangeStart(pattern.lastIndex, { includeComments: true });
 
                         if (token) {
-                            const previousToken = sourceCode.getTokenBefore(token);
+                            if (ignoreEOLComments && astUtils.isCommentToken(token) && isLastTokenOfLine(token)) {
+                                return;
+                            }
+
+                            const previousToken = sourceCode.getTokenBefore(token, { includeComments: true });
 
                             if (hasExceptions) {
                                 parent = sourceCode.getNodeByRangeIndex(pattern.lastIndex - 1);
                             }
 
                             if (!parent || !exceptions[parent.type]) {
+                                let value = token.value;
+
+                                if (token.type === "Block") {
+                                    value = `/*${formatReportedCommentValue(token)}*/`;
+                                } else if (token.type === "Line") {
+                                    value = `//${formatReportedCommentValue(token)}`;
+                                }
+
                                 context.report({
                                     node: token,
                                     loc: token.loc.start,
                                     message: "Multiple spaces found before '{{value}}'.",
-                                    data: { value: token.value },
+                                    data: { value },
                                     fix: createFix(previousToken, token)
                                 });
                             }
diff --git a/lib/rules/no-multiple-empty-lines.js b/lib/rules/no-multiple-empty-lines.js
index 2063ebd91..9d1067c20 100644
--- a/lib/rules/no-multiple-empty-lines.js
+++ b/lib/rules/no-multiple-empty-lines.js
@@ -111,10 +111,19 @@ module.exports = {
                                 message,
                                 data: { max: maxAllowed, pluralizedLines: maxAllowed === 1 ? "line" : "lines" },
                                 fix(fixer) {
-                                    return fixer.removeRange([
-                                        sourceCode.getIndexFromLoc({ line: lastLineNumber + 1, column: 0 }),
-                                        sourceCode.getIndexFromLoc({ line: lineNumber - maxAllowed, column: 0 })
-                                    ]);
+                                    const rangeStart = sourceCode.getIndexFromLoc({ line: lastLineNumber + 1, column: 0 });
+
+                                    /*
+                                     * The end of the removal range is usually the start index of the next line.
+                                     * However, at the end of the file there is no next line, so the end of the
+                                     * range is just the length of the text.
+                                     */
+                                    const lineNumberAfterRemovedLines = lineNumber - maxAllowed;
+                                    const rangeEnd = lineNumberAfterRemovedLines <= allLines.length
+                                        ? sourceCode.getIndexFromLoc({ line: lineNumberAfterRemovedLines, column: 0 })
+                                        : sourceCode.text.length;
+
+                                    return fixer.removeRange([rangeStart, rangeEnd]);
                                 }
                             });
                         }
diff --git a/lib/rules/no-redeclare.js b/lib/rules/no-redeclare.js
index bfbc09ffb..ccb57003e 100644
--- a/lib/rules/no-redeclare.js
+++ b/lib/rules/no-redeclare.js
@@ -35,7 +35,7 @@ module.exports = {
 
         /**
          * Find variables in a given scope and flag redeclared ones.
-         * @param {Scope} scope - An escope scope object.
+         * @param {Scope} scope - An eslint-scope scope object.
          * @returns {void}
          * @private
          */
diff --git a/lib/rules/no-undefined.js b/lib/rules/no-undefined.js
index d29ac1e72..7e9f96b92 100644
--- a/lib/rules/no-undefined.js
+++ b/lib/rules/no-undefined.js
@@ -36,7 +36,7 @@ module.exports = {
         /**
          * Checks the given scope for references to `undefined` and reports
          * all references found.
-         * @param {escope.Scope} scope The scope to check.
+         * @param {eslint-scope.Scope} scope The scope to check.
          * @returns {void}
          */
         function checkScope(scope) {
diff --git a/lib/rules/no-unmodified-loop-condition.js b/lib/rules/no-unmodified-loop-condition.js
index 824361191..dbf35baed 100644
--- a/lib/rules/no-unmodified-loop-condition.js
+++ b/lib/rules/no-unmodified-loop-condition.js
@@ -18,14 +18,14 @@ const Traverser = require("../util/traverser"),
 
 const pushAll = Function.apply.bind(Array.prototype.push);
 const SENTINEL_PATTERN = /(?:(?:Call|Class|Function|Member|New|Yield)Expression|Statement|Declaration)$/;
-const LOOP_PATTERN = /^(?:DoWhile|For|While)Statement$/;  // for-in/of statements don't have `test` property.
+const LOOP_PATTERN = /^(?:DoWhile|For|While)Statement$/; // for-in/of statements don't have `test` property.
 const GROUP_PATTERN = /^(?:BinaryExpression|ConditionalExpression)$/;
 const SKIP_PATTERN = /^(?:ArrowFunction|Class|Function)Expression$/;
 const DYNAMIC_PATTERN = /^(?:Call|Member|New|TaggedTemplate|Yield)Expression$/;
 
 /**
  * @typedef {Object} LoopConditionInfo
- * @property {escope.Reference} reference - The reference.
+ * @property {eslint-scope.Reference} reference - The reference.
  * @property {ASTNode} group - BinaryExpression or ConditionalExpression nodes
  *      that the reference is belonging to.
  * @property {Function} isInLoop - The predicate which checks a given reference
@@ -37,7 +37,7 @@ const DYNAMIC_PATTERN = /^(?:Call|Member|New|TaggedTemplate|Yield)Expression$/;
 /**
  * Checks whether or not a given reference is a write reference.
  *
- * @param {escope.Reference} reference - A reference to check.
+ * @param {eslint-scope.Reference} reference - A reference to check.
  * @returns {boolean} `true` if the reference is a write reference.
  */
 function isWriteReference(reference) {
@@ -77,7 +77,7 @@ function isUnmodifiedAndNotBelongToGroup(condition) {
  * Checks whether or not a given reference is inside of a given node.
  *
  * @param {ASTNode} node - A node to check.
- * @param {escope.Reference} reference - A reference to check.
+ * @param {eslint-scope.Reference} reference - A reference to check.
  * @returns {boolean} `true` if the reference is inside of the node.
  */
 function isInRange(node, reference) {
@@ -91,7 +91,7 @@ function isInRange(node, reference) {
  * Checks whether or not a given reference is inside of a loop node's condition.
  *
  * @param {ASTNode} node - A node to check.
- * @param {escope.Reference} reference - A reference to check.
+ * @param {eslint-scope.Reference} reference - A reference to check.
  * @returns {boolean} `true` if the reference is inside of the loop node's
  *      condition.
  */
@@ -134,7 +134,7 @@ function hasDynamicExpressions(root) {
 /**
  * Creates the loop condition information from a given reference.
  *
- * @param {escope.Reference} reference - A reference to create.
+ * @param {eslint-scope.Reference} reference - A reference to create.
  * @returns {LoopConditionInfo|null} Created loop condition info, or null.
  */
 function toLoopCondition(reference) {
@@ -188,7 +188,7 @@ function toLoopCondition(reference) {
  * Gets the function which encloses a given reference.
  * This supports only FunctionDeclaration.
  *
- * @param {escope.Reference} reference - A reference to get.
+ * @param {eslint-scope.Reference} reference - A reference to get.
  * @returns {ASTNode|null} The function node or null.
  */
 function getEncloseFunctionDeclaration(reference) {
@@ -209,7 +209,7 @@ function getEncloseFunctionDeclaration(reference) {
  * Updates the "modified" flags of given loop conditions with given modifiers.
  *
  * @param {LoopConditionInfo[]} conditions - The loop conditions to be updated.
- * @param {escope.Reference[]} modifiers - The references to update.
+ * @param {eslint-scope.Reference[]} modifiers - The references to update.
  * @returns {void}
  */
 function updateModifiedFlag(conditions, modifiers) {
@@ -311,7 +311,7 @@ module.exports = {
          * Finds unmodified references which are inside of a loop condition.
          * Then reports the references which are outside of groups.
          *
-         * @param {escope.Variable} variable - A variable to report.
+         * @param {eslint-scope.Variable} variable - A variable to report.
          * @returns {void}
          */
         function checkReferences(variable) {
diff --git a/lib/rules/no-unused-vars.js b/lib/rules/no-unused-vars.js
index 6f2703967..3ff9bc64f 100644
--- a/lib/rules/no-unused-vars.js
+++ b/lib/rules/no-unused-vars.js
@@ -108,7 +108,7 @@ module.exports = {
 
         /**
          * Determines if a given variable is being exported from a module.
-         * @param {Variable} variable - EScope variable object.
+         * @param {Variable} variable - eslint-scope variable object.
          * @returns {boolean} True if the variable is exported, false if not.
          * @private
          */
@@ -134,7 +134,7 @@ module.exports = {
 
         /**
          * Determines if a variable has a sibling rest property
-         * @param {Variable} variable - EScope variable object.
+         * @param {Variable} variable - eslint-scope variable object.
          * @returns {boolean} True if the variable is exported, false if not.
          * @private
          */
@@ -157,7 +157,7 @@ module.exports = {
 
         /**
          * Determines if a reference is a read operation.
-         * @param {Reference} ref - An escope Reference
+         * @param {Reference} ref - An eslint-scope Reference
          * @returns {boolean} whether the given reference represents a read operation
          * @private
          */
@@ -212,7 +212,7 @@ module.exports = {
          * - The reference is inside of a function scope which is different from
          *   the declaration.
          *
-         * @param {escope.Reference} ref - A reference to check.
+         * @param {eslint-scope.Reference} ref - A reference to check.
          * @param {ASTNode} prevRhsNode - The previous RHS node.
          * @returns {ASTNode|null} The RHS node or null.
          * @private
@@ -322,7 +322,7 @@ module.exports = {
         /**
          * Checks whether a given reference is a read to update itself or not.
          *
-         * @param {escope.Reference} ref - A reference to check.
+         * @param {eslint-scope.Reference} ref - A reference to check.
          * @param {ASTNode} rhsNode - The RHS node of the previous assignment.
          * @returns {boolean} The reference is a read to update itself.
          * @private
@@ -422,7 +422,7 @@ module.exports = {
         /**
          * Checks whether the given variable is the last parameter in the non-ignored parameters.
          *
-         * @param {escope.Variable} variable - The variable to check.
+         * @param {eslint-scope.Variable} variable - The variable to check.
          * @returns {boolean} `true` if the variable is the last.
          */
         function isLastInNonIgnoredParameters(variable) {
@@ -448,7 +448,7 @@ module.exports = {
 
         /**
          * Gets an array of variables without read references.
-         * @param {Scope} scope - an escope Scope object.
+         * @param {Scope} scope - an eslint-scope Scope object.
          * @param {Variable[]} unusedVars - an array that saving result.
          * @returns {Variable[]} unused variables of the scope and descendant scopes.
          * @private
@@ -540,7 +540,7 @@ module.exports = {
 
         /**
          * Gets the index of a given variable name in a given comment.
-         * @param {escope.Variable} variable - A variable to get.
+         * @param {eslint-scope.Variable} variable - A variable to get.
          * @param {ASTNode} comment - A comment node which includes the variable name.
          * @returns {number} The index of the variable name's location.
          * @private
@@ -561,7 +561,7 @@ module.exports = {
          * Creates the correct location of a given variables.
          * The location is at its name string in a `/*global` comment.
          *
-         * @param {escope.Variable} variable - A variable to get its location.
+         * @param {eslint-scope.Variable} variable - A variable to get its location.
          * @returns {{line: number, column: number}} The location object for the variable.
          * @private
          */
diff --git a/lib/rules/no-use-before-define.js b/lib/rules/no-use-before-define.js
index 1a779b86f..6d01e3e10 100644
--- a/lib/rules/no-use-before-define.js
+++ b/lib/rules/no-use-before-define.js
@@ -37,7 +37,7 @@ function parseOptions(options) {
 /**
  * Checks whether or not a given variable is a function declaration.
  *
- * @param {escope.Variable} variable - A variable to check.
+ * @param {eslint-scope.Variable} variable - A variable to check.
  * @returns {boolean} `true` if the variable is a function declaration.
  */
 function isFunction(variable) {
@@ -47,8 +47,8 @@ function isFunction(variable) {
 /**
  * Checks whether or not a given variable is a class declaration in an upper function scope.
  *
- * @param {escope.Variable} variable - A variable to check.
- * @param {escope.Reference} reference - A reference to check.
+ * @param {eslint-scope.Variable} variable - A variable to check.
+ * @param {eslint-scope.Reference} reference - A reference to check.
  * @returns {boolean} `true` if the variable is a class declaration.
  */
 function isOuterClass(variable, reference) {
@@ -60,8 +60,8 @@ function isOuterClass(variable, reference) {
 
 /**
 * Checks whether or not a given variable is a variable declaration in an upper function scope.
-* @param {escope.Variable} variable - A variable to check.
-* @param {escope.Reference} reference - A reference to check.
+* @param {eslint-scope.Variable} variable - A variable to check.
+* @param {eslint-scope.Reference} reference - A reference to check.
 * @returns {boolean} `true` if the variable is a variable declaration.
 */
 function isOuterVariable(variable, reference) {
@@ -167,8 +167,8 @@ module.exports = {
 
         /**
          * Determines whether a given use-before-define case should be reported according to the options.
-         * @param {escope.Variable} variable The variable that gets used before being defined
-         * @param {escope.Reference} reference The reference to the variable
+         * @param {eslint-scope.Variable} variable The variable that gets used before being defined
+         * @param {eslint-scope.Reference} reference The reference to the variable
          * @returns {boolean} `true` if the usage should be reported
          */
         function isForbidden(variable, reference) {
diff --git a/lib/rules/no-useless-escape.js b/lib/rules/no-useless-escape.js
index ffe119997..0212bd60e 100644
--- a/lib/rules/no-useless-escape.js
+++ b/lib/rules/no-useless-escape.js
@@ -75,7 +75,7 @@ module.exports = {
         docs: {
             description: "disallow unnecessary escape characters",
             category: "Best Practices",
-            recommended: false
+            recommended: true
         },
 
         schema: []
diff --git a/lib/rules/no-var.js b/lib/rules/no-var.js
index 86373ad50..c74e0b9ad 100644
--- a/lib/rules/no-var.js
+++ b/lib/rules/no-var.js
@@ -19,8 +19,8 @@ const astUtils = require("../ast-utils");
  * Finds the nearest function scope or global scope walking up the scope
  * hierarchy.
  *
- * @param {escope.Scope} scope - The scope to traverse.
- * @returns {escope.Scope} a function scope or global scope containing the given
+ * @param {eslint-scope.Scope} scope - The scope to traverse.
+ * @returns {eslint-scope.Scope} a function scope or global scope containing the given
  *      scope.
  */
 function getEnclosingFunctionScope(scope) {
@@ -34,7 +34,7 @@ function getEnclosingFunctionScope(scope) {
  * Checks whether the given variable has any references from a more specific
  * function expression (i.e. a closure).
  *
- * @param {escope.Variable} variable - A variable to check.
+ * @param {eslint-scope.Variable} variable - A variable to check.
  * @returns {boolean} `true` if the variable is used from a closure.
  */
 function isReferencedInClosure(variable) {
@@ -93,7 +93,7 @@ function getScopeNode(node) {
 /**
  * Checks whether a given variable is redeclared or not.
  *
- * @param {escope.Variable} variable - A variable to check.
+ * @param {eslint-scope.Variable} variable - A variable to check.
  * @returns {boolean} `true` if the variable is redeclared.
  */
 function isRedeclared(variable) {
@@ -112,7 +112,7 @@ function isUsedFromOutsideOf(scopeNode) {
     /**
      * Checks whether a given reference is inside of the specified scope or not.
      *
-     * @param {escope.Reference} reference - A reference to check.
+     * @param {eslint-scope.Reference} reference - A reference to check.
      * @returns {boolean} `true` if the reference is inside of the specified
      *      scope.
      */
diff --git a/lib/rules/padded-blocks.js b/lib/rules/padded-blocks.js
index 36036aec4..a485c1770 100644
--- a/lib/rules/padded-blocks.js
+++ b/lib/rules/padded-blocks.js
@@ -51,7 +51,11 @@ module.exports = {
         const config = context.options[0] || "always";
 
         if (typeof config === "string") {
-            options.blocks = config === "always";
+            const shouldHavePadding = config === "always";
+
+            options.blocks = shouldHavePadding;
+            options.switches = shouldHavePadding;
+            options.classes = shouldHavePadding;
         } else {
             if (config.hasOwnProperty("blocks")) {
                 options.blocks = config.blocks === "always";
diff --git a/lib/rules/prefer-arrow-callback.js b/lib/rules/prefer-arrow-callback.js
index ee385042f..a867a9390 100644
--- a/lib/rules/prefer-arrow-callback.js
+++ b/lib/rules/prefer-arrow-callback.js
@@ -11,7 +11,7 @@
 
 /**
  * Checks whether or not a given variable is a function name.
- * @param {escope.Variable} variable - A variable to check.
+ * @param {eslint-scope.Variable} variable - A variable to check.
  * @returns {boolean} `true` if the variable is a function name.
  */
 function isFunctionName(variable) {
@@ -31,8 +31,8 @@ function checkMetaProperty(node, metaName, propertyName) {
 
 /**
  * Gets the variable object of `arguments` which is defined implicitly.
- * @param {escope.Scope} scope - A scope to get.
- * @returns {escope.Variable} The found variable object.
+ * @param {eslint-scope.Scope} scope - A scope to get.
+ * @returns {eslint-scope.Variable} The found variable object.
  */
 function getVariableOfArguments(scope) {
     const variables = scope.variables;
@@ -159,7 +159,7 @@ module.exports = {
     create(context) {
         const options = context.options[0] || {};
 
-        const allowUnboundThis = options.allowUnboundThis !== false;  // default to true
+        const allowUnboundThis = options.allowUnboundThis !== false; // default to true
         const allowNamedFunctions = options.allowNamedFunctions;
         const sourceCode = context.getSourceCode();
 
diff --git a/lib/rules/prefer-const.js b/lib/rules/prefer-const.js
index 53bdc2ab7..595bb8a53 100644
--- a/lib/rules/prefer-const.js
+++ b/lib/rules/prefer-const.js
@@ -73,7 +73,7 @@ function canBecomeVariableDeclaration(identifier) {
  *   warn such variables because this rule cannot distinguish whether the
  *   exported variables are reassigned or not.
  *
- * @param {escope.Variable} variable - A variable to get.
+ * @param {eslint-scope.Variable} variable - A variable to get.
  * @param {boolean} ignoreReadBeforeAssign -
  *      The value of `ignoreReadBeforeAssign` option.
  * @returns {ASTNode|null}
@@ -146,7 +146,7 @@ function getIdentifierIfShouldBeConst(variable, ignoreReadBeforeAssign) {
  * This is used to detect a mix of reassigned and never reassigned in a
  * destructuring.
  *
- * @param {escope.Reference} reference - A reference to get.
+ * @param {eslint-scope.Reference} reference - A reference to get.
  * @returns {ASTNode|null} A VariableDeclarator/AssignmentExpression node or
  *      null.
  */
@@ -172,7 +172,7 @@ function getDestructuringHost(reference) {
  * This is used to detect a mix of reassigned and never reassigned in a
  * destructuring.
  *
- * @param {escope.Variable[]} variables - Variables to group by destructuring.
+ * @param {eslint-scope.Variable[]} variables - Variables to group by destructuring.
  * @param {boolean} ignoreReadBeforeAssign -
  *      The value of `ignoreReadBeforeAssign` option.
  * @returns {Map<ASTNode, ASTNode[]>} Grouped identifier nodes.
@@ -274,7 +274,7 @@ module.exports = {
          * the array is 1. In destructuring cases, the length of the array can
          * be 2 or more.
          *
-         * @param {(escope.Reference|null)[]} nodes -
+         * @param {(eslint-scope.Reference|null)[]} nodes -
          *      References which are grouped by destructuring to report.
          * @returns {void}
          */
diff --git a/lib/rules/prefer-rest-params.js b/lib/rules/prefer-rest-params.js
index a9db624dc..d55d5dad0 100644
--- a/lib/rules/prefer-rest-params.js
+++ b/lib/rules/prefer-rest-params.js
@@ -11,8 +11,8 @@
 
 /**
  * Gets the variable object of `arguments` which is defined implicitly.
- * @param {escope.Scope} scope - A scope to get.
- * @returns {escope.Variable} The found variable object.
+ * @param {eslint-scope.Scope} scope - A scope to get.
+ * @returns {eslint-scope.Variable} The found variable object.
  */
 function getVariableOfArguments(scope) {
     const variables = scope.variables;
@@ -40,7 +40,7 @@ function getVariableOfArguments(scope) {
  * - arguments[0]      .... true    // computed member access
  * - arguments.length  .... false   // normal member access
  *
- * @param {escope.Reference} reference - The reference to check.
+ * @param {eslint-scope.Reference} reference - The reference to check.
  * @returns {boolean} `true` if the reference is not normal member access.
  */
 function isNotNormalMemberAccess(reference) {
@@ -74,7 +74,7 @@ module.exports = {
         /**
          * Reports a given reference.
          *
-         * @param {escope.Reference} reference - A reference to report.
+         * @param {eslint-scope.Reference} reference - A reference to report.
          * @returns {void}
          */
         function report(reference) {
diff --git a/lib/rules/radix.js b/lib/rules/radix.js
index 0dfa081b6..0484c3bfb 100644
--- a/lib/rules/radix.js
+++ b/lib/rules/radix.js
@@ -21,7 +21,7 @@ const MODE_ALWAYS = "always",
 /**
  * Checks whether a given variable is shadowed or not.
  *
- * @param {escope.Variable} variable - A variable to check.
+ * @param {eslint-scope.Variable} variable - A variable to check.
  * @returns {boolean} `true` if the variable is shadowed.
  */
 function isShadowed(variable) {
diff --git a/lib/rules/space-before-function-paren.js b/lib/rules/space-before-function-paren.js
index 53aac71ed..8851c4587 100644
--- a/lib/rules/space-before-function-paren.js
+++ b/lib/rules/space-before-function-paren.js
@@ -87,9 +87,7 @@ module.exports = {
 
                 // Always ignore non-async functions and arrow functions without parens, e.g. async foo => bar
                 if (node.async && astUtils.isOpeningParenToken(sourceCode.getFirstToken(node, { skip: 1 }))) {
-
-                    // For backwards compatibility, the base config does not apply to async arrow functions.
-                    return overrideConfig.asyncArrow || "ignore";
+                    return overrideConfig.asyncArrow || baseConfig;
                 }
             } else if (isNamedFunction(node)) {
                 return overrideConfig.named || baseConfig;
diff --git a/lib/rules/space-unary-ops.js b/lib/rules/space-unary-ops.js
index 5b156fac2..d9998370b 100644
--- a/lib/rules/space-unary-ops.js
+++ b/lib/rules/space-unary-ops.js
@@ -288,9 +288,11 @@ module.exports = {
         * @returns {void}
         */
         function checkForSpaces(node) {
-            const tokens = sourceCode.getFirstTokens(node, 2),
-                firstToken = tokens[0],
-                secondToken = tokens[1];
+            const tokens = node.type === "UpdateExpression" && !node.prefix
+                ? sourceCode.getLastTokens(node, 2)
+                : sourceCode.getFirstTokens(node, 2);
+            const firstToken = tokens[0];
+            const secondToken = tokens[1];
 
             if ((node.type === "NewExpression" || node.prefix) && firstToken.type === "Keyword") {
                 checkUnaryWordOperatorForSpaces(node, firstToken, secondToken);
diff --git a/lib/rules/valid-jsdoc.js b/lib/rules/valid-jsdoc.js
index 66ad1f8d4..331ed7b69 100644
--- a/lib/rules/valid-jsdoc.js
+++ b/lib/rules/valid-jsdoc.js
@@ -180,18 +180,18 @@ module.exports = {
             let elements = [];
 
             switch (type.type) {
-                case "TypeApplication":  // {Array.<String>}
+                case "TypeApplication": // {Array.<String>}
                     elements = type.applications[0].type === "UnionType" ? type.applications[0].elements : type.applications;
                     typesToCheck.push(getCurrentExpectedTypes(type));
                     break;
-                case "RecordType":  // {{20:String}}
+                case "RecordType": // {{20:String}}
                     elements = type.fields;
                     break;
-                case "UnionType":  // {String|number|Test}
-                case "ArrayType":  // {[String, number, Test]}
+                case "UnionType": // {String|number|Test}
+                case "ArrayType": // {[String, number, Test]}
                     elements = type.elements;
                     break;
-                case "FieldType":  // Array.<{count: number, votes: number}>
+                case "FieldType": // Array.<{count: number, votes: number}>
                     if (type.value) {
                         typesToCheck.push(getCurrentExpectedTypes(type.value));
                     }
diff --git a/lib/timing.js b/lib/timing.js
index 204566286..15c93800f 100644
--- a/lib/timing.js
+++ b/lib/timing.js
@@ -98,7 +98,7 @@ function display(data) {
         return ALIGN[index](":", w + 1, "-");
     }).join("|"));
 
-    console.log(table.join("\n"));      // eslint-disable-line no-console
+    console.log(table.join("\n")); // eslint-disable-line no-console
 }
 
 /* istanbul ignore next */
diff --git a/lib/token-store/index.js b/lib/token-store/index.js
index 86d05cf7b..da398f4f4 100644
--- a/lib/token-store/index.js
+++ b/lib/token-store/index.js
@@ -17,29 +17,9 @@ const PaddedTokenCursor = require("./padded-token-cursor");
 // Helpers
 //------------------------------------------------------------------------------
 
-const PUBLIC_METHODS = Object.freeze([
-    "getTokenByRangeStart",
-
-    "getFirstToken",
-    "getLastToken",
-    "getTokenBefore",
-    "getTokenAfter",
-    "getFirstTokenBetween",
-    "getLastTokenBetween",
-
-    "getFirstTokens",
-    "getLastTokens",
-    "getTokensBefore",
-    "getTokensAfter",
-    "getFirstTokensBetween",
-    "getLastTokensBetween",
-
-    "getTokens",
-    "getTokensBetween",
-
-    "getTokenOrCommentBefore",
-    "getTokenOrCommentAfter"
-]);
+const TOKENS = Symbol("tokens");
+const COMMENTS = Symbol("comments");
+const INDEX_MAP = Symbol("indexMap");
 
 /**
  * Creates the map from locations to indices in `tokens`.
@@ -224,9 +204,9 @@ module.exports = class TokenStore {
      * @param {Comment[]} comments - The array of comments.
      */
     constructor(tokens, comments) {
-        this.tokens = tokens;
-        this.comments = comments.slice(0);
-        this.indexMap = createIndexMap(tokens, comments);
+        this[TOKENS] = tokens;
+        this[COMMENTS] = comments.slice(0);
+        this[INDEX_MAP] = createIndexMap(tokens, comments);
     }
 
     //--------------------------------------------------------------------------
@@ -243,9 +223,9 @@ module.exports = class TokenStore {
     getTokenByRangeStart(offset, options) {
         const includeComments = options && options.includeComments;
         const token = cursors.forward.createBaseCursor(
-            this.tokens,
-            this.comments,
-            this.indexMap,
+            this[TOKENS],
+            this[COMMENTS],
+            this[INDEX_MAP],
             offset,
             -1,
             includeComments
@@ -269,9 +249,9 @@ module.exports = class TokenStore {
     getFirstToken(node, options) {
         return createCursorWithSkip(
             cursors.forward,
-            this.tokens,
-            this.comments,
-            this.indexMap,
+            this[TOKENS],
+            this[COMMENTS],
+            this[INDEX_MAP],
             node.range[0],
             node.range[1],
             options
@@ -281,18 +261,15 @@ module.exports = class TokenStore {
     /**
      * Gets the last token of the given node.
      * @param {ASTNode} node - The AST node.
-     * @param {number|Function|Object} [options=0] - The option object. If this is a number then it's `options.skip`. If this is a function then it's `options.filter`.
-     * @param {boolean} [options.includeComments=false] - The flag to iterate comments as well.
-     * @param {Function|null} [options.filter=null] - The predicate function to choose tokens.
-     * @param {number} [options.skip=0] - The count of tokens the cursor skips.
+     * @param {number|Function|Object} [options=0] - The option object. Same options as getFirstToken()
      * @returns {Token|null} An object representing the token.
      */
     getLastToken(node, options) {
         return createCursorWithSkip(
             cursors.backward,
-            this.tokens,
-            this.comments,
-            this.indexMap,
+            this[TOKENS],
+            this[COMMENTS],
+            this[INDEX_MAP],
             node.range[0],
             node.range[1],
             options
@@ -302,18 +279,15 @@ module.exports = class TokenStore {
     /**
      * Gets the token that precedes a given node or token.
      * @param {ASTNode|Token|Comment} node - The AST node or token.
-     * @param {number|Function|Object} [options=0] - The option object. If this is a number then it's `options.skip`. If this is a function then it's `options.filter`.
-     * @param {boolean} [options.includeComments=false] - The flag to iterate comments as well.
-     * @param {Function|null} [options.filter=null] - The predicate function to choose tokens.
-     * @param {number} [options.skip=0] - The count of tokens the cursor skips.
+     * @param {number|Function|Object} [options=0] - The option object. Same options as getFirstToken()
      * @returns {Token|null} An object representing the token.
      */
     getTokenBefore(node, options) {
         return createCursorWithSkip(
             cursors.backward,
-            this.tokens,
-            this.comments,
-            this.indexMap,
+            this[TOKENS],
+            this[COMMENTS],
+            this[INDEX_MAP],
             -1,
             node.range[0],
             options
@@ -323,18 +297,15 @@ module.exports = class TokenStore {
     /**
      * Gets the token that follows a given node or token.
      * @param {ASTNode|Token|Comment} node - The AST node or token.
-     * @param {number|Function|Object} [options=0] - The option object. If this is a number then it's `options.skip`. If this is a function then it's `options.filter`.
-     * @param {boolean} [options.includeComments=false] - The flag to iterate comments as well.
-     * @param {Function|null} [options.filter=null] - The predicate function to choose tokens.
-     * @param {number} [options.skip=0] - The count of tokens the cursor skips.
+     * @param {number|Function|Object} [options=0] - The option object. Same options as getFirstToken()
      * @returns {Token|null} An object representing the token.
      */
     getTokenAfter(node, options) {
         return createCursorWithSkip(
             cursors.forward,
-            this.tokens,
-            this.comments,
-            this.indexMap,
+            this[TOKENS],
+            this[COMMENTS],
+            this[INDEX_MAP],
             node.range[1],
             -1,
             options
@@ -345,18 +316,15 @@ module.exports = class TokenStore {
      * Gets the first token between two non-overlapping nodes.
      * @param {ASTNode|Token|Comment} left - Node before the desired token range.
      * @param {ASTNode|Token|Comment} right - Node after the desired token range.
-     * @param {number|Function|Object} [options=0] - The option object. If this is a number then it's `options.skip`. If this is a function then it's `options.filter`.
-     * @param {boolean} [options.includeComments=false] - The flag to iterate comments as well.
-     * @param {Function|null} [options.filter=null] - The predicate function to choose tokens.
-     * @param {number} [options.skip=0] - The count of tokens the cursor skips.
+     * @param {number|Function|Object} [options=0] - The option object. Same options as getFirstToken()
      * @returns {Token|null} An object representing the token.
      */
     getFirstTokenBetween(left, right, options) {
         return createCursorWithSkip(
             cursors.forward,
-            this.tokens,
-            this.comments,
-            this.indexMap,
+            this[TOKENS],
+            this[COMMENTS],
+            this[INDEX_MAP],
             left.range[1],
             right.range[0],
             options
@@ -367,18 +335,15 @@ module.exports = class TokenStore {
      * Gets the last token between two non-overlapping nodes.
      * @param {ASTNode|Token|Comment} left Node before the desired token range.
      * @param {ASTNode|Token|Comment} right Node after the desired token range.
-     * @param {number|Function|Object} [options=0] The option object. If this is a number then it's `options.skip`. If this is a function then it's `options.filter`.
-     * @param {boolean} [options.includeComments=false] - The flag to iterate comments as well.
-     * @param {Function|null} [options.filter=null] - The predicate function to choose tokens.
-     * @param {number} [options.skip=0] - The count of tokens the cursor skips.
-     * @returns {Token|null} Tokens between left and right.
+     * @param {number|Function|Object} [options=0] - The option object. Same options as getFirstToken()
+     * @returns {Token|null} An object representing the token.
      */
     getLastTokenBetween(left, right, options) {
         return createCursorWithSkip(
             cursors.backward,
-            this.tokens,
-            this.comments,
-            this.indexMap,
+            this[TOKENS],
+            this[COMMENTS],
+            this[INDEX_MAP],
             left.range[1],
             right.range[0],
             options
@@ -427,9 +392,9 @@ module.exports = class TokenStore {
     getFirstTokens(node, options) {
         return createCursorWithCount(
             cursors.forward,
-            this.tokens,
-            this.comments,
-            this.indexMap,
+            this[TOKENS],
+            this[COMMENTS],
+            this[INDEX_MAP],
             node.range[0],
             node.range[1],
             options
@@ -439,18 +404,15 @@ module.exports = class TokenStore {
     /**
      * Gets the last `count` tokens of the given node.
      * @param {ASTNode} node - The AST node.
-     * @param {number|Function|Object} [options=0] - The option object. If this is a number then it's `options.count`. If this is a function then it's `options.filter`.
-     * @param {boolean} [options.includeComments=false] - The flag to iterate comments as well.
-     * @param {Function|null} [options.filter=null] - The predicate function to choose tokens.
-     * @param {number} [options.count=0] - The maximum count of tokens the cursor iterates.
+     * @param {number|Function|Object} [options=0] - The option object. Same options as getFirstTokens()
      * @returns {Token[]} Tokens.
      */
     getLastTokens(node, options) {
         return createCursorWithCount(
             cursors.backward,
-            this.tokens,
-            this.comments,
-            this.indexMap,
+            this[TOKENS],
+            this[COMMENTS],
+            this[INDEX_MAP],
             node.range[0],
             node.range[1],
             options
@@ -460,18 +422,15 @@ module.exports = class TokenStore {
     /**
      * Gets the `count` tokens that precedes a given node or token.
      * @param {ASTNode|Token|Comment} node - The AST node or token.
-     * @param {number|Function|Object} [options=0] - The option object. If this is a number then it's `options.count`. If this is a function then it's `options.filter`.
-     * @param {boolean} [options.includeComments=false] - The flag to iterate comments as well.
-     * @param {Function|null} [options.filter=null] - The predicate function to choose tokens.
-     * @param {number} [options.count=0] - The maximum count of tokens the cursor iterates.
+     * @param {number|Function|Object} [options=0] - The option object. Same options as getFirstTokens()
      * @returns {Token[]} Tokens.
      */
     getTokensBefore(node, options) {
         return createCursorWithCount(
             cursors.backward,
-            this.tokens,
-            this.comments,
-            this.indexMap,
+            this[TOKENS],
+            this[COMMENTS],
+            this[INDEX_MAP],
             -1,
             node.range[0],
             options
@@ -481,18 +440,15 @@ module.exports = class TokenStore {
     /**
      * Gets the `count` tokens that follows a given node or token.
      * @param {ASTNode|Token|Comment} node - The AST node or token.
-     * @param {number|Function|Object} [options=0] - The option object. If this is a number then it's `options.count`. If this is a function then it's `options.filter`.
-     * @param {boolean} [options.includeComments=false] - The flag to iterate comments as well.
-     * @param {Function|null} [options.filter=null] - The predicate function to choose tokens.
-     * @param {number} [options.count=0] - The maximum count of tokens the cursor iterates.
+     * @param {number|Function|Object} [options=0] - The option object. Same options as getFirstTokens()
      * @returns {Token[]} Tokens.
      */
     getTokensAfter(node, options) {
         return createCursorWithCount(
             cursors.forward,
-            this.tokens,
-            this.comments,
-            this.indexMap,
+            this[TOKENS],
+            this[COMMENTS],
+            this[INDEX_MAP],
             node.range[1],
             -1,
             options
@@ -503,18 +459,15 @@ module.exports = class TokenStore {
      * Gets the first `count` tokens between two non-overlapping nodes.
      * @param {ASTNode|Token|Comment} left - Node before the desired token range.
      * @param {ASTNode|Token|Comment} right - Node after the desired token range.
-     * @param {number|Function|Object} [options=0] - The option object. If this is a number then it's `options.count`. If this is a function then it's `options.filter`.
-     * @param {boolean} [options.includeComments=false] - The flag to iterate comments as well.
-     * @param {Function|null} [options.filter=null] - The predicate function to choose tokens.
-     * @param {number} [options.count=0] - The maximum count of tokens the cursor iterates.
+     * @param {number|Function|Object} [options=0] - The option object. Same options as getFirstTokens()
      * @returns {Token[]} Tokens between left and right.
      */
     getFirstTokensBetween(left, right, options) {
         return createCursorWithCount(
             cursors.forward,
-            this.tokens,
-            this.comments,
-            this.indexMap,
+            this[TOKENS],
+            this[COMMENTS],
+            this[INDEX_MAP],
             left.range[1],
             right.range[0],
             options
@@ -525,18 +478,15 @@ module.exports = class TokenStore {
      * Gets the last `count` tokens between two non-overlapping nodes.
      * @param {ASTNode|Token|Comment} left Node before the desired token range.
      * @param {ASTNode|Token|Comment} right Node after the desired token range.
-     * @param {number|Function|Object} [options=0] The option object. If this is a number then it's `options.count`. If this is a function then it's `options.filter`.
-     * @param {boolean} [options.includeComments=false] - The flag to iterate comments as well.
-     * @param {Function|null} [options.filter=null] - The predicate function to choose tokens.
-     * @param {number} [options.count=0] - The maximum count of tokens the cursor iterates.
+     * @param {number|Function|Object} [options=0] - The option object. Same options as getFirstTokens()
      * @returns {Token[]} Tokens between left and right.
      */
     getLastTokensBetween(left, right, options) {
         return createCursorWithCount(
             cursors.backward,
-            this.tokens,
-            this.comments,
-            this.indexMap,
+            this[TOKENS],
+            this[COMMENTS],
+            this[INDEX_MAP],
             left.range[1],
             right.range[0],
             options
@@ -561,9 +511,9 @@ module.exports = class TokenStore {
      */
     getTokens(node, beforeCount, afterCount) {
         return createCursorWithPadding(
-            this.tokens,
-            this.comments,
-            this.indexMap,
+            this[TOKENS],
+            this[COMMENTS],
+            this[INDEX_MAP],
             node.range[0],
             node.range[1],
             beforeCount,
@@ -590,9 +540,9 @@ module.exports = class TokenStore {
      */
     getTokensBetween(left, right, padding) {
         return createCursorWithPadding(
-            this.tokens,
-            this.comments,
-            this.indexMap,
+            this[TOKENS],
+            this[COMMENTS],
+            this[INDEX_MAP],
             left.range[1],
             right.range[0],
             padding,
@@ -600,5 +550,3 @@ module.exports = class TokenStore {
         ).getAllTokens();
     }
 };
-
-module.exports.PUBLIC_METHODS = PUBLIC_METHODS;
diff --git a/lib/util/module-resolver.js b/lib/util/module-resolver.js
index 505c57282..470a54f7c 100644
--- a/lib/util/module-resolver.js
+++ b/lib/util/module-resolver.js
@@ -68,7 +68,7 @@ class ModuleResolver {
          * lookup file paths when require() is called. So, we are hooking into the
          * exact same logic that Node.js uses.
          */
-        const result = Module._findPath(name, lookupPaths);   // eslint-disable-line no-underscore-dangle
+        const result = Module._findPath(name, lookupPaths); // eslint-disable-line no-underscore-dangle
 
         if (!result) {
             throw new Error(`Cannot find module '${name}'`);
diff --git a/lib/util/source-code.js b/lib/util/source-code.js
index 5106c1e61..caf772db6 100644
--- a/lib/util/source-code.js
+++ b/lib/util/source-code.js
@@ -106,86 +106,80 @@ function sortedMerge(tokens, comments) {
 // Public Interface
 //------------------------------------------------------------------------------
 
-/**
- * Represents parsed source code.
- * @param {string} text - The source code text.
- * @param {ASTNode} ast - The Program node of the AST representing the code. This AST should be created from the text that BOM was stripped.
- * @constructor
- */
-function SourceCode(text, ast) {
-    validate(ast);
+class SourceCode extends TokenStore {
 
     /**
-     * The flag to indicate that the source code has Unicode BOM.
-     * @type boolean
+     * Represents parsed source code.
+     * @param {string} text - The source code text.
+     * @param {ASTNode} ast - The Program node of the AST representing the code. This AST should be created from the text that BOM was stripped.
+     * @constructor
      */
-    this.hasBOM = (text.charCodeAt(0) === 0xFEFF);
+    constructor(text, ast) {
+        validate(ast);
 
-    /**
-     * The original text source code.
-     * BOM was stripped from this text.
-     * @type string
-     */
-    this.text = (this.hasBOM ? text.slice(1) : text);
+        super(ast.tokens, ast.comments);
 
-    /**
-     * The parsed AST for the source code.
-     * @type ASTNode
-     */
-    this.ast = ast;
+        /**
+         * The flag to indicate that the source code has Unicode BOM.
+         * @type boolean
+         */
+        this.hasBOM = (text.charCodeAt(0) === 0xFEFF);
 
-    /**
-     * The source code split into lines according to ECMA-262 specification.
-     * This is done to avoid each rule needing to do so separately.
-     * @type string[]
-     */
-    this.lines = [];
-    this.lineStartIndices = [0];
-
-    const lineEndingPattern = astUtils.createGlobalLinebreakMatcher();
-    let match;
-
-    /*
-     * Previously, this was implemented using a regex that
-     * matched a sequence of non-linebreak characters followed by a
-     * linebreak, then adding the lengths of the matches. However,
-     * this caused a catastrophic backtracking issue when the end
-     * of a file contained a large number of non-newline characters.
-     * To avoid this, the current implementation just matches newlines
-     * and uses match.index to get the correct line start indices.
-     */
-    while ((match = lineEndingPattern.exec(this.text))) {
-        this.lines.push(this.text.slice(this.lineStartIndices[this.lineStartIndices.length - 1], match.index));
-        this.lineStartIndices.push(match.index + match[0].length);
-    }
-    this.lines.push(this.text.slice(this.lineStartIndices[this.lineStartIndices.length - 1]));
+        /**
+         * The original text source code.
+         * BOM was stripped from this text.
+         * @type string
+         */
+        this.text = (this.hasBOM ? text.slice(1) : text);
 
-    this.tokensAndComments = sortedMerge(ast.tokens, ast.comments);
+        /**
+         * The parsed AST for the source code.
+         * @type ASTNode
+         */
+        this.ast = ast;
 
-    // create token store methods
-    const tokenStore = new TokenStore(ast.tokens, ast.comments);
+        /**
+         * The source code split into lines according to ECMA-262 specification.
+         * This is done to avoid each rule needing to do so separately.
+         * @type string[]
+         */
+        this.lines = [];
+        this.lineStartIndices = [0];
 
-    for (const methodName of TokenStore.PUBLIC_METHODS) {
-        this[methodName] = tokenStore[methodName].bind(tokenStore);
-    }
+        const lineEndingPattern = astUtils.createGlobalLinebreakMatcher();
+        let match;
 
-    // don't allow modification of this object
-    Object.freeze(this);
-    Object.freeze(this.lines);
-}
+        /*
+         * Previously, this was implemented using a regex that
+         * matched a sequence of non-linebreak characters followed by a
+         * linebreak, then adding the lengths of the matches. However,
+         * this caused a catastrophic backtracking issue when the end
+         * of a file contained a large number of non-newline characters.
+         * To avoid this, the current implementation just matches newlines
+         * and uses match.index to get the correct line start indices.
+         */
+        while ((match = lineEndingPattern.exec(this.text))) {
+            this.lines.push(this.text.slice(this.lineStartIndices[this.lineStartIndices.length - 1], match.index));
+            this.lineStartIndices.push(match.index + match[0].length);
+        }
+        this.lines.push(this.text.slice(this.lineStartIndices[this.lineStartIndices.length - 1]));
 
-/**
- * Split the source code into multiple lines based on the line delimiters
- * @param {string} text Source code as a string
- * @returns {string[]} Array of source code lines
- * @public
- */
-SourceCode.splitLines = function(text) {
-    return text.split(astUtils.createGlobalLinebreakMatcher());
-};
+        this.tokensAndComments = sortedMerge(ast.tokens, ast.comments);
+
+        // don't allow modification of this object
+        Object.freeze(this);
+        Object.freeze(this.lines);
+    }
 
-SourceCode.prototype = {
-    constructor: SourceCode,
+    /**
+     * Split the source code into multiple lines based on the line delimiters
+     * @param {string} text Source code as a string
+     * @returns {string[]} Array of source code lines
+     * @public
+     */
+    static splitLines(text) {
+        return text.split(astUtils.createGlobalLinebreakMatcher());
+    }
 
     /**
      * Gets the source code for the given node.
@@ -200,9 +194,7 @@ SourceCode.prototype = {
                 node.range[1] + (afterCount || 0));
         }
         return this.text;
-
-
-    },
+    }
 
     /**
      * Gets the entire source text split into an array of lines.
@@ -210,7 +202,7 @@ SourceCode.prototype = {
      */
     getLines() {
         return this.lines;
-    },
+    }
 
     /**
      * Retrieves an array containing all comments in the source code.
@@ -218,7 +210,7 @@ SourceCode.prototype = {
      */
     getAllComments() {
         return this.ast.comments;
-    },
+    }
 
     /**
      * Gets all comments for the given node.
@@ -226,7 +218,7 @@ SourceCode.prototype = {
      * @returns {Object} The list of comments indexed by their position.
      * @public
      */
-    getComments(node) {
+    getComments(node) { // eslint-disable-line class-methods-use-this
 
         let leadingComments = node.leadingComments || [];
         const trailingComments = node.trailingComments || [];
@@ -246,7 +238,7 @@ SourceCode.prototype = {
             leading: leadingComments,
             trailing: trailingComments
         };
-    },
+    }
 
     /**
      * Retrieves the JSDoc comment for a given node.
@@ -255,7 +247,7 @@ SourceCode.prototype = {
      *      given node or null if not found.
      * @public
      */
-    getJSDocComment(node) {
+    getJSDocComment(node) { // eslint-disable-line class-methods-use-this
 
         let parent = node.parent;
 
@@ -288,7 +280,7 @@ SourceCode.prototype = {
             default:
                 return null;
         }
-    },
+    }
 
     /**
      * Gets the deepest node containing a range index.
@@ -317,7 +309,7 @@ SourceCode.prototype = {
         });
 
         return result ? Object.assign({ parent: resultParent }, result) : null;
-    },
+    }
 
     /**
      * Determines if two tokens have at least one whitespace character
@@ -332,7 +324,7 @@ SourceCode.prototype = {
         const text = this.text.slice(first.range[1], second.range[0]);
 
         return /\s/.test(text.replace(/\/\*.*?\*\//g, ""));
-    },
+    }
 
     /**
     * Converts a source text index into a (line, column) pair.
@@ -366,8 +358,7 @@ SourceCode.prototype = {
         const lineNumber = lodash.sortedLastIndex(this.lineStartIndices, index);
 
         return { line: lineNumber, column: index - this.lineStartIndices[lineNumber - 1] };
-
-    },
+    }
 
     /**
     * Converts a (line, column) pair into a range index.
@@ -410,7 +401,6 @@ SourceCode.prototype = {
 
         return positionIndex;
     }
-};
-
+}
 
 module.exports = SourceCode;
diff --git a/package.json b/package.json
index 3cb48505e..eafb1cdd3 100644
--- a/package.json
+++ b/package.json
@@ -39,7 +39,7 @@
     "concat-stream": "^1.5.2",
     "debug": "^2.1.1",
     "doctrine": "^2.0.0",
-    "escope": "^3.6.0",
+    "eslint-scope": "^3.6.0",
     "espree": "^3.4.0",
     "esquery": "^1.0.0",
     "estraverse": "^4.2.0",


Test patch:
diff --git a/lib/testers/rule-tester.js b/lib/testers/rule-tester.js
index 61fbdf597..e74b863c1 100644
--- a/lib/testers/rule-tester.js
+++ b/lib/testers/rule-tester.js
@@ -129,114 +129,106 @@ function freezeDeeply(x) {
 // Public Interface
 //------------------------------------------------------------------------------
 
+// default separators for testing
+const DESCRIBE = Symbol("describe");
+const IT = Symbol("it");
+
 /**
- * Creates a new instance of RuleTester.
- * @param {Object} [testerConfig] Optional, extra configuration for the tester
- * @constructor
+ * This is `it` or `describe` if those don't exist.
+ * @this {Mocha}
+ * @param {string} text - The description of the test case.
+ * @param {Function} method - The logic of the test case.
+ * @returns {any} Returned value of `method`.
  */
-function RuleTester(testerConfig) {
+function defaultHandler(text, method) {
+    return method.apply(this);
+}
+
+class RuleTester {
 
     /**
-     * The configuration to use for this tester. Combination of the tester
-     * configuration and the default configuration.
-     * @type {Object}
+     * Creates a new instance of RuleTester.
+     * @param {Object} [testerConfig] Optional, extra configuration for the tester
+     * @constructor
      */
-    this.testerConfig = lodash.merge(
+    constructor(testerConfig) {
+
+        /**
+         * The configuration to use for this tester. Combination of the tester
+         * configuration and the default configuration.
+         * @type {Object}
+         */
+        this.testerConfig = lodash.merge(
+
+            // we have to clone because merge uses the first argument for recipient
+            lodash.cloneDeep(defaultConfig),
+            testerConfig
+        );
 
-        // we have to clone because merge uses the first argument for recipient
-        lodash.cloneDeep(defaultConfig),
-        testerConfig
-    );
+        /**
+         * Rule definitions to define before tests.
+         * @type {Object}
+         */
+        this.rules = {};
+    }
 
     /**
-     * Rule definitions to define before tests.
-     * @type {Object}
+     * Set the configuration to use for all future tests
+     * @param {Object} config the configuration to use.
+     * @returns {void}
      */
-    this.rules = {};
-}
+    static setDefaultConfig(config) {
+        if (typeof config !== "object") {
+            throw new Error("RuleTester.setDefaultConfig: config must be an object");
+        }
+        defaultConfig = config;
 
-/**
- * Set the configuration to use for all future tests
- * @param {Object} config the configuration to use.
- * @returns {void}
- */
-RuleTester.setDefaultConfig = function(config) {
-    if (typeof config !== "object") {
-        throw new Error("RuleTester.setDefaultConfig: config must be an object");
+        // Make sure the rules object exists since it is assumed to exist later
+        defaultConfig.rules = defaultConfig.rules || {};
     }
-    defaultConfig = config;
-
-    // Make sure the rules object exists since it is assumed to exist later
-    defaultConfig.rules = defaultConfig.rules || {};
-};
 
-/**
- * Get the current configuration used for all tests
- * @returns {Object} the current configuration
- */
-RuleTester.getDefaultConfig = function() {
-    return defaultConfig;
-};
+    /**
+     * Get the current configuration used for all tests
+     * @returns {Object} the current configuration
+     */
+    static getDefaultConfig() {
+        return defaultConfig;
+    }
 
-/**
- * Reset the configuration to the initial configuration of the tester removing
- * any changes made until now.
- * @returns {void}
- */
-RuleTester.resetDefaultConfig = function() {
-    defaultConfig = lodash.cloneDeep(testerDefaultConfig);
-};
+    /**
+     * Reset the configuration to the initial configuration of the tester removing
+     * any changes made until now.
+     * @returns {void}
+     */
+    static resetDefaultConfig() {
+        defaultConfig = lodash.cloneDeep(testerDefaultConfig);
+    }
 
-// default separators for testing
-const DESCRIBE = Symbol("describe");
-const IT = Symbol("it");
 
-RuleTester[DESCRIBE] = RuleTester[IT] = null;
+    // If people use `mocha test.js --watch` command, `describe` and `it` function
+    // instances are different for each execution. So `describe` and `it` should get fresh instance
+    // always.
+    static get describe() {
+        return (
+            this[DESCRIBE] ||
+            (typeof describe === "function" ? describe : defaultHandler)
+        );
+    }
 
-/**
- * This is `it` or `describe` if those don't exist.
- * @this {Mocha}
- * @param {string} text - The description of the test case.
- * @param {Function} method - The logic of the test case.
- * @returns {any} Returned value of `method`.
- */
-function defaultHandler(text, method) {
-    return method.apply(this);
-}
+    static set describe(value) {
+        this[DESCRIBE] = value;
+    }
 
-// If people use `mocha test.js --watch` command, `describe` and `it` function
-// instances are different for each execution. So this should get fresh instance
-// always.
-Object.defineProperties(RuleTester, {
-    describe: {
-        get() {
-            return (
-                RuleTester[DESCRIBE] ||
-                (typeof describe === "function" ? describe : defaultHandler)
-            );
-        },
-        set(value) {
-            RuleTester[DESCRIBE] = value;
-        },
-        configurable: true,
-        enumerable: true
-    },
-    it: {
-        get() {
-            return (
-                RuleTester[IT] ||
-                (typeof it === "function" ? it : defaultHandler)
-            );
-        },
-        set(value) {
-            RuleTester[IT] = value;
-        },
-        configurable: true,
-        enumerable: true
+    static get it() {
+        return (
+            this[IT] ||
+            (typeof it === "function" ? it : defaultHandler)
+        );
     }
-});
 
-RuleTester.prototype = {
+    static set it(value) {
+        this[IT] = value;
+    }
 
     /**
      * Define a rule for one particular run of tests.
@@ -246,7 +238,7 @@ RuleTester.prototype = {
      */
     defineRule(name, rule) {
         this.rules[name] = rule;
-    },
+    }
 
     /**
      * Adds a new rule test to execute.
@@ -562,7 +554,8 @@ RuleTester.prototype = {
 
         return result.suite;
     }
-};
+}
 
+RuleTester[DESCRIBE] = RuleTester[IT] = null;
 
 module.exports = RuleTester;
diff --git a/tests/fixtures/ignored-paths/.eslintignoreForDifferentCwd b/tests/fixtures/ignored-paths/.eslintignoreForDifferentCwd
new file mode 100644
index 000000000..ac105c765
--- /dev/null
+++ b/tests/fixtures/ignored-paths/.eslintignoreForDifferentCwd
@@ -0,0 +1,1 @@
+/undef.js
diff --git a/tests/lib/ast-utils.js b/tests/lib/ast-utils.js
index 448f9cde7..626497e77 100644
--- a/tests/lib/ast-utils.js
+++ b/tests/lib/ast-utils.js
@@ -214,7 +214,7 @@ describe("ast-utils", () => {
 
             eslint.reset();
             eslint.on("VariableDeclaration", checker);
-            eslint.verify("const a = 1; a = 2;", { ecmaFeatures: { blockBindings: true } }, filename, true);
+            eslint.verify("const a = 1; a = 2;", {}, filename, true);
         });
 
         it("should return false if reference is not assigned for const", () => {
@@ -232,7 +232,7 @@ describe("ast-utils", () => {
 
             eslint.reset();
             eslint.on("VariableDeclaration", checker);
-            eslint.verify("const a = 1; c = 2;", { ecmaFeatures: { blockBindings: true } }, filename, true);
+            eslint.verify("const a = 1; c = 2;", {}, filename, true);
         });
 
         // class
@@ -252,7 +252,7 @@ describe("ast-utils", () => {
 
             eslint.reset();
             eslint.on("ClassDeclaration", checker);
-            eslint.verify("class A { }\n A = 1;", { ecmaFeatures: { classes: true } }, filename, true);
+            eslint.verify("class A { }\n A = 1;", {}, filename, true);
         });
 
         it("should return false if reference is not assigned for class", () => {
@@ -270,7 +270,7 @@ describe("ast-utils", () => {
 
             eslint.reset();
             eslint.on("ClassDeclaration", checker);
-            eslint.verify("class A { } foo(A);", { ecmaFeatures: { classes: true } }, filename, true);
+            eslint.verify("class A { } foo(A);", {}, filename, true);
         });
     });
 
diff --git a/tests/lib/config.js b/tests/lib/config.js
index fbf65d67f..3bc5dc3b3 100644
--- a/tests/lib/config.js
+++ b/tests/lib/config.js
@@ -358,9 +358,6 @@ describe("Config", () => {
                         browser: true,
                         node: false
                     },
-                    ecmaFeatures: {
-                        globalReturn: false
-                    },
                     globals: environments.browser.globals
                 },
                 actual = configHelper.getConfig(file);
diff --git a/tests/lib/config/config-file.js b/tests/lib/config/config-file.js
index c6ecc09ad..a8a3bfd79 100644
--- a/tests/lib/config/config-file.js
+++ b/tests/lib/config/config-file.js
@@ -53,7 +53,7 @@ function getFixturePath(filepath) {
  * @private
  */
 function readJSModule(code) {
-    return eval(`var module = {};\n${code}`);  // eslint-disable-line no-eval
+    return eval(`var module = {};\n${code}`); // eslint-disable-line no-eval
 }
 
 /**
@@ -710,7 +710,7 @@ describe("ConfigFile", () => {
                 rules: {
                     a: 2, // from node_modules/eslint-config-a
                     b: 2, // from node_modules/eslint-config-a/node_modules/eslint-config-b
-                    c: 2  // from node_modules/eslint-config-a/node_modules/eslint-config-b/node_modules/eslint-config-c
+                    c: 2 // from node_modules/eslint-config-a/node_modules/eslint-config-b/node_modules/eslint-config-c
                 }
             });
         });
@@ -724,7 +724,7 @@ describe("ConfigFile", () => {
                 globals: {},
                 parserOptions: {},
                 rules: {
-                    a: 2,       // from node_modules/eslint-config-a/index.js
+                    a: 2, // from node_modules/eslint-config-a/index.js
                     relative: 2 // from node_modules/eslint-config-a/relative.js
                 }
             });
@@ -739,7 +739,7 @@ describe("ConfigFile", () => {
                 globals: {},
                 parserOptions: {},
                 rules: {
-                    a: 2,       // from node_modules/eslint-config-a/index.js
+                    a: 2, // from node_modules/eslint-config-a/index.js
                     relative: 2 // from node_modules/eslint-config-a/relative.js
                 }
             });
@@ -782,7 +782,7 @@ describe("ConfigFile", () => {
                     globals: {},
                     parserOptions: {},
                     rules: {
-                        a: 2,       // from node_modules/eslint-config-a/index.js
+                        a: 2, // from node_modules/eslint-config-a/index.js
                         relative: 2 // from node_modules/eslint-config-a/relative.js
                     }
                 });
diff --git a/tests/lib/config/config-initializer.js b/tests/lib/config/config-initializer.js
index 611c08fc0..d313dca5f 100644
--- a/tests/lib/config/config-initializer.js
+++ b/tests/lib/config/config-initializer.js
@@ -255,7 +255,7 @@ describe("configInitializer", () => {
                     process.chdir(originalDir);
                     throw err;
                 } finally {
-                    sandbox.restore();  // restore console.log()
+                    sandbox.restore(); // restore console.log()
                 }
             });
 
diff --git a/tests/lib/config/plugins.js b/tests/lib/config/plugins.js
index 61b759797..3d7b98032 100644
--- a/tests/lib/config/plugins.js
+++ b/tests/lib/config/plugins.js
@@ -154,33 +154,33 @@ describe("Plugins", () => {
             assert.equal(Rules.get("@scope/example/foo"), scopedPlugin.rules.foo);
         });
 
-        describe("(NOTE: those behavior will be removed by 4.0.0)", () => {
-            it("should load a scoped plugin when referenced by short name, and should get the plugin even if '@scope/' is omitted", () => {
+        describe("when referencing a scope plugin and omitting @scope/", () => {
+            it("should load a scoped plugin when referenced by short name, but should not get the plugin if '@scope/' is omitted", () => {
                 StubbedPlugins.load("@scope/example");
-                assert.equal(StubbedPlugins.get("example"), scopedPlugin);
+                assert.equal(StubbedPlugins.get("example"), null);
             });
 
-            it("should load a scoped plugin when referenced by long name, and should get the plugin even if '@scope/' is omitted", () => {
+            it("should load a scoped plugin when referenced by long name, but should not get the plugin if '@scope/' is omitted", () => {
                 StubbedPlugins.load("@scope/eslint-plugin-example");
-                assert.equal(StubbedPlugins.get("example"), scopedPlugin);
+                assert.equal(StubbedPlugins.get("example"), null);
             });
 
-            it("should register environments when scoped plugin has environments, and should get the environment even if '@scope/' is omitted", () => {
+            it("should register environments when scoped plugin has environments, but should not get the environment if '@scope/' is omitted", () => {
                 scopedPlugin.environments = {
                     foo: {}
                 };
                 StubbedPlugins.load("@scope/eslint-plugin-example");
 
-                assert.equal(Environments.get("example/foo"), scopedPlugin.environments.foo);
+                assert.equal(Environments.get("example/foo"), null);
             });
 
-            it("should register rules when scoped plugin has rules, and should get the rule even if '@scope/' is omitted", () => {
+            it("should register rules when scoped plugin has rules, but should not get the rule if '@scope/' is omitted", () => {
                 scopedPlugin.rules = {
                     foo: {}
                 };
                 StubbedPlugins.load("@scope/eslint-plugin-example");
 
-                assert.equal(Rules.get("example/foo"), scopedPlugin.rules.foo);
+                assert.equal(Rules.get("example/foo"), null);
             });
         });
     });
diff --git a/tests/lib/eslint.js b/tests/lib/eslint.js
index 9a1456cb1..3fdf40347 100644
--- a/tests/lib/eslint.js
+++ b/tests/lib/eslint.js
@@ -48,7 +48,7 @@ const TEST_CODE = "var answer = 6 * 7;",
 //------------------------------------------------------------------------------
 
 /**
- * Get variables in the current escope
+ * Get variables in the current scope
  * @param {Object} scope current scope
  * @param {string} name name of the variable to look for
  * @returns {ASTNode} The variable object
@@ -841,6 +841,8 @@ describe("eslint", () => {
                 nodeType: "Program",
                 line: 1,
                 column: 1,
+                endLine: 1,
+                endColumn: 2,
                 source: "0"
             });
         });
@@ -859,6 +861,8 @@ describe("eslint", () => {
                 nodeType: "Program",
                 line: 1,
                 column: 1,
+                endLine: 1,
+                endColumn: 2,
                 source: "0"
             });
         });
@@ -877,6 +881,8 @@ describe("eslint", () => {
                 nodeType: "Program",
                 line: 1,
                 column: 1,
+                endLine: 1,
+                endColumn: 2,
                 source: "0",
                 fix: { range: [1, 1], text: "" }
             });
@@ -1066,7 +1072,7 @@ describe("eslint", () => {
             });
         });
 
-        it("should not have 'endLine' and 'endColumn' when there is not 'loc' property.", () => {
+        it("should have 'endLine' and 'endColumn' when there is not 'loc' property.", () => {
             eslint.on("Program", node => {
                 eslint.report(
                     "test-rule",
@@ -1076,10 +1082,12 @@ describe("eslint", () => {
                 );
             });
 
-            const messages = eslint.verify("0", config, "", true);
+            const sourceText = "foo + bar;";
 
-            assert.strictEqual(messages[0].endLine, void 0);
-            assert.strictEqual(messages[0].endColumn, void 0);
+            const messages = eslint.verify(sourceText, config, "", true);
+
+            assert.strictEqual(messages[0].endLine, 1);
+            assert.strictEqual(messages[0].endColumn, sourceText.length + 1); // (1-based column)
         });
 
         it("should have 'endLine' and 'endColumn' when 'loc' property has 'end' property.", () => {
diff --git a/tests/lib/ignored-paths.js b/tests/lib/ignored-paths.js
index 643a11ada..07d96672d 100644
--- a/tests/lib/ignored-paths.js
+++ b/tests/lib/ignored-paths.js
@@ -85,7 +85,7 @@ function countDefaultPatterns(ignoredPaths) {
     let count = ignoredPaths.defaultPatterns.length;
 
     if (!ignoredPaths.options || (ignoredPaths.options.dotfiles !== true)) {
-        count = count + 2;  // Two patterns for ignoring dotfiles
+        count = count + 2; // Two patterns for ignoring dotfiles
     }
     return count;
 }
@@ -342,6 +342,13 @@ describe("IgnoredPaths", () => {
             assert.isFalse(ignoredPaths.contains(getFixturePath("sampleignorepattern")));
 
         });
+
+        it("should resolve relative paths from the ignorePath, not cwd", () => {
+            const ignoredPaths = new IgnoredPaths({ ignore: true, ignorePath: getFixturePath(".eslintignoreForDifferentCwd"), cwd: getFixturePath("subdir") });
+
+            assert.isFalse(ignoredPaths.contains(getFixturePath("subdir/undef.js")));
+            assert.isTrue(ignoredPaths.contains(getFixturePath("undef.js")));
+        });
     });
 
     describe("initialization with ignorePath containing commented lines", () => {
@@ -631,6 +638,17 @@ describe("IgnoredPaths", () => {
             assert.isFalse(shouldIgnore(resolve(".hidden")));
             assert.isFalse(shouldIgnore(resolve(".hidden/a")));
         });
+
+        it("should use the ignorePath's directory as the base to resolve relative paths, not cwd", () => {
+            const cwd = getFixturePath("subdir");
+            const ignoredPaths = new IgnoredPaths({ ignore: true, cwd, ignorePath: getFixturePath(".eslintignoreForDifferentCwd") });
+
+            const shouldIgnore = ignoredPaths.getIgnoredFoldersGlobChecker();
+            const resolve = createResolve(cwd);
+
+            assert.isFalse(shouldIgnore(resolve("undef.js")));
+            assert.isTrue(shouldIgnore(resolve("../undef.js")));
+        });
     });
 
 });
diff --git a/tests/lib/rules/line-comment-position.js b/tests/lib/rules/line-comment-position.js
index e72e85cf0..fcc70c9fa 100644
--- a/tests/lib/rules/line-comment-position.js
+++ b/tests/lib/rules/line-comment-position.js
@@ -108,7 +108,7 @@ ruleTester.run("line-comment-position", rule, {
                 column: 1
             }]
         },
-        {   // deprecated option still works
+        { // deprecated option still works
             code: "// jscs: disable\n1 + 1;",
             options: [{ position: "beside", applyDefaultPatterns: false }],
             errors: [{
@@ -118,7 +118,7 @@ ruleTester.run("line-comment-position", rule, {
                 column: 1
             }]
         },
-        {   // new option name takes precedence
+        { // new option name takes precedence
             code: "// jscs: disable\n1 + 1;",
             options: [{ position: "beside", applyDefaultIgnorePatterns: false, applyDefaultPatterns: true }],
             errors: [{
diff --git a/tests/lib/rules/no-await-in-loop.js b/tests/lib/rules/no-await-in-loop.js
index 86924f073..15673ded2 100644
--- a/tests/lib/rules/no-await-in-loop.js
+++ b/tests/lib/rules/no-await-in-loop.js
@@ -20,7 +20,7 @@ ruleTester.run("no-await-in-loop", rule, {
         "async function foo() { for (var bar = await baz in qux) {} }",
 
         // While loops
-        "async function foo() { while (true) { async function foo() { await bar; } } }",  // Blocked by a function declaration
+        "async function foo() { while (true) { async function foo() { await bar; } } }", // Blocked by a function declaration
         // For loops
         "async function foo() { for (var i = await bar; i < n; i++) {  } }",
 
diff --git a/tests/lib/rules/no-else-return.js b/tests/lib/rules/no-else-return.js
index 6aea5c867..eca303dbc 100644
--- a/tests/lib/rules/no-else-return.js
+++ b/tests/lib/rules/no-else-return.js
@@ -61,7 +61,7 @@ ruleTester.run("no-else-return", rule, {
             errors: [{ message: "Unnecessary 'else' after 'return'.", type: "ReturnStatement" }] },
         {
             code: "function foo4() { if (true) { if (false) return x; else return y; } else { return z; } }",
-            output: "function foo4() { if (true) { if (false) return x; return y; } else { return z; } }",  // Other case is fixed in the second pass.
+            output: "function foo4() { if (true) { if (false) return x; return y; } else { return z; } }", // Other case is fixed in the second pass.
             errors: [{ message: "Unnecessary 'else' after 'return'.", type: "ReturnStatement" }, { message: "Unnecessary 'else' after 'return'.", type: "BlockStatement" }]
         },
         {
@@ -76,7 +76,7 @@ ruleTester.run("no-else-return", rule, {
         },
         {
             code: "function foo7() { if (true) { if (false) { if (true) return x; else return y; } return w; } else { return z; } }",
-            output: "function foo7() { if (true) { if (false) { if (true) return x; return y; } return w; } else { return z; } }",  // Other case is fixed in the second pass.
+            output: "function foo7() { if (true) { if (false) { if (true) return x; return y; } return w; } else { return z; } }", // Other case is fixed in the second pass.
             errors: [
                 { message: "Unnecessary 'else' after 'return'.", type: "ReturnStatement" },
                 { message: "Unnecessary 'else' after 'return'.", type: "BlockStatement" }
@@ -84,7 +84,7 @@ ruleTester.run("no-else-return", rule, {
         },
         {
             code: "function foo8() { if (true) { if (false) { if (true) return x; else return y; } else { w = x; } } else { return z; } }",
-            output: "function foo8() { if (true) { if (false) { if (true) return x; return y; } else { w = x; } } else { return z; } }",  // Other case is fixed in the second pass.
+            output: "function foo8() { if (true) { if (false) { if (true) return x; return y; } else { w = x; } } else { return z; } }", // Other case is fixed in the second pass.
             errors: [
                 { message: "Unnecessary 'else' after 'return'.", type: "ReturnStatement" },
                 { message: "Unnecessary 'else' after 'return'.", type: "BlockStatement" }
diff --git a/tests/lib/rules/no-multi-spaces.js b/tests/lib/rules/no-multi-spaces.js
index f3d874423..beaeec742 100644
--- a/tests/lib/rules/no-multi-spaces.js
+++ b/tests/lib/rules/no-multi-spaces.js
@@ -74,7 +74,27 @@ ruleTester.run("no-multi-spaces", rule, {
         {
             code: "var  answer = 6 *  7;",
             options: [{ exceptions: { VariableDeclaration: true, BinaryExpression: true } }]
-        }
+        },
+
+        // https://github.com/eslint/eslint/issues/7693
+        "var x = 5; // comment",
+        "var x = 5; /* multiline\n * comment\n */",
+        "var x = 5;\n  // comment",
+        "var x = 5;  \n// comment",
+        "var x = 5;\n  /* multiline\n * comment\n */",
+        "var x = 5;  \n/* multiline\n * comment\n */",
+        { code: "var x = 5; // comment", options: [{ ignoreEOLComments: false }] },
+        { code: "var x = 5; /* multiline\n * comment\n */", options: [{ ignoreEOLComments: false }] },
+        { code: "var x = 5;\n  // comment", options: [{ ignoreEOLComments: false }] },
+        { code: "var x = 5;  \n// comment", options: [{ ignoreEOLComments: false }] },
+        { code: "var x = 5;\n  /* multiline\n * comment\n */", options: [{ ignoreEOLComments: false }] },
+        { code: "var x = 5;  \n/* multiline\n * comment\n */", options: [{ ignoreEOLComments: false }] },
+        { code: "var x = 5;  // comment", options: [{ ignoreEOLComments: true }] },
+        { code: "var x = 5;  /* multiline\n * comment\n */", options: [{ ignoreEOLComments: true }] },
+        { code: "var x = 5;\n  // comment", options: [{ ignoreEOLComments: true }] },
+        { code: "var x = 5;  \n// comment", options: [{ ignoreEOLComments: true }] },
+        { code: "var x = 5;\n  /* multiline\n * comment\n */", options: [{ ignoreEOLComments: true }] },
+        { code: "var x = 5;  \n/* multiline\n * comment\n */", options: [{ ignoreEOLComments: true }] }
     ],
 
     invalid: [
@@ -379,6 +399,199 @@ ruleTester.run("no-multi-spaces", rule, {
                 message: "Multiple spaces found before '5'.",
                 type: "Numeric"
             }]
+        },
+
+        // https://github.com/eslint/eslint/issues/7693
+        {
+            code: "var x =  /* comment */ 5;",
+            output: "var x = /* comment */ 5;",
+            errors: [{
+                message: "Multiple spaces found before '/* comment */'.",
+                type: "Block"
+            }]
+        },
+        {
+            code: "var x = /* comment */  5;",
+            output: "var x = /* comment */ 5;",
+            errors: [{
+                message: "Multiple spaces found before '5'.",
+                type: "Numeric"
+            }]
+        },
+        {
+            code: "var x = 5;  // comment",
+            output: "var x = 5; // comment",
+            errors: [{
+                message: "Multiple spaces found before '// comment'.",
+                type: "Line"
+            }]
+        },
+        {
+            code: "var x = 5;  // comment\nvar y = 6;",
+            output: "var x = 5; // comment\nvar y = 6;",
+            errors: [{
+                message: "Multiple spaces found before '// comment'.",
+                type: "Line"
+            }]
+        },
+        {
+            code: "var x = 5;  /* multiline\n * comment\n */",
+            output: "var x = 5; /* multiline\n * comment\n */",
+            errors: [{
+                message: "Multiple spaces found before '/* multiline...*/'.",
+                type: "Block"
+            }]
+        },
+        {
+            code: "var x = 5;  /* multiline\n * comment\n */\nvar y = 6;",
+            output: "var x = 5; /* multiline\n * comment\n */\nvar y = 6;",
+            errors: [{
+                message: "Multiple spaces found before '/* multiline...*/'.",
+                type: "Block"
+            }]
+        },
+        {
+            code: "var x = 5;  // this is a long comment",
+            output: "var x = 5; // this is a long comment",
+            errors: [{
+                message: "Multiple spaces found before '// this is a l...'.",
+                type: "Line"
+            }]
+        },
+        {
+            code: "var x =  /* comment */ 5;",
+            output: "var x = /* comment */ 5;",
+            options: [{ ignoreEOLComments: false }],
+            errors: [{
+                message: "Multiple spaces found before '/* comment */'.",
+                type: "Block"
+            }]
+        },
+        {
+            code: "var x = /* comment */  5;",
+            output: "var x = /* comment */ 5;",
+            options: [{ ignoreEOLComments: false }],
+            errors: [{
+                message: "Multiple spaces found before '5'.",
+                type: "Numeric"
+            }]
+        },
+        {
+            code: "var x = 5;  // comment",
+            output: "var x = 5; // comment",
+            options: [{ ignoreEOLComments: false }],
+            errors: [{
+                message: "Multiple spaces found before '// comment'.",
+                type: "Line"
+            }]
+        },
+        {
+            code: "var x = 5;  // comment\nvar y = 6;",
+            output: "var x = 5; // comment\nvar y = 6;",
+            options: [{ ignoreEOLComments: false }],
+            errors: [{
+                message: "Multiple spaces found before '// comment'.",
+                type: "Line"
+            }]
+        },
+        {
+            code: "var x = 5;  /* multiline\n * comment\n */",
+            output: "var x = 5; /* multiline\n * comment\n */",
+            options: [{ ignoreEOLComments: false }],
+            errors: [{
+                message: "Multiple spaces found before '/* multiline...*/'.",
+                type: "Block"
+            }]
+        },
+        {
+            code: "var x = 5;  /* multiline\n * comment\n */\nvar y = 6;",
+            output: "var x = 5; /* multiline\n * comment\n */\nvar y = 6;",
+            options: [{ ignoreEOLComments: false }],
+            errors: [{
+                message: "Multiple spaces found before '/* multiline...*/'.",
+                type: "Block"
+            }]
+        },
+        {
+            code: "var x = 5;  // this is a long comment",
+            output: "var x = 5; // this is a long comment",
+            options: [{ ignoreEOLComments: false }],
+            errors: [{
+                message: "Multiple spaces found before '// this is a l...'.",
+                type: "Line"
+            }]
+        },
+        {
+            code: "var x =  /* comment */ 5;  // EOL comment",
+            output: "var x = /* comment */ 5;  // EOL comment",
+            options: [{ ignoreEOLComments: true }],
+            errors: [{
+                message: "Multiple spaces found before '/* comment */'.",
+                type: "Block"
+            }]
+        },
+        {
+            code: "var x =  /* comment */ 5;  // EOL comment\nvar y = 6;",
+            output: "var x = /* comment */ 5;  // EOL comment\nvar y = 6;",
+            options: [{ ignoreEOLComments: true }],
+            errors: [{
+                message: "Multiple spaces found before '/* comment */'.",
+                type: "Block"
+            }]
+        },
+        {
+            code: "var x = /* comment */  5;  /* EOL comment */",
+            output: "var x = /* comment */ 5;  /* EOL comment */",
+            options: [{ ignoreEOLComments: true }],
+            errors: [{
+                message: "Multiple spaces found before '5'.",
+                type: "Numeric"
+            }]
+        },
+        {
+            code: "var x = /* comment */  5;  /* EOL comment */\nvar y = 6;",
+            output: "var x = /* comment */ 5;  /* EOL comment */\nvar y = 6;",
+            options: [{ ignoreEOLComments: true }],
+            errors: [{
+                message: "Multiple spaces found before '5'.",
+                type: "Numeric"
+            }]
+        },
+        {
+            code: "var x =  /*comment without spaces*/ 5;",
+            output: "var x = /*comment without spaces*/ 5;",
+            options: [{ ignoreEOLComments: true }],
+            errors: [{
+                message: "Multiple spaces found before '/*comment with...*/'.",
+                type: "Block"
+            }]
+        },
+        {
+            code: "var x = 5;  //comment without spaces",
+            output: "var x = 5; //comment without spaces",
+            options: [{ ignoreEOLComments: false }],
+            errors: [{
+                message: "Multiple spaces found before '//comment with...'.",
+                type: "Line"
+            }]
+        },
+        {
+            code: "var x = 5;  /*comment without spaces*/",
+            output: "var x = 5; /*comment without spaces*/",
+            options: [{ ignoreEOLComments: false }],
+            errors: [{
+                message: "Multiple spaces found before '/*comment with...*/'.",
+                type: "Block"
+            }]
+        },
+        {
+            code: "var x = 5;  /*comment\n without spaces*/",
+            output: "var x = 5; /*comment\n without spaces*/",
+            options: [{ ignoreEOLComments: false }],
+            errors: [{
+                message: "Multiple spaces found before '/*comment...*/'.",
+                type: "Block"
+            }]
         }
     ]
 });
diff --git a/tests/lib/rules/no-multiple-empty-lines.js b/tests/lib/rules/no-multiple-empty-lines.js
index 65379321a..a3ff4e034 100644
--- a/tests/lib/rules/no-multiple-empty-lines.js
+++ b/tests/lib/rules/no-multiple-empty-lines.js
@@ -316,6 +316,14 @@ ruleTester.run("no-multiple-empty-lines", rule, {
             code: `a\n\n\n\n${"a".repeat(1e5)}`,
             output: `a\n\n\n${"a".repeat(1e5)}`,
             errors: [getExpectedError(2)]
+        },
+        {
+
+            // https://github.com/eslint/eslint/issues/8401
+            code: "foo\n ",
+            output: "foo\n",
+            options: [{ max: 1, maxEOF: 0 }],
+            errors: [getExpectedErrorEOF(0)]
         }
     ]
 });
diff --git a/tests/lib/rules/no-self-assign.js b/tests/lib/rules/no-self-assign.js
index 6f080ae67..e4b14fdb8 100644
--- a/tests/lib/rules/no-self-assign.js
+++ b/tests/lib/rules/no-self-assign.js
@@ -46,7 +46,7 @@ ruleTester.run("no-self-assign", rule, {
         { code: "a[b] = a.b", options: [{ props: true }] },
         { code: "a.b().c = a.b().c", options: [{ props: true }] },
         { code: "b().c = b().c", options: [{ props: true }] },
-        { code: "a[b + 1] = a[b + 1]", options: [{ props: true }] },  // it ignores non-simple computed properties.
+        { code: "a[b + 1] = a[b + 1]", options: [{ props: true }] }, // it ignores non-simple computed properties.
         { code: "a.b = a.b" },
         { code: "a.b.c = a.b.c" },
         { code: "a[b] = a[b]" },
diff --git a/tests/lib/rules/no-useless-return.js b/tests/lib/rules/no-useless-return.js
index 355506aea..78e82fb26 100644
--- a/tests/lib/rules/no-useless-return.js
+++ b/tests/lib/rules/no-useless-return.js
@@ -211,7 +211,7 @@ ruleTester.run("no-useless-return", rule, {
                 }
                 return;
               }
-            `,  // Other case is fixed in the second pass.
+            `, // Other case is fixed in the second pass.
             errors: [
                 { message: "Unnecessary return statement.", type: "ReturnStatement" },
                 { message: "Unnecessary return statement.", type: "ReturnStatement" }
@@ -416,7 +416,7 @@ ruleTester.run("no-useless-return", rule, {
         },
         {
             code: "function foo() { return; return; }",
-            output: "function foo() {  return; }",  // Other case is fixed in the second pass.
+            output: "function foo() {  return; }", // Other case is fixed in the second pass.
             errors: [
                 { message: "Unnecessary return statement.", type: "ReturnStatement" },
                 { message: "Unnecessary return statement.", type: "ReturnStatement" }
diff --git a/tests/lib/rules/padded-blocks.js b/tests/lib/rules/padded-blocks.js
index c9208183b..a691e1108 100644
--- a/tests/lib/rules/padded-blocks.js
+++ b/tests/lib/rules/padded-blocks.js
@@ -39,23 +39,14 @@ ruleTester.run("padded-blocks", rule, {
         { code: "{\n\na();\n\n/* comment */ }", options: ["always"] },
         { code: "{\n\na();\n\n/* comment */ }", options: [{ blocks: "always" }] },
 
-        // Ignore switches by default
-        { code: "switch (a) {\n\ncase 0: foo();\ncase 1: bar();\n\n}", options: ["always"] },
-        { code: "switch (a) {\n\ncase 0: foo();\ncase 1: bar();\n\n}", options: ["never"] },
-
-        // Ignore block statements if not configured
-        { code: "{\na();\n}", options: [{ switches: "always" }] },
-        { code: "{\n\na();\n\n}", options: [{ switches: "never" }] },
-
         { code: "switch (a) {}", options: [{ switches: "always" }] },
+        { code: "switch (a) {\n\ncase 0: foo();\ncase 1: bar();\n\n}", options: ["always"] },
         { code: "switch (a) {\n\ncase 0: foo();\ncase 1: bar();\n\n}", options: [{ switches: "always" }] },
         { code: "switch (a) {\n\n//comment\ncase 0: foo();//comment\n\n}", options: [{ switches: "always" }] },
         { code: "switch (a) {//coment\n\ncase 0: foo();\ncase 1: bar();\n\n/* comment */}", options: [{ switches: "always" }] },
 
-        // Ignore classes by default
-        { code: "class A{\nfoo(){}\n}", parserOptions: { ecmaVersion: 6 } },
         { code: "class A{\n\nfoo(){}\n\n}", parserOptions: { ecmaVersion: 6 } },
-
+        { code: "class A{\n\nfoo(){}\n\n}", parserOptions: { ecmaVersion: 6 }, options: ["always"] },
         { code: "class A{}", parserOptions: { ecmaVersion: 6 }, options: [{ classes: "always" }] },
         { code: "class A{\n\n}", parserOptions: { ecmaVersion: 6 }, options: [{ classes: "always" }] },
         { code: "class A{\n\nfoo(){}\n\n}", parserOptions: { ecmaVersion: 6 }, options: [{ classes: "always" }] },
@@ -75,8 +66,27 @@ ruleTester.run("padded-blocks", rule, {
         { code: "{\n\n// comment\nif (\n// comment\n a) {}\n\n }", options: ["always"] },
         { code: "{\n// comment\nif (\n// comment\n a) {}\n }", options: ["never"] },
         { code: "{\n// comment\nif (\n// comment\n a) {}\n }", options: [{ blocks: "never" }] },
+
+        { code: "switch (a) {\ncase 0: foo();\n}", options: ["never"] },
         { code: "switch (a) {\ncase 0: foo();\n}", options: [{ switches: "never" }] },
-        { code: "class A{\nfoo(){}\n}", parserOptions: { ecmaVersion: 6 }, options: [{ classes: "never" }] }
+
+
+        { code: "class A{\nfoo(){}\n}", parserOptions: { ecmaVersion: 6 }, options: ["never"] },
+        { code: "class A{\nfoo(){}\n}", parserOptions: { ecmaVersion: 6 }, options: [{ classes: "never" }] },
+
+        // Ignore block statements if not configured
+        { code: "{\na();\n}", options: [{ switches: "always" }] },
+        { code: "{\n\na();\n\n}", options: [{ switches: "never" }] },
+
+        // Ignore switch statements if not configured
+        { code: "switch (a) {\ncase 0: foo();\ncase 1: bar();\n}", options: [{ blocks: "always", classes: "always" }] },
+        { code: "switch (a) {\n\ncase 0: foo();\ncase 1: bar();\n\n}", options: [{ blocks: "never", classes: "never" }] },
+
+
+        // Ignore class statements if not configured
+        { code: "class A{\nfoo(){}\n}", parserOptions: { ecmaVersion: 6 }, options: [{ blocks: "always" }] },
+        { code: "class A{\n\nfoo(){}\n\n}", parserOptions: { ecmaVersion: 6 }, options: [{ blocks: "never" }] }
+
     ],
     invalid: [
         {
@@ -214,6 +224,23 @@ ruleTester.run("padded-blocks", rule, {
                 }
             ]
         },
+        {
+            code: "switch (a) {\ncase 0: foo();\ncase 1: bar();\n}",
+            output: "switch (a) {\n\ncase 0: foo();\ncase 1: bar();\n\n}",
+            options: ["always"],
+            errors: [
+                {
+                    message: ALWAYS_MESSAGE,
+                    line: 1,
+                    column: 12
+                },
+                {
+                    message: ALWAYS_MESSAGE,
+                    line: 4,
+                    column: 1
+                }
+            ]
+        },
         {
             code: "switch (a) {\ncase 0: foo();\ncase 1: bar();\n}",
             output: "switch (a) {\n\ncase 0: foo();\ncase 1: bar();\n\n}",
@@ -248,6 +275,24 @@ ruleTester.run("padded-blocks", rule, {
                 }
             ]
         },
+        {
+            code: "class A {\nconstructor(){}\n}",
+            output: "class A {\n\nconstructor(){}\n\n}",
+            parserOptions: { ecmaVersion: 6 },
+            options: ["always"],
+            errors: [
+                {
+                    message: ALWAYS_MESSAGE,
+                    line: 1,
+                    column: 9
+                },
+                {
+                    message: ALWAYS_MESSAGE,
+                    line: 3,
+                    column: 1
+                }
+            ]
+        },
         {
             code: "class A {\nconstructor(){}\n}",
             output: "class A {\n\nconstructor(){}\n\n}",
@@ -416,6 +461,23 @@ ruleTester.run("padded-blocks", rule, {
                 }
             ]
         },
+        {
+            code: "switch (a) {\n\ncase 0: foo();\n\n}",
+            output: "switch (a) {\ncase 0: foo();\n}",
+            options: ["never"],
+            errors: [
+                {
+                    message: NEVER_MESSAGE,
+                    line: 1,
+                    column: 12
+                },
+                {
+                    message: NEVER_MESSAGE,
+                    line: 5,
+                    column: 1
+                }
+            ]
+        },
         {
             code: "switch (a) {\n\ncase 0: foo();\n}",
             output: "switch (a) {\ncase 0: foo();\n}",
@@ -440,6 +502,30 @@ ruleTester.run("padded-blocks", rule, {
                 }
             ]
         },
+        {
+            code: "class A {\n\nconstructor(){\n\nfoo();\n\n}\n\n}",
+            output: "class A {\nconstructor(){\nfoo();\n}\n}",
+            parserOptions: { ecmaVersion: 6 },
+            options: ["never"],
+            errors: [
+                {
+                    message: NEVER_MESSAGE,
+                    line: 1
+                },
+                {
+                    message: NEVER_MESSAGE,
+                    line: 3
+                },
+                {
+                    message: NEVER_MESSAGE,
+                    line: 7
+                },
+                {
+                    message: NEVER_MESSAGE,
+                    line: 9
+                }
+            ]
+        },
         {
             code: "class A {\n\nconstructor(){\n\nfoo();\n\n}\n\n}",
             output: "class A {\nconstructor(){\n\nfoo();\n\n}\n}",
diff --git a/tests/lib/rules/space-before-function-paren.js b/tests/lib/rules/space-before-function-paren.js
index 5e37dfd4e..a4e23864c 100644
--- a/tests/lib/rules/space-before-function-paren.js
+++ b/tests/lib/rules/space-before-function-paren.js
@@ -110,13 +110,8 @@ ruleTester.run("space-before-function-paren", rule, {
         { code: "async() => 1", options: [{ asyncArrow: "never" }], parserOptions: { ecmaVersion: 8 } },
         { code: "async () => 1", options: [{ asyncArrow: "ignore" }], parserOptions: { ecmaVersion: 8 } },
         { code: "async() => 1", options: [{ asyncArrow: "ignore" }], parserOptions: { ecmaVersion: 8 } },
-
-        // ignore by default for now.
         { code: "async () => 1", parserOptions: { ecmaVersion: 8 } },
-        { code: "async() => 1", parserOptions: { ecmaVersion: 8 } },
         { code: "async () => 1", options: ["always"], parserOptions: { ecmaVersion: 8 } },
-        { code: "async() => 1", options: ["always"], parserOptions: { ecmaVersion: 8 } },
-        { code: "async () => 1", options: ["never"], parserOptions: { ecmaVersion: 8 } },
         { code: "async() => 1", options: ["never"], parserOptions: { ecmaVersion: 8 } }
     ],
 
@@ -494,6 +489,26 @@ ruleTester.run("space-before-function-paren", rule, {
             options: [{ asyncArrow: "never" }],
             parserOptions: { ecmaVersion: 8 },
             errors: ["Unexpected space before function parentheses."]
+        },
+        {
+            code: "async() => 1",
+            output: "async () => 1",
+            parserOptions: { ecmaVersion: 8 },
+            errors: [{ message: "Missing space before function parentheses.", type: "ArrowFunctionExpression" }]
+        },
+        {
+            code: "async() => 1",
+            output: "async () => 1",
+            options: ["always"],
+            parserOptions: { ecmaVersion: 8 },
+            errors: [{ message: "Missing space before function parentheses.", type: "ArrowFunctionExpression" }]
+        },
+        {
+            code: "async () => 1",
+            output: "async() => 1",
+            options: ["never"],
+            parserOptions: { ecmaVersion: 8 },
+            errors: [{ message: "Unexpected space before function parentheses.", type: "ArrowFunctionExpression" }]
         }
     ]
 });
diff --git a/tests/lib/rules/space-unary-ops.js b/tests/lib/rules/space-unary-ops.js
index 0da1177b6..d5466bff5 100644
--- a/tests/lib/rules/space-unary-ops.js
+++ b/tests/lib/rules/space-unary-ops.js
@@ -36,6 +36,13 @@ ruleTester.run("space-unary-ops", rule, {
             code: "this.a--",
             options: [{ words: true }]
         },
+        {
+            code: "foo .bar++"
+        },
+        {
+            code: "foo.bar --",
+            options: [{ nonwords: true }]
+        },
         {
             code: "delete foo.bar",
             options: [{ words: true }]
@@ -417,6 +424,21 @@ ruleTester.run("space-unary-ops", rule, {
                 message: "Unary operator '++' must be followed by whitespace."
             }]
         },
+        {
+            code: "foo .bar++",
+            output: "foo .bar ++",
+            options: [{ nonwords: true }],
+            errors: [{
+                message: "Space is required before unary expressions '++'."
+            }]
+        },
+        {
+            code: "foo.bar --",
+            output: "foo.bar--",
+            errors: [{
+                message: "Unexpected space before unary operator '--'."
+            }]
+        },
         {
             code: "+ +foo",
             output: null,
diff --git a/tests/lib/util/node-event-generator.js b/tests/lib/util/node-event-generator.js
index 8cea6c47b..e297b5e45 100644
--- a/tests/lib/util/node-event-generator.js
+++ b/tests/lib/util/node-event-generator.js
@@ -222,13 +222,13 @@ describe("NodeEventGenerator", () => {
         assertEmissions(
             "[foo, 5, foo]",
             ["Identifier + Literal"],
-            ast => [["Identifier + Literal", ast.body[0].expression.elements[1]]]  // 5
+            ast => [["Identifier + Literal", ast.body[0].expression.elements[1]]] // 5
         );
 
         assertEmissions(
             "[foo, {}, 5]",
             ["Identifier + Literal", "Identifier ~ Literal"],
-            ast => [["Identifier ~ Literal", ast.body[0].expression.elements[2]]]  // 5
+            ast => [["Identifier ~ Literal", ast.body[0].expression.elements[2]]] // 5
         );
 
         assertEmissions(