Repository: eslint/eslint
Pull request #11568 (2019-03-30 15:57:14)

Docs: README updates to reflect JSCS compat project is finished
<!--
    ESLint adheres to the [JS Foundation Code of Conduct](https://js.foundation/community/code-of-conduct).
-->

**What is the purpose of this pull request? (put an "X" next to item)**

[x] Documentation update
[ ] Bug fix ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/bug-report.md))
[ ] New rule ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/rule-proposal.md))
[ ] Changes an existing rule ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/rule-change-proposal.md))
[ ] Add autofixing to a rule
[ ] Add a CLI option
[ ] Add something to the core
[ ] Other, please explain:

<!--
    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 (https://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)**

Edited the FAQ about JSCS to reflect ESLint's belief that we are nearly 100% compatible with JSCS and no longer need the project.

See https://github.com/eslint/tsc-meetings/blob/master/notes/2019/2019-03-14.md#jscs-compatibility-status.

**Is there anything you'd like reviewers to focus on?**

This is just a draft-- please suggest alternative/better wording if needed.


Patch:
diff --git a/.travis.yml b/.travis.yml
index baf4d28fd..03cd84ead 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,8 +1,6 @@
 language: node_js
 node_js:
-    - "6"
     - "8"
-    - "9"
     - "10"
     - "11"
 
diff --git a/README.md b/README.md
index 172c956ad..d8c7634e2 100644
--- a/README.md
+++ b/README.md
@@ -26,13 +26,9 @@ ESLint is a tool for identifying and reporting on patterns found in ECMAScript/J
 
 ## Installation and Usage
 
-Prerequisites: [Node.js](https://nodejs.org/en/) (>=6.14), npm version 3+.
+Prerequisites: [Node.js](https://nodejs.org/) (`^8.10.0`, `^10.13.0`, or `>=11.10.1`), npm version 3+.
 
-There are two ways to install ESLint: globally and locally.
-
-### Local Installation and Usage
-
-If you want to include ESLint as part of your project's build system, we recommend installing it locally. You can do so using npm:
+You can install ESLint using npm:
 
 ```
 $ npm install eslint --save-dev
@@ -50,31 +46,7 @@ After that, you can run ESLint on any file or directory like this:
 $ ./node_modules/.bin/eslint yourfile.js
 ```
 
-Any plugins or shareable configs that you use must also be installed locally to work with a locally-installed ESLint.
-
-### Global Installation and Usage
-
-If you want to make ESLint available to tools that run across all of your projects, we recommend installing ESLint globally. You can do so using npm:
-
-```
-$ npm install -g eslint
-```
-
-You should then set up a configuration file:
-
-```
-$ eslint --init
-```
-
-After that, you can run ESLint on any file or directory like this:
-
-```
-$ eslint yourfile.js
-```
-
-Any plugins or shareable configs that you use must also be installed globally to work with a globally-installed ESLint.
-
-**Note:** `eslint --init` is intended for setting up and configuring ESLint on a per-project basis and will perform a local installation of ESLint and its plugins in the directory in which it is run. If you prefer using a global installation of ESLint, any plugins used in your configuration must also be installed globally.
+It is also possible to install ESLint globally rather than locally (using `npm install eslint --global`). However, any plugins or shareable configs that you use must be installed locally in either case.
 
 ## Configuration
 
@@ -114,9 +86,11 @@ Before filing an issue, please be sure to read the guidelines for what you're re
 
 ### I'm using JSCS, should I migrate to ESLint?
 
-Maybe, depending on how much you need it. [JSCS has reached end of life](https://eslint.org/blog/2016/07/jscs-end-of-life), but if it is working for you then there is no reason to move yet. There are still [a few issues](https://github.com/eslint/eslint/milestones/JSCS%20Compatibility) pending. We’ll announce when all of the changes necessary to support JSCS users in ESLint are complete and will start encouraging JSCS users to switch to ESLint at that time.
+Yes. [JSCS has reached end of life](https://eslint.org/blog/2016/07/jscs-end-of-life) and is no longer supported.
+
+We have prepared a [migration guide](https://eslint.org/docs/user-guide/migrating-from-jscs) to help you convert your JSCS settings to an ESLint configuration.
 
-If you are having issues with JSCS, you can try to move to ESLint. Have a look at our [migration guide](https://eslint.org/docs/user-guide/migrating-from-jscs).
+We are now at or near 100% compatibility with JSCS. If you try ESLint and believe we are not yet compatible with a JSCS rule/configuration, please create an issue (mentioning that it is a JSCS compatibility issue) and we will evaluate it as per our normal process.
 
 ### Does Prettier replace ESLint?
 
@@ -124,16 +98,9 @@ No, ESLint does both traditional linting (looking for problematic patterns) and
 
 ### Why can't ESLint find my plugins?
 
-ESLint can be [globally or locally installed](#installation-and-usage). If you install ESLint globally, your plugins must also be installed globally; if you install ESLint locally, your plugins must also be installed locally.
-
-If you are trying to run globally, make sure your plugins are installed globally (use `npm ls -g`).
-
-If you are trying to run locally:
-
 * Make sure your plugins (and ESLint) are both in your project's `package.json` as devDependencies (or dependencies, if your project uses ESLint at runtime).
 * Make sure you have run `npm install` and all your dependencies are installed.
-
-In all cases, make sure your plugins' peerDependencies have been installed as well. You can use `npm view eslint-plugin-myplugin peerDependencies` to see what peer dependencies `eslint-plugin-myplugin` has.
+* Make sure your plugins' peerDependencies have been installed as well. You can use `npm view eslint-plugin-myplugin peerDependencies` to see what peer dependencies `eslint-plugin-myplugin` has.
 
 ### Does ESLint support JSX?
 
@@ -243,18 +210,25 @@ Teddy Katz
 </td></tr></tbody></table>
 
 
+### Reviewers
 
-
-### Committers
-
-The people who review and fix bugs and help triage issues.
+The people who review and implement new features.
 
 <table><tbody><tr><td align="center" valign="top" width="11%">
 <a href="https://github.com/aladdin-add">
 <img src="https://github.com/aladdin-add.png?s=75" width="75" height="75"><br />
 薛定谔的猫
 </a>
-</td><td align="center" valign="top" width="11%">
+</td></tr></tbody></table>
+
+
+
+
+### Committers
+
+The people who review and fix bugs and help triage issues.
+
+<table><tbody><tr><td align="center" valign="top" width="11%">
 <a href="https://github.com/g-plane">
 <img src="https://github.com/g-plane.png?s=75" width="75" height="75"><br />
 Pig Fang
@@ -271,9 +245,9 @@ The following companies, organizations, and individuals support ESLint's ongoing
 <!-- NOTE: This section is autogenerated. Do not manually edit.-->
 <!--sponsorsstart-->
 <h3>Gold Sponsors</h3>
-<p><a href="https://www.airbnb.com/"><img src="https://images.opencollective.com/proxy/images/?src=https%3A%2F%2Fopencollective-production.s3-us-west-1.amazonaws.com%2F098e3bd0-4d57-11e8-9324-0f6cc1f92bf1.png&height=96" alt="Airbnb" height="96"></a> <a href="https://code.facebook.com/projects/"><img src="https://images.opencollective.com/proxy/images/?src=https%3A%2F%2Fres.cloudinary.com%2Fopencollective%2Fimage%2Fupload%2Fv1508519428%2FS9gk78AS_400x400_fulq2l.jpg&height=96" alt="Facebook Open Source" height="96"></a> <a href="https://badoo.com/team?utm_source=eslint"><img src="https://images.opencollective.com/proxy/images/?src=https%3A%2F%2Fopencollective-production.s3-us-west-1.amazonaws.com%2Fbbdb9cc0-3b5d-11e9-9537-ad85092287b8.png&height=96" alt="Badoo" height="96"></a></p><h3>Silver Sponsors</h3>
+<p><a href="https://www.gitkraken.com/"><img src="https://images.opencollective.com/proxy/images/?src=https%3A%2F%2Flogo.clearbit.com%2Fgitkraken.com&height=96" alt="GitKraken" height="96"></a> <a href="https://code.facebook.com/projects/"><img src="https://images.opencollective.com/proxy/images/?src=https%3A%2F%2Fres.cloudinary.com%2Fopencollective%2Fimage%2Fupload%2Fv1508519428%2FS9gk78AS_400x400_fulq2l.jpg&height=96" alt="Facebook Open Source" height="96"></a> <a href="https://badoo.com/team?utm_source=eslint"><img src="https://images.opencollective.com/proxy/images/?src=https%3A%2F%2Fopencollective-production.s3-us-west-1.amazonaws.com%2Fbbdb9cc0-3b5d-11e9-9537-ad85092287b8.png&height=96" alt="Badoo" height="96"></a></p><h3>Silver Sponsors</h3>
 <p><a href="https://www.ampproject.org/"><img src="https://images.opencollective.com/proxy/images/?src=https%3A%2F%2Fopencollective-production.s3-us-west-1.amazonaws.com%2F68ed8b70-ebf2-11e6-9958-cb7e79408c56.png&height=96" alt="AMP Project" height="64"></a></p><h3>Bronze Sponsors</h3>
-<p><a href="http://faithlife.com/ref/about"><img src="https://images.opencollective.com/proxy/images/?src=https%3A%2F%2Flogo.clearbit.com%2Ffaithlife.com&height=96" alt="Faithlife" height="32"></a> <a href="https://jsheroes.io/"><img src="https://images.opencollective.com/proxy/images/?src=https%3A%2F%2Flogo.clearbit.com%2Fjsheroes.io&height=96" alt="JSHeroes " height="32"></a></p>
+<p><a href="http://faithlife.com/ref/about"><img src="https://images.opencollective.com/proxy/images/?src=https%3A%2F%2Flogo.clearbit.com%2Ffaithlife.com&height=96" alt="Faithlife" height="32"></a> <a href="https://jsheroes.io/"><img src="https://images.opencollective.com/proxy/images/?src=https%3A%2F%2Flogo.clearbit.com%2Fjsheroes.io&height=96" alt="JSHeroes " height="32"></a> <a href="http://www.firesticktricks.com"><img src="https://images.opencollective.com/proxy/images/?src=https%3A%2F%2Flogo.clearbit.com%2Ffiresticktricks.com&height=96" alt="Fire Stick Tricks" height="32"></a></p>
 <!--sponsorsend-->
 
 ## Technology Sponsors
diff --git a/conf/eslint-recommended.js b/conf/eslint-recommended.js
index 447a99638..d6368000f 100644
--- a/conf/eslint-recommended.js
+++ b/conf/eslint-recommended.js
@@ -9,7 +9,10 @@
 const builtInRules = require("../lib/built-in-rules-index");
 
 module.exports = {
-    rules: Object.assign({}, ...Object.keys(builtInRules).map(ruleId => ({
-        [ruleId]: builtInRules[ruleId].meta.docs.recommended ? "error" : "off"
-    })))
+    rules: Object.assign(
+        {},
+        ...Object.keys(builtInRules)
+            .filter(ruleId => builtInRules[ruleId].meta.docs.recommended)
+            .map(ruleId => ({ [ruleId]: "error" }))
+    )
 };
diff --git a/docs/developer-guide/nodejs-api.md b/docs/developer-guide/nodejs-api.md
index 3d41f25b9..6907e03c4 100644
--- a/docs/developer-guide/nodejs-api.md
+++ b/docs/developer-guide/nodejs-api.md
@@ -273,10 +273,8 @@ Map {
 
 ### Linter#defineParser
 
-Each instance of `Linter` holds a map of custom parsers. If you want to define a parser programmatically you can add this function
-with the name of the parser as first argument and the [parser object](/docs/developer-guide/working-with-plugins.md#working-with-custom-parsers) as second argument.
-
-If during linting the parser is not found, it will fallback to `require(parserId)`.
+Each instance of `Linter` holds a map of custom parsers. If you want to define a parser programmatically, you can add this function
+with the name of the parser as first argument and the [parser object](/docs/developer-guide/working-with-plugins.md#working-with-custom-parsers) as second argument. The default `"espree"` parser will already be loaded for every `Linter` instance.
 
 ```js
 const Linter = require("eslint").Linter;
diff --git a/docs/developer-guide/shareable-configs.md b/docs/developer-guide/shareable-configs.md
index 41d8b8f35..2181d1b44 100644
--- a/docs/developer-guide/shareable-configs.md
+++ b/docs/developer-guide/shareable-configs.md
@@ -38,6 +38,8 @@ You should declare your dependency on ESLint in `package.json` using the [peerDe
 }
 ```
 
+If your shareable config depends on a plugin, you should also specify it as a `peerDependency` (plugins will be loaded relative to the end user's project, so the end user is required to install the plugins they need). However, if your shareable config depends on a third-party parser or another shareable config, you can specify these packages as `dependencies`.
+
 You can also test your shareable config on your computer before publishing by linking your module globally. Type:
 
 ```bash
diff --git a/docs/rules/arrow-parens.md b/docs/rules/arrow-parens.md
index 371a500ff..f13efe9d8 100644
--- a/docs/rules/arrow-parens.md
+++ b/docs/rules/arrow-parens.md
@@ -52,7 +52,7 @@ This rule has a string option and an object one.
 String options are:
 
 * `"always"` (default) requires parens around arguments in all cases.
-* `"as-needed"` allows omitting parens when there is only one argument.
+* `"as-needed"` enforces no braces where they can be omitted.
 
 Object properties for variants of the `"as-needed"` option:
 
diff --git a/docs/rules/comma-dangle.md b/docs/rules/comma-dangle.md
index 275fd0363..ef9234205 100644
--- a/docs/rules/comma-dangle.md
+++ b/docs/rules/comma-dangle.md
@@ -48,7 +48,7 @@ This rule has a string option or an object option:
         "objects": "never",
         "imports": "never",
         "exports": "never",
-        "functions": "ignore"
+        "functions": "never"
     }]
 }
 ```
@@ -58,8 +58,6 @@ This rule has a string option or an object option:
 * `"always-multiline"` requires trailing commas when the last element or property is in a *different* line than the closing `]` or `}` and disallows trailing commas when the last element or property is on the *same* line as the closing `]` or `}`
 * `"only-multiline"` allows (but does not require) trailing commas when the last element or property is in a *different* line than the closing `]` or `}` and disallows trailing commas when the last element or property is on the *same* line as the closing `]` or `}`
 
-Trailing commas in function declarations and function calls are valid syntax since ECMAScript 2017; however, the string option does not check these situations for backwards compatibility.
-
 You can also use an object option to configure this rule for each type of syntax.
 Each of the following options can be set to `"never"`, `"always"`, `"always-multiline"`, `"only-multiline"`, or `"ignore"`.
 The default for each option is `"never"` unless otherwise specified.
@@ -69,7 +67,6 @@ The default for each option is `"never"` unless otherwise specified.
 * `imports` is for import declarations of ES Modules. (e.g. `import {a,} from "foo";`)
 * `exports` is for export declarations of ES Modules. (e.g. `export {a,};`)
 * `functions` is for function declarations and function calls. (e.g. `(function(a,){ })(b,);`)
-    * `functions` is set to `"ignore"` by default for consistency with the string option.
     * `functions` should only be enabled when linting ECMAScript 2017 or higher.
 
 ### never
diff --git a/docs/rules/function-paren-newline.md b/docs/rules/function-paren-newline.md
index 0c886bf53..099021e0e 100644
--- a/docs/rules/function-paren-newline.md
+++ b/docs/rules/function-paren-newline.md
@@ -13,6 +13,7 @@ This rule has a single option, which can either be a string or an object.
 * `"always"` requires line breaks inside all function parentheses.
 * `"never"` disallows line breaks inside all function parentheses.
 * `"multiline"` (default) requires linebreaks inside function parentheses if any of the parameters/arguments have a line break between them. Otherwise, it disallows linebreaks.
+* `"multiline-arguments"` works like `multiline` but allows linebreaks inside function parentheses if there is only one parameter/argument.
 * `"consistent"` requires consistent usage of linebreaks for each pair of parentheses. It reports an error if one parenthesis in the pair has a linebreak inside it and the other parenthesis does not.
 * `{ "minItems": value }` requires linebreaks inside function parentheses if the number of parameters/arguments is at least `value`. Otherwise, it disallows linebreaks.
 
@@ -225,6 +226,56 @@ foo(
 );
 ```
 
+Examples of **incorrect** code for this rule with the `"multiline-arguments"` option:
+
+```js
+/* eslint function-paren-newline: ["error", "multiline-arguments"] */
+
+function foo(bar,
+  baz
+) {}
+
+var foo = function(bar,
+  baz
+) {};
+
+var foo = (
+  bar,
+  baz) => {};
+
+foo(
+  bar,
+  baz);
+
+foo(
+  bar, qux,
+  baz
+);
+```
+
+Examples of **correct** code for this rule with the consistent `"multiline-arguments"` option:
+
+```js
+/* eslint function-paren-newline: ["error", "multiline-arguments"] */
+
+function foo(
+  bar,
+  baz
+) {}
+
+var foo = function(bar, baz) {};
+
+var foo = (
+  bar
+) => {};
+
+foo(
+  function() {
+    return baz;
+  }
+);
+```
+
 Examples of **incorrect** code for this rule with the `{ "minItems": 3 }` option:
 
 ```js
diff --git a/docs/rules/no-confusing-arrow.md b/docs/rules/no-confusing-arrow.md
index dca249f79..6947af0fb 100644
--- a/docs/rules/no-confusing-arrow.md
+++ b/docs/rules/no-confusing-arrow.md
@@ -48,7 +48,7 @@ This rule accepts a single options argument with the following defaults:
 }
 ```
 
-`allowParens` is a boolean setting that can be `true` or `false`:
+`allowParens` is a boolean setting that can be `true`(default) or `false`:
 
 1. `true` relaxes the rule and accepts parenthesis as a valid "confusion-preventing" syntax.
 2. `false` warns even if the expression is wrapped in parenthesis
diff --git a/docs/user-guide/command-line-interface.md b/docs/user-guide/command-line-interface.md
index 4cd3b9a53..a71684f70 100644
--- a/docs/user-guide/command-line-interface.md
+++ b/docs/user-guide/command-line-interface.md
@@ -111,14 +111,6 @@ Example:
 
 This example uses the configuration file at `~/my-eslint.json`.
 
-It also accepts a module ID of a [sharable config](../developer-guide/shareable-configs).
-
-Example:
-
-    eslint -c myconfig file.js
-
-This example directly uses the sharable config `eslint-config-myconfig`.
-
 If `.eslintrc.*` and/or `package.json` files are also used for configuration (i.e., `--no-eslintrc` was not specified), the configurations will be merged. Options from this configuration file have precedence over the options from `.eslintrc.*` and `package.json` files.
 
 #### `--env`
diff --git a/docs/user-guide/configuring.md b/docs/user-guide/configuring.md
index ec91ed774..ff8021b64 100644
--- a/docs/user-guide/configuring.md
+++ b/docs/user-guide/configuring.md
@@ -52,17 +52,12 @@ Here's an example `.eslintrc.json` file:
 
 Setting parser options helps ESLint determine what is a parsing error. All language options are `false` by default.
 
-### Deprecated
-
-* `ecmaFeatures.experimentalObjectRestSpread` - enable support for the experimental [object rest/spread properties](https://github.com/tc39/proposal-object-rest-spread). This syntax has been supported in `ecmaVersion: 2018`. This option will be removed in the future.
-
 ## Specifying Parser
 
 By default, ESLint uses [Espree](https://github.com/eslint/espree) as its parser. You can optionally specify that a different parser should be used in your configuration file so long as the parser meets the following requirements:
 
-1. It must be an npm module installed locally.
-1. It must have an Esprima-compatible interface (it must export a `parse()` method).
-1. It must produce Esprima-compatible AST and token objects.
+1. It must be a Node module loadable from the config file where it appears. Usually, this means you should install the parser package separately using npm.
+1. It must conform to the [parser interface](/docs/developer-guide/working-with-plugins.md#working-with-custom-parsers).
 
 Note that even with these compatibilities, there are no guarantees that an external parser will work correctly with ESLint and ESLint will not fix bugs related to incompatibilities with other parsers.
 
@@ -255,7 +250,7 @@ For historical reasons, the boolean value `false` and the string value `"readabl
 
 ## Configuring Plugins
 
-ESLint supports the use of third-party plugins. Before using the plugin you have to install it using npm.
+ESLint supports the use of third-party plugins. Before using the plugin, you have to install it using npm.
 
 To configure plugins inside of a configuration file, use the `plugins` key, which contains a list of plugin names. The `eslint-plugin-` prefix can be omitted from the plugin name.
 
@@ -277,7 +272,7 @@ And in YAML:
     - eslint-plugin-plugin2
 ```
 
-**Note:** Due to the behavior of Node's `require` function, a globally-installed instance of ESLint can only use globally-installed ESLint plugins, and locally-installed version can only use *locally-installed* plugins. Mixing local and global plugins is not supported.
+**Note:** Plugins are resolved relative to the current working directory of the ESLint process. In other words, ESLint will load the same plugin as a user would obtain by running `require('eslint-plugin-pluginname')` in a Node REPL from their project root.
 
 ## Configuring Rules
 
@@ -625,10 +620,10 @@ A configuration file can extend the set of enabled rules from base configuration
 
 The `extends` property value is either:
 
-* a string that specifies a configuration
+* a string that specifies a configuration (either a path to a config file, the name of a shareable config, `eslint:recommended`, or `eslint:all`)
 * an array of strings: each additional configuration extends the preceding configurations
 
-ESLint extends configurations recursively so a base configuration can also have an `extends` property.
+ESLint extends configurations recursively, so a base configuration can also have an `extends` property. Relative paths and shareable config names in an `extends` property are resolved from the location of the config file where they appear.
 
 The `rules` property can do any of the following to extend (or override) the set of rules:
 
@@ -723,9 +718,7 @@ Example of a configuration file in JSON format:
 
 ### Using a configuration file
 
-The `extends` property value can be an absolute or relative path to a base [configuration file](#using-configuration-files).
-
-ESLint resolves a relative path to a base configuration file relative to the configuration file that uses it **unless** that file is in your home directory or a directory that isn't an ancestor to the directory in which ESLint is installed (either locally or globally). In those cases, ESLint resolves the relative path to the base file relative to the linted **project** directory (typically the current working directory).
+The `extends` property value can be an absolute or relative path to a base [configuration file](#using-configuration-files). ESLint resolves a relative path to a base configuration file relative to the configuration file that uses it.
 
 Example of a configuration file in JSON format:
 
diff --git a/docs/user-guide/getting-started.md b/docs/user-guide/getting-started.md
index 2e6e6617a..c35471ea5 100644
--- a/docs/user-guide/getting-started.md
+++ b/docs/user-guide/getting-started.md
@@ -15,61 +15,25 @@ ESLint is a tool for identifying and reporting on patterns found in ECMAScript/J
 
 Prerequisites: [Node.js](https://nodejs.org/en/) (>=6.14), npm version 3+.
 
-There are two ways to install ESLint: globally and locally.
-
-### Local Installation and Usage
-
-If you want to include ESLint as part of your project's build system, we recommend installing it locally. You can do so using npm:
+You can install ESLint using npm:
 
 ```
 $ npm install eslint --save-dev
 ```
 
-You should then setup a configuration file:
+You should then set up a configuration file:
 
 ```
 $ ./node_modules/.bin/eslint --init
 ```
 
-After that, you can run ESLint in your project's root directory like this:
-
-```
-$ ./node_modules/.bin/eslint yourfile.js
-```
-
-Instead of navigating to `./node_modules/.bin/` you may also use `npx` to run `eslint`:
-
-```
-$ npx eslint
-```
-
-**Note:** If ESLint wasn't manually installed (via `npm`), `npx` will install `eslint` to a temporary directory and execute it.
-
-Any plugins or shareable configs that you use must also be installed locally to work with a locally-installed ESLint.
-
-### Global Installation and Usage
-
-If you want to make ESLint available to tools that run across all of your projects, we recommend installing ESLint globally. You can do so using npm:
-
-```
-$ npm install -g eslint
-```
-
-You should then setup a configuration file:
-
-```
-$ eslint --init
-```
-
 After that, you can run ESLint on any file or directory like this:
 
 ```
-$ eslint yourfile.js
+$ ./node_modules/.bin/eslint yourfile.js
 ```
 
-Any plugins or shareable configs that you use must also be installed globally to work with a globally-installed ESLint.
-
-**Note:** `eslint --init` is intended for setting up and configuring ESLint on a per-project basis and will perform a local installation of ESLint and its plugins in the directory in which it is run. If you prefer using a global installation of ESLint, any plugins used in your configuration must also be installed globally.
+It is also possible to install ESLint globally rather than locally (using `npm install eslint --global`). However, any plugins or shareable configs that you use must be installed locally in either case.
 
 ## Configuration
 
diff --git a/lib/cli-engine.js b/lib/cli-engine.js
index 301ffe3c7..1f4020338 100644
--- a/lib/cli-engine.js
+++ b/lib/cli-engine.js
@@ -27,13 +27,12 @@ const fs = require("fs"),
     globUtils = require("./util/glob-utils"),
     validator = require("./config/config-validator"),
     hash = require("./util/hash"),
-    ModuleResolver = require("./util/module-resolver"),
+    relativeModuleResolver = require("./util/relative-module-resolver"),
     naming = require("./util/naming"),
     pkg = require("../package.json"),
     loadRules = require("./load-rules");
 
 const debug = require("debug")("eslint:cli-engine");
-const resolver = new ModuleResolver();
 const validFixTypes = new Set(["problem", "suggestion", "layout"]);
 
 //------------------------------------------------------------------------------
@@ -184,6 +183,13 @@ function processText(text, configHelper, filename, fix, allowInlineConfig, repor
         configHelper.plugins.loadAll(config.plugins);
     }
 
+    if (config.parser) {
+        if (!path.isAbsolute(config.parser)) {
+            throw new Error(`Expected parser to be an absolute path but found ${config.parser}. This is a bug.`);
+        }
+        linter.defineParser(config.parser, require(config.parser));
+    }
+
     const loadedPlugins = configHelper.plugins.getAll();
 
     for (const plugin in loadedPlugins) {
@@ -463,7 +469,23 @@ class CLIEngine {
             });
         }
 
-        this.config = new Config(this.options, this.linter);
+        this.config = new Config(
+            {
+                cwd: this.options.cwd,
+                baseConfig: this.options.baseConfig,
+                rules: this.options.rules,
+                ignore: this.options.ignore,
+                ignorePath: this.options.ignorePath,
+                parser: this.options.parser,
+                parserOptions: this.options.parserOptions,
+                useEslintrc: this.options.useEslintrc,
+                envs: this.options.envs,
+                globals: this.options.globals,
+                configFile: this.options.configFile,
+                plugins: this.options.plugins
+            },
+            this.linter
+        );
 
         if (this.options.cache) {
             const cacheFile = getCacheFile(this.options.cacheLocation || this.options.cacheFile, this.options.cwd);
@@ -521,13 +543,14 @@ class CLIEngine {
 
             if (filteredMessages.length > 0) {
                 filtered.push(
-                    Object.assign(result, {
+                    {
+                        ...result,
                         messages: filteredMessages,
                         errorCount: filteredMessages.length,
                         warningCount: 0,
                         fixableErrorCount: result.fixableErrorCount,
                         fixableWarningCount: 0
-                    })
+                    }
                 );
             }
         });
@@ -764,16 +787,16 @@ class CLIEngine {
 
             let formatterPath;
 
-            // if there's a slash, then it's a file
+            // if there's a slash, then it's a file (TODO: this check seems dubious for scoped npm packages)
             if (!namespace && normalizedFormatName.indexOf("/") > -1) {
                 formatterPath = path.resolve(cwd, normalizedFormatName);
             } else {
                 try {
                     const npmFormat = naming.normalizePackageName(normalizedFormatName, "eslint-formatter");
 
-                    formatterPath = resolver.resolve(npmFormat, `${cwd}/node_modules`);
+                    formatterPath = relativeModuleResolver(npmFormat, path.join(cwd, "__placeholder__.js"));
                 } catch (e) {
-                    formatterPath = `./formatters/${normalizedFormatName}`;
+                    formatterPath = path.resolve(__dirname, "formatters", normalizedFormatName);
                 }
             }
 
diff --git a/lib/config.js b/lib/config.js
index 9c8438859..10e10830c 100644
--- a/lib/config.js
+++ b/lib/config.js
@@ -15,7 +15,8 @@ const path = require("path"),
     ConfigFile = require("./config/config-file"),
     ConfigCache = require("./config/config-cache"),
     Plugins = require("./config/plugins"),
-    FileFinder = require("./util/file-finder");
+    FileFinder = require("./util/file-finder"),
+    relativeModuleResolver = require("./util/relative-module-resolver");
 
 const debug = require("debug")("eslint:config");
 
@@ -26,6 +27,9 @@ const debug = require("debug")("eslint:config");
 const PERSONAL_CONFIG_DIR = os.homedir();
 const SUBCONFIG_SEP = ":";
 
+// A string which is not a valid config filepath, used to index a `baseConfig` object in the cache.
+const BASE_CONFIG_FILEPATH_CACHE_KEY = "";
+
 //------------------------------------------------------------------------------
 // Helpers
 //------------------------------------------------------------------------------
@@ -40,20 +44,6 @@ function hasRules(options) {
     return options.rules && Object.keys(options.rules).length > 0;
 }
 
-/**
- * Determines if a module is can be resolved.
- * @param {string} moduleId The ID (name) of the module
- * @returns {boolean} True if it is resolvable; False otherwise.
- */
-function isResolvable(moduleId) {
-    try {
-        require.resolve(moduleId);
-        return true;
-    } catch (err) {
-        return false;
-    }
-}
-
 //------------------------------------------------------------------------------
 // API
 //------------------------------------------------------------------------------
@@ -71,24 +61,36 @@ class Config {
         const options = providedOptions || {};
 
         this.linterContext = linterContext;
-        this.plugins = new Plugins(linterContext.environments, linterContext.defineRule.bind(linterContext));
-
         this.options = options;
+
+        /*
+         * `Linter#getRules` is slow, but a rule map is needed for every config file processed.
+         * Cache the rule map and keep it up to date.
+         */
+        this.ruleMap = new Map(linterContext.getRules());
+        this.plugins = new Plugins(linterContext.environments, {
+            defineRule: (ruleId, rule) => {
+                linterContext.defineRule(ruleId, rule);
+                this.ruleMap.set(ruleId, rule);
+            },
+            pluginRootPath: this.options.cwd
+        });
         this.ignore = options.ignore;
         this.ignorePath = options.ignorePath;
         this.parser = options.parser;
         this.parserOptions = options.parserOptions || {};
 
         this.configCache = new ConfigCache();
+        this._rootConfigReferencePath = path.join(this.options.cwd, "__placeholder__.js");
 
         this.baseConfig = options.baseConfig
-            ? ConfigOps.merge({}, ConfigFile.loadObject(options.baseConfig, this))
+            ? ConfigFile.loadObject(this, {
+                config: options.baseConfig,
+                filePath: path.join(this.options.cwd, "__baseConfig_placeholder_filename__.js"),
+                configFullName: "(Provided base config)"
+            })
             : { rules: {} };
-        this.baseConfig.filePath = "";
-        this.baseConfig.baseDirectory = this.options.cwd;
-
-        this.configCache.setConfig(this.baseConfig.filePath, this.baseConfig);
-        this.configCache.setMergedVectorConfig(this.baseConfig.filePath, this.baseConfig);
+        this.configCache.setConfig(BASE_CONFIG_FILEPATH_CACHE_KEY, this.baseConfig);
 
         this.useEslintrc = (options.useEslintrc !== false);
 
@@ -132,23 +134,28 @@ class Config {
         });
     }
 
+    getPluginRootPath() {
+        return this.options.cwd;
+    }
+
     /**
-     * Loads the config options from a config specified on the command line.
+     * Loads the config options from a config specified on the command line, relative to the CWD.
      * @param {string} [config] A shareable named config or path to a config file.
      * @returns {void}
      */
     loadSpecificConfig(config) {
         if (config) {
             debug(`Using command line config ${config}`);
-            const isNamedConfig =
-                isResolvable(config) ||
-                isResolvable(`eslint-config-${config}`) ||
-                config.charAt(0) === "@";
-
-            this.specificConfig = ConfigFile.load(
-                isNamedConfig ? config : path.resolve(this.options.cwd, config),
-                this
-            );
+            const resolvedPath = path.resolve(this.options.cwd, config);
+
+            this.specificConfigInfo = {
+                filePath: resolvedPath,
+                config: ConfigFile.load(
+                    resolvedPath,
+                    this,
+                    this._rootConfigReferencePath
+                )
+            };
         }
     }
 
@@ -164,10 +171,10 @@ class Config {
 
             if (filename) {
                 debug("Using personal config");
-                config = ConfigFile.load(filename, this);
+                config = ConfigFile.load(filename, this, this._rootConfigReferencePath);
             }
 
-            this.personalConfig = config || null;
+            this.personalConfig = config ? { config, filePath: filename } : null;
         }
 
         return this.personalConfig;
@@ -177,14 +184,14 @@ class Config {
      * Builds a hierarchy of config objects, including the base config, all local configs from the directory tree,
      * and a config file specified on the command line, if applicable.
      * @param {string} directory a file in whose directory we start looking for a local config
-     * @returns {Object[]} The config objects, in ascending order of precedence
+     * @returns {{filePath: (string|null), config: Config}[]} The config objects, in ascending order of precedence
      * @private
      */
     getConfigHierarchy(directory) {
         debug(`Constructing config file hierarchy for ${directory}`);
 
         // Step 1: Always include baseConfig
-        let configs = [this.baseConfig];
+        let configs = [{ filePath: BASE_CONFIG_FILEPATH_CACHE_KEY, config: this.baseConfig }];
 
         // Step 2: Add user-specified config from .eslintrc.* and package.json files
         if (this.useEslintrc) {
@@ -195,9 +202,9 @@ class Config {
         }
 
         // Step 3: Merge in command line config file
-        if (this.specificConfig) {
+        if (this.specificConfigInfo) {
             debug("Using command line config file");
-            configs.push(this.specificConfig);
+            configs.push(this.specificConfigInfo);
         }
 
         return configs;
@@ -237,7 +244,7 @@ class Config {
             }
 
             debug(`Loading ${localConfigFile}`);
-            const localConfig = ConfigFile.load(localConfigFile, this);
+            const localConfig = ConfigFile.load(localConfigFile, this, this._rootConfigReferencePath);
 
             // Ignore empty config files
             if (!localConfig) {
@@ -245,7 +252,7 @@ class Config {
             }
 
             debug(`Using ${localConfigFile}`);
-            configs.unshift(localConfig);
+            configs.unshift({ filePath: localConfigFile, config: localConfig });
             searched.push(localConfigDirectory);
 
             // Stop traversing if a config is found with the root flag set
@@ -254,7 +261,7 @@ class Config {
             }
         }
 
-        if (!configs.length && !this.specificConfig) {
+        if (!configs.length && !this.specificConfigInfo) {
 
             // Fall back on the personal config from ~/.eslintrc
             debug("Using personal config file");
@@ -289,21 +296,26 @@ class Config {
      * to entries in the config file's overrides section whose glob patterns match the specified file path; e.g., the
      * vector entry { configFile: '/home/john/app/.eslintrc', matchingOverrides: [0, 2] } would indicate that the main
      * project .eslintrc file and its first and third override blocks apply to the current file.
-     * @param {string} filePath The file path for which to build the hierarchy and config vector.
+     * @param {string} sourceFilePath The file path for which to build the hierarchy and config vector.
      * @returns {Array<Object>} config vector applicable to the specified path
      * @private
      */
-    getConfigVector(filePath) {
-        const directory = filePath ? path.dirname(filePath) : this.options.cwd;
+    getConfigVector(sourceFilePath) {
+        const directory = sourceFilePath ? path.dirname(sourceFilePath) : this.options.cwd;
 
-        return this.getConfigHierarchy(directory).map(config => {
+        return this.getConfigHierarchy(directory).map(({ config, filePath }) => {
             const vectorEntry = {
-                filePath: config.filePath,
+                filePath,
                 matchingOverrides: []
             };
 
             if (config.overrides) {
-                const relativePath = path.relative(config.baseDirectory, filePath || directory);
+                const relativePath = path.relative(
+                    filePath
+                        ? path.dirname(filePath)
+                        : this.options.cwd,
+                    sourceFilePath || directory
+                );
 
                 config.overrides.forEach((override, i) => {
                     if (ConfigOps.pathMatchesGlobs(relativePath, override.files, override.excludedFiles)) {
@@ -359,10 +371,9 @@ class Config {
 
         /*
          * Step 3: Override parser only if it is passed explicitly through the command line
-         * or if it's not defined yet (because the final object will at least have the parser key)
          */
-        if (this.parser || !config.parser) {
-            config = ConfigOps.merge(config, { parser: this.parser });
+        if (this.parser) {
+            config = ConfigOps.merge(config, { parser: relativeModuleResolver(this.parser, this._rootConfigReferencePath) });
         }
 
         // Step 4: Apply environments to the config
diff --git a/lib/config/config-file.js b/lib/config/config-file.js
index 80344f509..218963b27 100644
--- a/lib/config/config-file.js
+++ b/lib/config/config-file.js
@@ -13,12 +13,12 @@ const fs = require("fs"),
     path = require("path"),
     ConfigOps = require("./config-ops"),
     validator = require("./config-validator"),
-    ModuleResolver = require("../util/module-resolver"),
+    relativeModuleResolver = require("../util/relative-module-resolver"),
     naming = require("../util/naming"),
-    pathIsInside = require("path-is-inside"),
     stripComments = require("strip-json-comments"),
     stringify = require("json-stable-stringify-without-jsonify"),
-    importFresh = require("import-fresh");
+    importFresh = require("import-fresh"),
+    Plugins = require("./plugins");
 
 const debug = require("debug")("eslint:config-file");
 
@@ -52,8 +52,6 @@ const CONFIG_FILES = [
     "package.json"
 ];
 
-const resolver = new ModuleResolver();
-
 /**
  * Convenience wrapper for synchronously reading file contents.
  * @param {string} filePath The filename to read.
@@ -201,21 +199,21 @@ function configMissingError(configName) {
 /**
  * Loads a configuration file regardless of the source. Inspects the file path
  * to determine the correctly way to load the config file.
- * @param {Object} file The path to the configuration.
+ * @param {Object} configInfo The path to the configuration.
  * @returns {Object} The configuration information.
  * @private
  */
-function loadConfigFile(file) {
-    const filePath = file.filePath;
+function loadConfigFile(configInfo) {
+    const { filePath } = configInfo;
     let config;
 
     switch (path.extname(filePath)) {
         case ".js":
             config = loadJSConfigFile(filePath);
-            if (file.configName) {
-                config = config.configs[file.configName];
+            if (configInfo.configName) {
+                config = config.configs[configInfo.configName];
                 if (!config) {
-                    throw configMissingError(file.configFullName);
+                    throw configMissingError(configInfo.configFullName);
                 }
             }
             break;
@@ -342,45 +340,6 @@ function write(config, filePath) {
     }
 }
 
-/**
- * Determines the base directory for node packages referenced in a config file.
- * This does not include node_modules in the path so it can be used for all
- * references relative to a config file.
- * @param {string} configFilePath The config file referencing the file.
- * @returns {string} The base directory for the file path.
- * @private
- */
-function getBaseDir(configFilePath) {
-
-    // calculates the path of the project including ESLint as dependency
-    const projectPath = path.resolve(__dirname, "../../../");
-
-    if (configFilePath && pathIsInside(configFilePath, projectPath)) {
-
-        // be careful of https://github.com/substack/node-resolve/issues/78
-        return path.join(path.resolve(configFilePath));
-    }
-
-    /*
-     * default to ESLint project path since it's unlikely that plugins will be
-     * in this directory
-     */
-    return path.join(projectPath);
-}
-
-/**
- * Determines the lookup path, including node_modules, for package
- * references relative to a config file.
- * @param {string} configFilePath The config file referencing the file.
- * @returns {string} The lookup path for the file path.
- * @private
- */
-function getLookupPath(configFilePath) {
-    const basedir = getBaseDir(configFilePath);
-
-    return path.join(basedir, "node_modules");
-}
-
 /**
  * Resolves a eslint core config path
  * @param {string} name The eslint config name.
@@ -414,44 +373,21 @@ function getEslintCoreConfigPath(name) {
  * @param {Config} configContext Plugin context for the config instance
  * @param {string} filePath The file path from which the configuration information
  *      was loaded.
- * @param {string} [relativeTo] The path to resolve relative to.
  * @returns {Object} A new configuration object with all of the "extends" fields
  *      loaded and merged.
  * @private
  */
-function applyExtends(config, configContext, filePath, relativeTo) {
-    let configExtends = config.extends;
-
-    // normalize into an array for easier handling
-    if (!Array.isArray(config.extends)) {
-        configExtends = [config.extends];
-    }
+function applyExtends(config, configContext, filePath) {
+    const extendsList = Array.isArray(config.extends) ? config.extends : [config.extends];
 
     // Make the last element in an array take the highest precedence
-    return configExtends.reduceRight((previousValue, parentPath) => {
+    const flattenedConfig = extendsList.reduceRight((previousValue, extendedConfigReference) => {
         try {
-            let extensionPath;
-
-            if (parentPath.startsWith("eslint:")) {
-                extensionPath = getEslintCoreConfigPath(parentPath);
-            } else if (isFilePath(parentPath)) {
-
-                /*
-                 * If the `extends` path is relative, use the directory of the current configuration
-                 * file as the reference point. Otherwise, use as-is.
-                 */
-                extensionPath = (path.isAbsolute(parentPath)
-                    ? parentPath
-                    : path.join(relativeTo || path.dirname(filePath), parentPath)
-                );
-            } else {
-                extensionPath = parentPath;
-            }
-            debug(`Loading ${extensionPath}`);
+            debug(`Loading ${extendedConfigReference}`);
 
             // eslint-disable-next-line no-use-before-define
-            return ConfigOps.merge(load(extensionPath, configContext, relativeTo), previousValue);
-        } catch (e) {
+            return ConfigOps.merge(load(extendedConfigReference, configContext, filePath), previousValue);
+        } catch (err) {
 
             /*
              * If the file referenced by `extends` failed to load, add the path
@@ -459,124 +395,155 @@ function applyExtends(config, configContext, filePath, relativeTo) {
              * message so the user is able to see where it was referenced from,
              * then re-throw.
              */
-            e.message += `\nReferenced from: ${filePath}`;
-            throw e;
+            err.message += `\nReferenced from: ${filePath}`;
+
+            if (err.messageTemplate === "plugin-missing") {
+                err.messageData.configStack.push(filePath);
+            }
+
+            throw err;
         }
 
-    }, config);
+    }, Object.assign({}, config));
+
+    delete flattenedConfig.extends;
+    return flattenedConfig;
 }
 
 /**
  * Resolves a configuration file path into the fully-formed path, whether filename
  * or package name.
- * @param {string} filePath The filepath to resolve.
- * @param {string} [relativeTo] The path to resolve relative to.
+ * @param {string} extendedConfigReference The config to extend, as it appears in a config file
+ * @param {string} referencedFromPath The path to the config file that contains `extendedConfigReference`
+ * @param {string} pluginRootPath The absolute path to the directory where plugins should be resolved from
  * @returns {Object} An object containing 3 properties:
  * - 'filePath' (required) the resolved path that can be used directly to load the configuration.
- * - 'configName' the name of the configuration inside the plugin.
+ * - 'configName' the name of the configuration inside the plugin, if this is a plugin config.
  * - 'configFullName' (required) the name of the configuration as used in the eslint config(e.g. 'plugin:node/recommended'),
  *     or the absolute path to a config file. This should uniquely identify a config.
  * @private
  */
-function resolve(filePath, relativeTo) {
-    if (isFilePath(filePath)) {
-        const fullPath = path.resolve(relativeTo || "", filePath);
-
-        return { filePath: fullPath, configFullName: fullPath };
+function resolve(extendedConfigReference, referencedFromPath, pluginRootPath) {
+    if (extendedConfigReference.startsWith("eslint:")) {
+        return {
+            filePath: getEslintCoreConfigPath(extendedConfigReference),
+            configFullName: extendedConfigReference
+        };
     }
-    let normalizedPackageName;
 
-    if (filePath.startsWith("plugin:")) {
-        const configFullName = filePath;
-        const pluginName = filePath.slice(7, filePath.lastIndexOf("/"));
-        const configName = filePath.slice(filePath.lastIndexOf("/") + 1);
-
-        normalizedPackageName = naming.normalizePackageName(pluginName, "eslint-plugin");
-        debug(`Attempting to resolve ${normalizedPackageName}`);
+    if (extendedConfigReference.startsWith("plugin:")) {
+        const configFullName = extendedConfigReference;
+        const pluginName = extendedConfigReference.slice(7, extendedConfigReference.lastIndexOf("/"));
+        const configName = extendedConfigReference.slice(extendedConfigReference.lastIndexOf("/") + 1);
 
         return {
-            filePath: require.resolve(normalizedPackageName),
+            filePath: Plugins.resolve(pluginName, pluginRootPath),
             configName,
             configFullName
         };
     }
-    normalizedPackageName = naming.normalizePackageName(filePath, "eslint-config");
-    debug(`Attempting to resolve ${normalizedPackageName}`);
 
-    return {
-        filePath: resolver.resolve(normalizedPackageName, getLookupPath(relativeTo)),
-        configFullName: filePath
-    };
+    if (isFilePath(extendedConfigReference)) {
+        const resolvedConfigPath = path.resolve(path.dirname(referencedFromPath), extendedConfigReference);
 
+        return {
+            filePath: resolvedConfigPath,
+            configFullName: resolvedConfigPath
+        };
+    }
+
+    const normalizedConfigName = naming.normalizePackageName(extendedConfigReference, "eslint-config");
 
+    return {
+        filePath: relativeModuleResolver(normalizedConfigName, referencedFromPath),
+        configFullName: extendedConfigReference
+    };
 }
 
+
 /**
- * Loads a configuration file from the given file path.
- * @param {Object} resolvedPath The value from calling resolve() on a filename or package name.
- * @param {Config} configContext Plugins context
- * @returns {Object} The configuration information.
+ * Loads a config object, applying extends/parser/plugins if present.
+ * @param {Config} configContext Context for the config instance
+ * @param {Object} options.config a config object to load
+ * @param {string} options.filePath The path where `extends` and parsers should be resolved from (usually the path where the config was located from)
+ * @param {string} options.configFullName The full name of the config, for use in error messages
+ * @returns {Object} the config object with extends applied if present, or the passed config if not
+ * @private
  */
-function loadFromDisk(resolvedPath, configContext) {
-    const dirname = path.dirname(resolvedPath.filePath),
-        lookupPath = getLookupPath(dirname);
-    let config = loadConfigFile(resolvedPath);
-
-    if (config) {
+function loadObject(configContext, { config: configObject, filePath, configFullName }) {
+    const config = Object.assign({}, configObject);
 
-        // ensure plugins are properly loaded first
-        if (config.plugins) {
+    // ensure plugins are properly loaded first
+    if (config.plugins) {
+        try {
             configContext.plugins.loadAll(config.plugins);
+        } catch (pluginLoadErr) {
+            if (pluginLoadErr.messageTemplate === "plugin-missing") {
+                pluginLoadErr.messageData.configStack.push(filePath);
+            }
+            throw pluginLoadErr;
         }
+    }
 
-        // include full path of parser if present
-        if (config.parser) {
-            if (isFilePath(config.parser)) {
-                config.parser = path.resolve(dirname || "", config.parser);
+    // include full path of parser if present
+    if (config.parser) {
+        try {
+            config.parser = relativeModuleResolver(config.parser, filePath);
+        } catch (err) {
+            if (err.code === "MODULE_NOT_FOUND" && config.parser === "espree") {
+                config.parser = require.resolve("espree");
             } else {
-                config.parser = resolver.resolve(config.parser, lookupPath);
+                err.message += `\nFailed to resolve parser '${config.parser}' declared in '${filePath}'.`;
+                throw err;
             }
         }
+    }
 
-        const ruleMap = configContext.linterContext.getRules();
+    const ruleMap = configContext.ruleMap;
 
-        // validate the configuration before continuing
-        validator.validate(config, ruleMap.get.bind(ruleMap), configContext.linterContext.environments, resolvedPath.configFullName);
+    // validate the configuration before continuing
+    validator.validate(config, ruleMap.get.bind(ruleMap), configContext.linterContext.environments, configFullName);
 
-        /*
-         * If an `extends` property is defined, it represents a configuration file to use as
-         * a "parent". Load the referenced file and merge the configuration recursively.
-         */
-        if (config.extends) {
-            config = applyExtends(config, configContext, resolvedPath.filePath, dirname);
-        }
+    /*
+     * If an `extends` property is defined, it represents a configuration file to use as
+     * a "parent". Load the referenced file and merge the configuration recursively.
+     */
+    if (config.extends) {
+        return applyExtends(config, configContext, filePath);
     }
 
     return config;
 }
 
 /**
- * Loads a config object, applying extends if present.
- * @param {Object} configObject a config object to load
- * @param {Config} configContext Context for the config instance
- * @returns {Object} the config object with extends applied if present, or the passed config if not
- * @private
+ * Loads a configuration file from the given file path.
+ * @param {Object} configInfo The value from calling resolve() on a filename or package name.
+ * @param {Config} configContext Config context
+ * @returns {Object} The configuration information.
  */
-function loadObject(configObject, configContext) {
-    return configObject.extends ? applyExtends(configObject, configContext, "") : configObject;
+function loadFromDisk(configInfo, configContext) {
+    const config = loadConfigFile(configInfo);
+
+    // loadConfigFile will return null for a `package.json` file that does not have an `eslintConfig` property.
+    if (config) {
+        return loadObject(configContext, { config, filePath: configInfo.filePath, configFullName: configInfo.configFullName });
+    }
+
+    return null;
 }
 
 /**
  * Loads a config object from the config cache based on its filename, falling back to the disk if the file is not yet
  * cached.
- * @param {string} filePath the path to the config file
+ * @param {string} extendedConfigReference The config to extend, as it appears in a config file
  * @param {Config} configContext Context for the config instance
- * @param {string} [relativeTo] The path to resolve relative to.
+ * @param {string} referencedFromPath The path to the config file that contains `extendedConfigReference` (or where
+ * the `extendedConfigReference` should be resolved from, if it doesn't appear in a config file)
  * @returns {Object} the parsed config object (empty object if there was a parse error)
  * @private
  */
-function load(filePath, configContext, relativeTo) {
-    const resolvedPath = resolve(filePath, relativeTo);
+function load(extendedConfigReference, configContext, referencedFromPath) {
+    const resolvedPath = resolve(extendedConfigReference, referencedFromPath, configContext.getPluginRootPath());
 
     const cachedConfig = configContext.configCache.getConfig(resolvedPath.configFullName);
 
@@ -587,8 +554,6 @@ function load(filePath, configContext, relativeTo) {
     const config = loadFromDisk(resolvedPath, configContext);
 
     if (config) {
-        config.filePath = resolvedPath.filePath;
-        config.baseDirectory = path.dirname(resolvedPath.filePath);
         configContext.configCache.setConfig(resolvedPath.configFullName, config);
     }
 
@@ -617,9 +582,6 @@ function isExistingFile(filename) {
 //------------------------------------------------------------------------------
 
 module.exports = {
-
-    getBaseDir,
-    getLookupPath,
     load,
     loadObject,
     resolve,
diff --git a/lib/config/config-initializer.js b/lib/config/config-initializer.js
index adaf6c158..6c60905f6 100644
--- a/lib/config/config-initializer.js
+++ b/lib/config/config-initializer.js
@@ -11,6 +11,7 @@
 //------------------------------------------------------------------------------
 
 const util = require("util"),
+    path = require("path"),
     inquirer = require("inquirer"),
     ProgressBar = require("progress"),
     semver = require("semver"),
@@ -18,7 +19,7 @@ const util = require("util"),
     ConfigFile = require("./config-file"),
     ConfigOps = require("./config-ops"),
     getSourceCodeOfFiles = require("../util/source-code-utils").getSourceCodeOfFiles,
-    ModuleResolver = require("../util/module-resolver"),
+    relativeModuleResolver = require("../util/relative-module-resolver"),
     npmUtils = require("../util/npm-utils"),
     recConfig = require("../../conf/eslint-recommended"),
     log = require("../util/logging");
@@ -333,8 +334,7 @@ function getConfigForStyleGuide(guide) {
  */
 function getLocalESLintVersion() {
     try {
-        const resolver = new ModuleResolver();
-        const eslintPath = resolver.resolve("eslint", process.cwd());
+        const eslintPath = relativeModuleResolver("eslint", path.join(process.cwd(), "__placeholder__.js"));
         const eslint = require(eslintPath);
 
         return eslint.linter.version || null;
diff --git a/lib/config/config-ops.js b/lib/config/config-ops.js
index b38cdf7d7..48a8302d9 100644
--- a/lib/config/config-ops.js
+++ b/lib/config/config-ops.js
@@ -339,10 +339,7 @@ module.exports = {
 
         mergedConfig = overrides.reduce((lastConfig, override) => this.merge(lastConfig, override), mergedConfig);
 
-        if (mergedConfig.filePath) {
-            delete mergedConfig.filePath;
-            delete mergedConfig.baseDirectory;
-        } else if (mergedConfig.files) {
+        if (mergedConfig.files) {
             delete mergedConfig.files;
         }
 
@@ -366,7 +363,7 @@ module.exports = {
             }
         });
 
-        const opts = { matchBase: true };
+        const opts = { dot: true, matchBase: true };
 
         return patternList.some(pattern => minimatch(filePath, pattern, opts)) &&
             !excludedPatternList.some(excludedPattern => minimatch(filePath, excludedPattern, opts));
@@ -377,6 +374,7 @@ module.exports = {
      * @param {(boolean|string|null)} configuredValue The value given for a global in configuration or in
      * a global directive comment
      * @returns {("readable"|"writeable"|"off")} The value normalized as a string
+     * @throws Error if global value is invalid
      */
     normalizeConfigGlobal(configuredValue) {
         switch (configuredValue) {
@@ -396,9 +394,8 @@ module.exports = {
             case "readonly":
                 return "readable";
 
-            // Fallback to minimize compatibility impact
             default:
-                return "writeable";
+                throw new Error(`'${configuredValue}' is not a valid configuration for a global (use 'readonly', 'writable', or 'off')`);
         }
     }
 };
diff --git a/lib/config/config-validator.js b/lib/config/config-validator.js
index 386db7775..c6d106e60 100644
--- a/lib/config/config-validator.js
+++ b/lib/config/config-validator.js
@@ -10,11 +10,12 @@
 //------------------------------------------------------------------------------
 
 const path = require("path"),
-    ajv = require("../util/ajv"),
     lodash = require("lodash"),
     configSchema = require("../../conf/config-schema.js"),
-    util = require("util");
+    util = require("util"),
+    ConfigOps = require("./config-ops");
 
+const ajv = require("../util/ajv")();
 const ruleValidators = new WeakMap();
 
 //------------------------------------------------------------------------------
@@ -177,6 +178,27 @@ function validateRules(rulesConfig, ruleMapper, source = null) {
     });
 }
 
+/**
+ * Validates a `globals` section of a config file
+ * @param {Object} globalsConfig The `glboals` section
+ * @param {string|null} source The name of the configuration source to report in the event of an error.
+ * @returns {void}
+ */
+function validateGlobals(globalsConfig, source = null) {
+    if (!globalsConfig) {
+        return;
+    }
+
+    Object.entries(globalsConfig)
+        .forEach(([configuredGlobal, configuredValue]) => {
+            try {
+                ConfigOps.normalizeConfigGlobal(configuredValue);
+            } catch (err) {
+                throw new Error(`ESLint configuration of global '${configuredGlobal}' in ${source} is invalid:\n${err.message}`);
+            }
+        });
+}
+
 /**
  * Formats an array of schema validation errors.
  * @param {Array} errors An array of error messages to format.
@@ -238,15 +260,6 @@ function validateConfigSchema(config, source = null) {
     if (Object.hasOwnProperty.call(config, "ecmaFeatures")) {
         emitDeprecationWarning(source, "ESLINT_LEGACY_ECMAFEATURES");
     }
-
-    if (
-        (config.parser || "espree") === "espree" &&
-        config.parserOptions &&
-        config.parserOptions.ecmaFeatures &&
-        config.parserOptions.ecmaFeatures.experimentalObjectRestSpread
-    ) {
-        emitDeprecationWarning(source, "ESLINT_LEGACY_OBJECT_REST_SPREAD");
-    }
 }
 
 /**
@@ -261,6 +274,7 @@ function validate(config, ruleMapper, envContext, source = null) {
     validateConfigSchema(config, source);
     validateRules(config.rules, ruleMapper, source);
     validateEnvironment(config.env, envContext, source);
+    validateGlobals(config.globals, source);
 
     for (const override of config.overrides || []) {
         validateRules(override.rules, ruleMapper, source);
diff --git a/lib/config/plugins.js b/lib/config/plugins.js
index 029e7b106..fabcbfeae 100644
--- a/lib/config/plugins.js
+++ b/lib/config/plugins.js
@@ -8,9 +8,10 @@
 // Requirements
 //------------------------------------------------------------------------------
 
+const path = require("path");
 const debug = require("debug")("eslint:plugins");
 const naming = require("../util/naming");
-const path = require("path");
+const relativeModuleResolver = require("../util/relative-module-resolver");
 
 //------------------------------------------------------------------------------
 // Public Interface
@@ -24,12 +25,14 @@ class Plugins {
     /**
      * Creates the plugins context
      * @param {Environments} envContext - env context
-     * @param {function(string, Rule): void} defineRule - Callback for when a plugin is defined which introduces rules
+     * @param {function(string, Rule): void} options.defineRule - Callback for when a plugin is defined which introduces rules
+     * @param {string} options.pluginRootPath The path from which all plugins should be resolved
      */
-    constructor(envContext, defineRule) {
+    constructor(envContext, { defineRule, pluginRootPath }) {
         this._plugins = Object.create(null);
         this._environments = envContext;
         this._defineRule = defineRule;
+        this._pluginRootPath = pluginRootPath;
     }
 
     /**
@@ -73,6 +76,35 @@ class Plugins {
         return this._plugins;
     }
 
+    /**
+     * Resolves a plugin with the given name
+     * @param {string} pluginName The name of the plugin to resolve
+     * @param {string} pluginRootPath The absolute path to the directory where the plugin should be resolved from
+     * @returns {string} The full path to the plugin module
+     * @throws {Error} An templated error with debugging information if the plugin cannot be loaded.
+     */
+    static resolve(pluginName, pluginRootPath) {
+        const longName = naming.normalizePackageName(pluginName, "eslint-plugin");
+        const pathToResolveRelativeTo = path.join(pluginRootPath, "__placeholder__.js");
+
+        try {
+            return relativeModuleResolver(longName, pathToResolveRelativeTo);
+        } catch (missingPluginErr) {
+
+            // If the plugin can't be resolved, display the missing plugin error (usually a config or install error)
+            debug(`Failed to load plugin ${longName} from ${pluginRootPath}.`);
+            missingPluginErr.message = `Failed to load plugin ${pluginName} from ${pluginRootPath}: ${missingPluginErr.message}`;
+            missingPluginErr.messageTemplate = "plugin-missing";
+            missingPluginErr.messageData = {
+                pluginName: longName,
+                pluginRootPath,
+                configStack: []
+            };
+
+            throw missingPluginErr;
+        }
+    }
+
     /**
      * Loads a plugin with the given name.
      * @param {string} pluginName The name of the plugin to load.
@@ -80,53 +112,27 @@ class Plugins {
      * @throws {Error} If the plugin cannot be loaded.
      */
     load(pluginName) {
-        const longName = naming.normalizePackageName(pluginName, "eslint-plugin");
-        const shortName = naming.getShorthandName(longName, "eslint-plugin");
-        let plugin = null;
-
         if (pluginName.match(/\s+/u)) {
             const whitespaceError = new Error(`Whitespace found in plugin name '${pluginName}'`);
 
             whitespaceError.messageTemplate = "whitespace-found";
-            whitespaceError.messageData = {
-                pluginName: longName
-            };
+            whitespaceError.messageData = { pluginName };
             throw whitespaceError;
         }
 
-        if (!this._plugins[shortName]) {
-            try {
-                plugin = require(longName);
-            } catch (pluginLoadErr) {
-                try {
-
-                    // Check whether the plugin exists
-                    require.resolve(longName);
-                } catch (missingPluginErr) {
-
-                    // If the plugin can't be resolved, display the missing plugin error (usually a config or install error)
-                    debug(`Failed to load plugin ${longName}.`);
-                    missingPluginErr.message = `Failed to load plugin ${pluginName}: ${missingPluginErr.message}`;
-                    missingPluginErr.messageTemplate = "plugin-missing";
-                    missingPluginErr.messageData = {
-                        pluginName: longName,
-                        eslintPath: path.resolve(__dirname, "../..")
-                    };
-                    throw missingPluginErr;
-                }
+        const longName = naming.normalizePackageName(pluginName, "eslint-plugin");
+        const shortName = naming.getShorthandName(longName, "eslint-plugin");
 
-                // Otherwise, the plugin exists and is throwing on module load for some reason, so print the stack trace.
-                throw pluginLoadErr;
-            }
+        if (!this._plugins[shortName]) {
+            const pluginPath = Plugins.resolve(shortName, this._pluginRootPath);
+            const plugin = require(pluginPath);
 
             // This step is costly, so skip if debug is disabled
             if (debug.enabled) {
-                const resolvedPath = require.resolve(longName);
-
                 let version = null;
 
                 try {
-                    version = require(`${longName}/package.json`).version;
+                    version = require(relativeModuleResolver(`${longName}/package.json`, this._pluginRootPath)).version;
                 } catch (e) {
 
                     // Do nothing
@@ -136,7 +142,7 @@ class Plugins {
                     ? `${longName}@${version}`
                     : `${longName}, version unknown`;
 
-                debug(`Loaded plugin ${pluginName} (${loadedPluginAndVersion}) (from ${resolvedPath})`);
+                debug(`Loaded plugin ${pluginName} (${loadedPluginAndVersion}) (from ${pluginPath})`);
             }
 
             this.define(pluginName, plugin);
diff --git a/lib/linter.js b/lib/linter.js
index be38b9908..bd3d8b235 100644
--- a/lib/linter.js
+++ b/lib/linter.js
@@ -73,8 +73,13 @@ const commentParser = new ConfigCommentParser();
 function addDeclaredGlobals(globalScope, configGlobals, commentDirectives) {
     const mergedGlobalsInfo = Object.assign(
         {},
+
+        /*
+         * `ConfigOps.normalizeConfigGlobal` will throw an error if a configured global value is invalid. However, these errors would
+         * typically be caught when validating a config anyway (validity for inline global comments is checked separately).
+         */
         lodash.mapValues(configGlobals, value => ({ sourceComment: null, value: ConfigOps.normalizeConfigGlobal(value) })),
-        lodash.mapValues(commentDirectives.enabledGlobals, ({ comment, value }) => ({ sourceComment: comment, value: ConfigOps.normalizeConfigGlobal(value) }))
+        lodash.mapValues(commentDirectives.enabledGlobals, ({ comment, value }) => ({ sourceComment: comment, value }))
     );
 
     Object.keys(mergedGlobalsInfo)
@@ -196,10 +201,33 @@ function getDirectiveComments(filename, ast, ruleMapper) {
                     break;
 
                 case "globals":
-                case "global":
-                    Object.assign(enabledGlobals, commentParser.parseStringConfig(directiveValue, comment));
-                    break;
+                case "global": {
+                    const updatedGlobals = commentParser.parseStringConfig(directiveValue, comment);
+
+                    Object.keys(updatedGlobals).forEach(globalName => {
+                        const { value } = updatedGlobals[globalName];
+                        let normalizedValue;
+
+                        try {
+                            normalizedValue = ConfigOps.normalizeConfigGlobal(value);
+                        } catch (err) {
+                            problems.push({
+                                ruleId: null,
+                                severity: 2,
+                                message: err.message,
+                                line: comment.loc.start.line,
+                                column: comment.loc.start.column + 1,
+                                endLine: comment.loc.end.line,
+                                endColumn: comment.loc.end.column + 1,
+                                nodeType: null
+                            });
+                            return;
+                        }
 
+                        enabledGlobals[globalName] = { comment, value: normalizedValue };
+                    });
+                    break;
+                }
                 case "eslint-disable":
                     disableDirectives.push(...createDisableDirectives("disable", comment.loc.start, directiveValue));
                     break;
@@ -336,16 +364,6 @@ function resolveParserOptions(parserName, providedOptions, enabledEnvironments)
 
     mergedParserOptions.ecmaVersion = normalizeEcmaVersion(mergedParserOptions.ecmaVersion, isModule);
 
-    // TODO: For backward compatibility. Will remove on v6.0.0.
-    if (
-        parserName === DEFAULT_PARSER_NAME &&
-        mergedParserOptions.ecmaFeatures &&
-        mergedParserOptions.ecmaFeatures.experimentalObjectRestSpread &&
-        (!mergedParserOptions.ecmaVersion || mergedParserOptions.ecmaVersion < 9)
-    ) {
-        mergedParserOptions.ecmaVersion = 9;
-    }
-
     return mergedParserOptions;
 }
 
@@ -445,24 +463,22 @@ function parse(text, providedParserOptions, parserName, parserMap, filePath) {
         filePath
     });
 
-    let parser;
-
-    try {
-        parser = parserMap.get(parserName) || require(parserName);
-    } catch (ex) {
+    if (!parserMap.has(parserName)) {
         return {
             success: false,
             error: {
                 ruleId: null,
                 fatal: true,
                 severity: 2,
-                message: ex.message,
+                message: `Configured parser '${parserName}' was not found.`,
                 line: 0,
                 column: 0
             }
         };
     }
 
+    const parser = parserMap.get(parserName);
+
     /*
      * Check for parsing errors first. If there's a parsing error, nothing
      * else can happen. However, a parsing error does not throw an error
diff --git a/lib/rules/camelcase.js b/lib/rules/camelcase.js
index 4647cc17e..cdc16fab9 100644
--- a/lib/rules/camelcase.js
+++ b/lib/rules/camelcase.js
@@ -90,7 +90,7 @@ module.exports = {
          */
         function isAllowed(name) {
             return allow.findIndex(
-                entry => name === entry || name.match(new RegExp(entry)) // eslint-disable-line require-unicode-regexp
+                entry => name === entry || name.match(new RegExp(entry, "u"))
             ) !== -1;
         }
 
diff --git a/lib/rules/capitalized-comments.js b/lib/rules/capitalized-comments.js
index 137be9e93..036616b33 100644
--- a/lib/rules/capitalized-comments.js
+++ b/lib/rules/capitalized-comments.js
@@ -91,7 +91,7 @@ function createRegExpForIgnorePatterns(normalizedOptions) {
         const ignorePatternStr = normalizedOptions[key].ignorePattern;
 
         if (ignorePatternStr) {
-            const regExp = RegExp(`^\\s*(?:${ignorePatternStr})`); // eslint-disable-line require-unicode-regexp
+            const regExp = RegExp(`^\\s*(?:${ignorePatternStr})`, "u");
 
             normalizedOptions[key].ignorePatternRegExp = regExp;
         }
diff --git a/lib/rules/comma-dangle.js b/lib/rules/comma-dangle.js
index 96799b307..379f106bc 100644
--- a/lib/rules/comma-dangle.js
+++ b/lib/rules/comma-dangle.js
@@ -21,7 +21,7 @@ const DEFAULT_OPTIONS = Object.freeze({
     objects: "never",
     imports: "never",
     exports: "never",
-    functions: "ignore"
+    functions: "never"
 });
 
 /**
diff --git a/lib/rules/default-case.js b/lib/rules/default-case.js
index c8d0af392..821e0d72b 100644
--- a/lib/rules/default-case.js
+++ b/lib/rules/default-case.js
@@ -39,7 +39,7 @@ module.exports = {
     create(context) {
         const options = context.options[0] || {};
         const commentPattern = options.commentPattern
-            ? new RegExp(options.commentPattern) // eslint-disable-line require-unicode-regexp
+            ? new RegExp(options.commentPattern, "u")
             : DEFAULT_COMMENT_PATTERN;
 
         const sourceCode = context.getSourceCode();
diff --git a/lib/rules/dot-notation.js b/lib/rules/dot-notation.js
index e25ab12b7..883c9090f 100644
--- a/lib/rules/dot-notation.js
+++ b/lib/rules/dot-notation.js
@@ -61,7 +61,7 @@ module.exports = {
         let allowPattern;
 
         if (options.allowPattern) {
-            allowPattern = new RegExp(options.allowPattern); // eslint-disable-line require-unicode-regexp
+            allowPattern = new RegExp(options.allowPattern, "u");
         }
 
         /**
diff --git a/lib/rules/function-paren-newline.js b/lib/rules/function-paren-newline.js
index 37256484f..c4775fb19 100644
--- a/lib/rules/function-paren-newline.js
+++ b/lib/rules/function-paren-newline.js
@@ -31,7 +31,7 @@ module.exports = {
             {
                 oneOf: [
                     {
-                        enum: ["always", "never", "consistent", "multiline"]
+                        enum: ["always", "never", "consistent", "multiline", "multiline-arguments"]
                     },
                     {
                         type: "object",
@@ -50,6 +50,7 @@ module.exports = {
         messages: {
             expectedBefore: "Expected newline before ')'.",
             expectedAfter: "Expected newline after '('.",
+            expectedBetween: "Expected newline between arguments/params.",
             unexpectedBefore: "Unexpected newline before '('.",
             unexpectedAfter: "Unexpected newline after ')'."
         }
@@ -59,6 +60,7 @@ module.exports = {
         const sourceCode = context.getSourceCode();
         const rawOption = context.options[0] || "multiline";
         const multilineOption = rawOption === "multiline";
+        const multilineArgumentsOption = rawOption === "multiline-arguments";
         const consistentOption = rawOption === "consistent";
         let minItems;
 
@@ -83,7 +85,10 @@ module.exports = {
          * @returns {boolean} `true` if there should be newlines inside the function parens
          */
         function shouldHaveNewlines(elements, hasLeftNewline) {
-            if (multilineOption) {
+            if (multilineArgumentsOption && elements.length === 1) {
+                return hasLeftNewline;
+            }
+            if (multilineOption || multilineArgumentsOption) {
                 return elements.some((element, index) => index !== elements.length - 1 && element.loc.end.line !== elements[index + 1].loc.start.line);
             }
             if (consistentOption) {
@@ -93,7 +98,7 @@ module.exports = {
         }
 
         /**
-         * Validates a list of arguments or parameters
+         * Validates parens
          * @param {Object} parens An object with keys `leftParen` for the left paren token, and `rightParen` for the right paren token
          * @param {ASTNode[]} elements The arguments or parameters in the list
          * @returns {void}
@@ -148,6 +153,33 @@ module.exports = {
             }
         }
 
+        /**
+         * Validates a list of arguments or parameters
+         * @param {Object} parens An object with keys `leftParen` for the left paren token, and `rightParen` for the right paren token
+         * @param {ASTNode[]} elements The arguments or parameters in the list
+         * @returns {void}
+         */
+        function validateArguments(parens, elements) {
+            const leftParen = parens.leftParen;
+            const tokenAfterLeftParen = sourceCode.getTokenAfter(leftParen);
+            const hasLeftNewline = !astUtils.isTokenOnSameLine(leftParen, tokenAfterLeftParen);
+            const needsNewlines = shouldHaveNewlines(elements, hasLeftNewline);
+
+            for (let i = 0; i <= elements.length - 2; i++) {
+                const currentElement = elements[i];
+                const nextElement = elements[i + 1];
+                const hasNewLine = currentElement.loc.end.line !== nextElement.loc.start.line;
+
+                if (!hasNewLine && needsNewlines) {
+                    context.report({
+                        node: currentElement,
+                        messageId: "expectedBetween",
+                        fix: fixer => fixer.insertTextBefore(nextElement, "\n")
+                    });
+                }
+            }
+        }
+
         /**
          * Gets the left paren and right paren tokens of a node.
          * @param {ASTNode} node The node with parens
@@ -215,6 +247,10 @@ module.exports = {
 
             if (parens) {
                 validateParens(parens, astUtils.isFunction(node) ? node.params : node.arguments);
+
+                if (multilineArgumentsOption) {
+                    validateArguments(parens, astUtils.isFunction(node) ? node.params : node.arguments);
+                }
             }
         }
 
diff --git a/lib/rules/handle-callback-err.js b/lib/rules/handle-callback-err.js
index 4d08e4fec..640946699 100644
--- a/lib/rules/handle-callback-err.js
+++ b/lib/rules/handle-callback-err.js
@@ -52,7 +52,7 @@ module.exports = {
          */
         function matchesConfiguredErrorName(name) {
             if (isPattern(errorArgument)) {
-                const regexp = new RegExp(errorArgument); // eslint-disable-line require-unicode-regexp
+                const regexp = new RegExp(errorArgument, "u");
 
                 return regexp.test(name);
             }
diff --git a/lib/rules/id-match.js b/lib/rules/id-match.js
index 48c108767..b97a497fd 100644
--- a/lib/rules/id-match.js
+++ b/lib/rules/id-match.js
@@ -53,7 +53,7 @@ module.exports = {
         // Options
         //--------------------------------------------------------------------------
         const pattern = context.options[0] || "^.+$",
-            regexp = new RegExp(pattern); // eslint-disable-line require-unicode-regexp
+            regexp = new RegExp(pattern, "u");
 
         const options = context.options[1] || {},
             properties = !!options.properties,
diff --git a/lib/rules/line-comment-position.js b/lib/rules/line-comment-position.js
index 0023a1f11..d2af56e8a 100644
--- a/lib/rules/line-comment-position.js
+++ b/lib/rules/line-comment-position.js
@@ -31,8 +31,7 @@ module.exports = {
                         type: "object",
                         properties: {
                             position: {
-                                enum: ["above", "beside"],
-                                default: "above"
+                                enum: ["above", "beside"]
                             },
                             ignorePattern: {
                                 type: "string"
@@ -78,7 +77,7 @@ module.exports = {
 
         const defaultIgnoreRegExp = astUtils.COMMENTS_IGNORE_PATTERN;
         const fallThroughRegExp = /^\s*falls?\s?through/u;
-        const customIgnoreRegExp = new RegExp(ignorePattern); // eslint-disable-line require-unicode-regexp
+        const customIgnoreRegExp = new RegExp(ignorePattern, "u");
         const sourceCode = context.getSourceCode();
 
         //--------------------------------------------------------------------------
diff --git a/lib/rules/lines-around-comment.js b/lib/rules/lines-around-comment.js
index f2f9e3f9d..9d6ce4616 100644
--- a/lib/rules/lines-around-comment.js
+++ b/lib/rules/lines-around-comment.js
@@ -130,7 +130,7 @@ module.exports = {
         const options = Object.assign({}, context.options[0]);
         const ignorePattern = options.ignorePattern;
         const defaultIgnoreRegExp = astUtils.COMMENTS_IGNORE_PATTERN;
-        const customIgnoreRegExp = new RegExp(ignorePattern); // eslint-disable-line require-unicode-regexp
+        const customIgnoreRegExp = new RegExp(ignorePattern, "u");
         const applyDefaultIgnorePatterns = options.applyDefaultIgnorePatterns !== false;
 
         options.beforeBlockComment = typeof options.beforeBlockComment !== "undefined" ? options.beforeBlockComment : true;
diff --git a/lib/rules/max-len.js b/lib/rules/max-len.js
index 6e7dd6f1a..24ceeffb3 100644
--- a/lib/rules/max-len.js
+++ b/lib/rules/max-len.js
@@ -14,8 +14,7 @@ const OPTIONS_SCHEMA = {
     properties: {
         code: {
             type: "integer",
-            minimum: 0,
-            default: 80
+            minimum: 0
         },
         comments: {
             type: "integer",
@@ -23,35 +22,28 @@ const OPTIONS_SCHEMA = {
         },
         tabWidth: {
             type: "integer",
-            minimum: 0,
-            default: 4
+            minimum: 0
         },
         ignorePattern: {
             type: "string"
         },
         ignoreComments: {
-            type: "boolean",
-            default: false
+            type: "boolean"
         },
         ignoreStrings: {
-            type: "boolean",
-            default: false
+            type: "boolean"
         },
         ignoreUrls: {
-            type: "boolean",
-            default: false
+            type: "boolean"
         },
         ignoreTemplateLiterals: {
-            type: "boolean",
-            default: false
+            type: "boolean"
         },
         ignoreRegExpLiterals: {
-            type: "boolean",
-            default: false
+            type: "boolean"
         },
         ignoreTrailingComments: {
-            type: "boolean",
-            default: false
+            type: "boolean"
         }
     },
     additionalProperties: false
@@ -141,19 +133,19 @@ module.exports = {
             options.tabWidth = context.options[1];
         }
 
-        const maxLength = options.code || 80,
-            tabWidth = options.tabWidth || 4,
-            ignoreComments = options.ignoreComments || false,
-            ignoreStrings = options.ignoreStrings || false,
-            ignoreTemplateLiterals = options.ignoreTemplateLiterals || false,
-            ignoreRegExpLiterals = options.ignoreRegExpLiterals || false,
-            ignoreTrailingComments = options.ignoreTrailingComments || options.ignoreComments || false,
-            ignoreUrls = options.ignoreUrls || false,
+        const maxLength = typeof options.code === "number" ? options.code : 80,
+            tabWidth = typeof options.tabWidth === "number" ? options.tabWidth : 4,
+            ignoreComments = !!options.ignoreComments,
+            ignoreStrings = !!options.ignoreStrings,
+            ignoreTemplateLiterals = !!options.ignoreTemplateLiterals,
+            ignoreRegExpLiterals = !!options.ignoreRegExpLiterals,
+            ignoreTrailingComments = !!options.ignoreTrailingComments || !!options.ignoreComments,
+            ignoreUrls = !!options.ignoreUrls,
             maxCommentLength = options.comments;
         let ignorePattern = options.ignorePattern || null;
 
         if (ignorePattern) {
-            ignorePattern = new RegExp(ignorePattern); // eslint-disable-line require-unicode-regexp
+            ignorePattern = new RegExp(ignorePattern, "u");
         }
 
         //--------------------------------------------------------------------------
diff --git a/lib/rules/max-lines-per-function.js b/lib/rules/max-lines-per-function.js
index 3b2f656fb..5abb3f0b8 100644
--- a/lib/rules/max-lines-per-function.js
+++ b/lib/rules/max-lines-per-function.js
@@ -19,20 +19,16 @@ const OPTIONS_SCHEMA = {
     properties: {
         max: {
             type: "integer",
-            minimum: 0,
-            default: 50
+            minimum: 0
         },
         skipComments: {
-            type: "boolean",
-            default: false
+            type: "boolean"
         },
         skipBlankLines: {
-            type: "boolean",
-            default: false
+            type: "boolean"
         },
         IIFEs: {
-            type: "boolean",
-            default: false
+            type: "boolean"
         }
     },
     additionalProperties: false
@@ -101,10 +97,10 @@ module.exports = {
         let IIFEs = false;
 
         if (typeof option === "object") {
-            maxLines = option.max;
-            skipComments = option.skipComments;
-            skipBlankLines = option.skipBlankLines;
-            IIFEs = option.IIFEs;
+            maxLines = typeof option.max === "number" ? option.max : 50;
+            skipComments = !!option.skipComments;
+            skipBlankLines = !!option.skipBlankLines;
+            IIFEs = !!option.IIFEs;
         } else if (typeof option === "number") {
             maxLines = option;
         }
diff --git a/lib/rules/new-cap.js b/lib/rules/new-cap.js
index 9e88981ce..dcae238d9 100644
--- a/lib/rules/new-cap.js
+++ b/lib/rules/new-cap.js
@@ -136,10 +136,10 @@ module.exports = {
         const skipProperties = config.properties === false;
 
         const newIsCapExceptions = checkArray(config, "newIsCapExceptions", []).reduce(invert, {});
-        const newIsCapExceptionPattern = config.newIsCapExceptionPattern ? new RegExp(config.newIsCapExceptionPattern) : null; // eslint-disable-line require-unicode-regexp
+        const newIsCapExceptionPattern = config.newIsCapExceptionPattern ? new RegExp(config.newIsCapExceptionPattern, "u") : null;
 
         const capIsNewExceptions = calculateCapIsNewExceptions(config);
-        const capIsNewExceptionPattern = config.capIsNewExceptionPattern ? new RegExp(config.capIsNewExceptionPattern) : null; // eslint-disable-line require-unicode-regexp
+        const capIsNewExceptionPattern = config.capIsNewExceptionPattern ? new RegExp(config.capIsNewExceptionPattern, "u") : null;
 
         const listeners = {};
 
diff --git a/lib/rules/no-confusing-arrow.js b/lib/rules/no-confusing-arrow.js
index 79df9a513..09d6e27d2 100644
--- a/lib/rules/no-confusing-arrow.js
+++ b/lib/rules/no-confusing-arrow.js
@@ -41,7 +41,7 @@ module.exports = {
         schema: [{
             type: "object",
             properties: {
-                allowParens: { type: "boolean", default: false }
+                allowParens: { type: "boolean", default: true }
             },
             additionalProperties: false
         }],
@@ -53,8 +53,10 @@ module.exports = {
 
     create(context) {
         const config = context.options[0] || {};
+        const allowParens = config.allowParens || (config.allowParens === void 0);
         const sourceCode = context.getSourceCode();
 
+
         /**
          * Reports if an arrow function contains an ambiguous conditional.
          * @param {ASTNode} node - A node to check and report.
@@ -63,14 +65,14 @@ module.exports = {
         function checkArrowFunc(node) {
             const body = node.body;
 
-            if (isConditional(body) && !(config.allowParens && astUtils.isParenthesised(sourceCode, body))) {
+            if (isConditional(body) && !(allowParens && astUtils.isParenthesised(sourceCode, body))) {
                 context.report({
                     node,
                     messageId: "confusing",
                     fix(fixer) {
 
                         // if `allowParens` is not set to true dont bother wrapping in parens
-                        return config.allowParens && fixer.replaceText(node.body, `(${sourceCode.getText(node.body)})`);
+                        return allowParens && fixer.replaceText(node.body, `(${sourceCode.getText(node.body)})`);
                     }
                 });
             }
diff --git a/lib/rules/no-fallthrough.js b/lib/rules/no-fallthrough.js
index 241e07258..c6a71b6c8 100644
--- a/lib/rules/no-fallthrough.js
+++ b/lib/rules/no-fallthrough.js
@@ -95,7 +95,7 @@ module.exports = {
         let fallthroughCommentPattern = null;
 
         if (options.commentPattern) {
-            fallthroughCommentPattern = new RegExp(options.commentPattern); // eslint-disable-line require-unicode-regexp
+            fallthroughCommentPattern = new RegExp(options.commentPattern, "u");
         } else {
             fallthroughCommentPattern = DEFAULT_FALLTHROUGH_COMMENT;
         }
diff --git a/lib/rules/no-unused-vars.js b/lib/rules/no-unused-vars.js
index d4c4c6355..1482a0238 100644
--- a/lib/rules/no-unused-vars.js
+++ b/lib/rules/no-unused-vars.js
@@ -88,15 +88,15 @@ module.exports = {
                 config.caughtErrors = firstOption.caughtErrors || config.caughtErrors;
 
                 if (firstOption.varsIgnorePattern) {
-                    config.varsIgnorePattern = new RegExp(firstOption.varsIgnorePattern); // eslint-disable-line require-unicode-regexp
+                    config.varsIgnorePattern = new RegExp(firstOption.varsIgnorePattern, "u");
                 }
 
                 if (firstOption.argsIgnorePattern) {
-                    config.argsIgnorePattern = new RegExp(firstOption.argsIgnorePattern); // eslint-disable-line require-unicode-regexp
+                    config.argsIgnorePattern = new RegExp(firstOption.argsIgnorePattern, "u");
                 }
 
                 if (firstOption.caughtErrorsIgnorePattern) {
-                    config.caughtErrorsIgnorePattern = new RegExp(firstOption.caughtErrorsIgnorePattern); // eslint-disable-line require-unicode-regexp
+                    config.caughtErrorsIgnorePattern = new RegExp(firstOption.caughtErrorsIgnorePattern, "u");
                 }
             }
         }
diff --git a/lib/rules/no-warning-comments.js b/lib/rules/no-warning-comments.js
index 1d1b55460..0882f6126 100644
--- a/lib/rules/no-warning-comments.js
+++ b/lib/rules/no-warning-comments.js
@@ -5,6 +5,7 @@
 
 "use strict";
 
+const { escapeRegExp } = require("lodash");
 const astUtils = require("../util/ast-utils");
 
 //------------------------------------------------------------------------------
@@ -58,7 +59,7 @@ module.exports = {
          * @returns {RegExp} The term converted to a RegExp
          */
         function convertToRegExp(term) {
-            const escaped = term.replace(/[-/\\$^*+?.()|[\]{}]/gu, "\\$&");
+            const escaped = escapeRegExp(term);
             const wordBoundary = "\\b";
             const eitherOrWordBoundary = `|${wordBoundary}`;
             let prefix;
@@ -95,7 +96,7 @@ module.exports = {
                  * ^\s*TERM\b.  This checks the word boundary
                  * at the beginning of the comment.
                  */
-                return new RegExp(prefix + escaped + suffix, "i"); // eslint-disable-line require-unicode-regexp
+                return new RegExp(prefix + escaped + suffix, "iu");
             }
 
             /*
@@ -103,7 +104,7 @@ module.exports = {
              * \bTERM\b|\bTERM\b, this checks the entire comment
              * for the term.
              */
-            return new RegExp(prefix + escaped + suffix + eitherOrWordBoundary + term + wordBoundary, "i"); // eslint-disable-line require-unicode-regexp
+            return new RegExp(prefix + escaped + suffix + eitherOrWordBoundary + term + wordBoundary, "iu");
         }
 
         const warningRegExps = warningTerms.map(convertToRegExp);
diff --git a/lib/rules/one-var.js b/lib/rules/one-var.js
index 0571cd338..e79461ce4 100644
--- a/lib/rules/one-var.js
+++ b/lib/rules/one-var.js
@@ -32,8 +32,7 @@ module.exports = {
                         type: "object",
                         properties: {
                             separateRequires: {
-                                type: "boolean",
-                                default: false
+                                type: "boolean"
                             },
                             var: {
                                 enum: ["always", "never", "consecutive"]
@@ -77,7 +76,7 @@ module.exports = {
             options.let = { uninitialized: mode, initialized: mode };
             options.const = { uninitialized: mode, initialized: mode };
         } else if (typeof mode === "object") { // options configuration is an object
-            options.separateRequires = mode.separateRequires;
+            options.separateRequires = !!mode.separateRequires;
             options.var = { uninitialized: mode.var, initialized: mode.var };
             options.let = { uninitialized: mode.let, initialized: mode.let };
             options.const = { uninitialized: mode.const, initialized: mode.const };
diff --git a/lib/rules/spaced-comment.js b/lib/rules/spaced-comment.js
index 6e469ef94..e6b6e1b65 100644
--- a/lib/rules/spaced-comment.js
+++ b/lib/rules/spaced-comment.js
@@ -126,7 +126,7 @@ function createAlwaysStylePattern(markers, exceptions) {
     pattern += "?"; // or nothing.
     pattern += createExceptionsPattern(exceptions);
 
-    return new RegExp(pattern); // eslint-disable-line require-unicode-regexp
+    return new RegExp(pattern, "u");
 }
 
 /**
@@ -142,7 +142,7 @@ function createAlwaysStylePattern(markers, exceptions) {
 function createNeverStylePattern(markers) {
     const pattern = `^(${markers.map(escape).join("|")})?[ \t]+`;
 
-    return new RegExp(pattern); // eslint-disable-line require-unicode-regexp
+    return new RegExp(pattern, "u");
 }
 
 //------------------------------------------------------------------------------
@@ -250,9 +250,9 @@ module.exports = {
             // Create RegExp object for valid patterns.
             rule[type] = {
                 beginRegex: requireSpace ? createAlwaysStylePattern(markers, exceptions) : createNeverStylePattern(markers),
-                endRegex: balanced && requireSpace ? new RegExp(`${createExceptionsPattern(exceptions)}$`) : new RegExp(endNeverPattern), // eslint-disable-line require-unicode-regexp
+                endRegex: balanced && requireSpace ? new RegExp(`${createExceptionsPattern(exceptions)}$`, "u") : new RegExp(endNeverPattern, "u"),
                 hasExceptions: exceptions.length > 0,
-                markers: new RegExp(`^(${markers.map(escape).join("|")})`) // eslint-disable-line require-unicode-regexp
+                markers: new RegExp(`^(${markers.map(escape).join("|")})`, "u")
             };
 
             return rule;
diff --git a/lib/rules/valid-jsdoc.js b/lib/rules/valid-jsdoc.js
index 3250d9233..9ec6938e3 100644
--- a/lib/rules/valid-jsdoc.js
+++ b/lib/rules/valid-jsdoc.js
@@ -482,7 +482,7 @@ module.exports = {
                 }
 
                 if (options.matchDescription) {
-                    const regex = new RegExp(options.matchDescription); // eslint-disable-line require-unicode-regexp
+                    const regex = new RegExp(options.matchDescription, "u");
 
                     if (!regex.test(jsdoc.description)) {
                         context.report({ node: jsdocNode, messageId: "unsatisfiedDesc" });
diff --git a/lib/util/ajv.js b/lib/util/ajv.js
index ecf0ebd17..3fb0fbdd3 100644
--- a/lib/util/ajv.js
+++ b/lib/util/ajv.js
@@ -15,17 +15,20 @@ const Ajv = require("ajv"),
 // Public Interface
 //------------------------------------------------------------------------------
 
-const ajv = new Ajv({
-    meta: false,
-    useDefaults: true,
-    validateSchema: false,
-    missingRefs: "ignore",
-    verbose: true,
-    schemaId: "auto"
-});
+module.exports = (additionalOptions = {}) => {
+    const ajv = new Ajv({
+        meta: false,
+        useDefaults: true,
+        validateSchema: false,
+        missingRefs: "ignore",
+        verbose: true,
+        schemaId: "auto",
+        ...additionalOptions
+    });
 
-ajv.addMetaSchema(metaSchema);
-// eslint-disable-next-line no-underscore-dangle
-ajv._opts.defaultMeta = metaSchema.id;
+    ajv.addMetaSchema(metaSchema);
+    // eslint-disable-next-line no-underscore-dangle
+    ajv._opts.defaultMeta = metaSchema.id;
 
-module.exports = ajv;
+    return ajv;
+};
diff --git a/lib/util/module-resolver.js b/lib/util/module-resolver.js
deleted file mode 100644
index 1e9b66330..000000000
--- a/lib/util/module-resolver.js
+++ /dev/null
@@ -1,83 +0,0 @@
-/**
- * @fileoverview Implements the Node.js require.resolve algorithm
- * @author Nicholas C. Zakas
- */
-
-"use strict";
-
-//------------------------------------------------------------------------------
-// Requirements
-//------------------------------------------------------------------------------
-
-const Module = require("module");
-
-//------------------------------------------------------------------------------
-// Private
-//------------------------------------------------------------------------------
-
-const DEFAULT_OPTIONS = {
-
-    /*
-     * module.paths is an array of paths to search for resolving things relative
-     * to this file. Module.globalPaths contains all of the special Node.js
-     * directories that can also be searched for modules.
-     *
-     * Need to check for existence of module.paths because Jest seems not to
-     * include it. See https://github.com/eslint/eslint/issues/5791.
-     */
-    lookupPaths: module.paths ? module.paths.concat(Module.globalPaths) : Module.globalPaths.concat()
-};
-
-/**
- * Resolves modules based on a set of options.
- */
-class ModuleResolver {
-
-    /**
-     * Resolves modules based on a set of options.
-     * @param {Object} options The options for resolving modules.
-     * @param {string[]} options.lookupPaths An array of paths to include in the
-     *      lookup with the highest priority paths coming first.
-     */
-    constructor(options) {
-        this.options = Object.assign({}, DEFAULT_OPTIONS, options || {});
-    }
-
-    /**
-     * Resolves the file location of a given module relative to the configured
-     * lookup paths.
-     * @param {string} name The module name to resolve.
-     * @param {string} extraLookupPath An extra path to look into for the module.
-     *      This path is used with the highest priority.
-     * @returns {string} The resolved file path for the module.
-     * @throws {Error} If the module cannot be resolved.
-     */
-    resolve(name, extraLookupPath) {
-
-        /*
-         * First, clone the lookup paths so we're not messing things up for
-         * subsequent calls to this function. Then, move the extraLookupPath to the
-         * top of the lookup paths list so it will be searched first.
-         */
-        const lookupPaths = [extraLookupPath, ...this.options.lookupPaths];
-
-        /**
-         * Module._findPath is an internal method to Node.js, then one they use to
-         * 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
-
-        if (!result) {
-            throw new Error(`Cannot find module '${name}'`);
-        }
-
-        return result;
-    }
-}
-
-//------------------------------------------------------------------------------
-// Public API
-//------------------------------------------------------------------------------
-
-module.exports = ModuleResolver;
diff --git a/lib/util/relative-module-resolver.js b/lib/util/relative-module-resolver.js
new file mode 100644
index 000000000..9e86909c5
--- /dev/null
+++ b/lib/util/relative-module-resolver.js
@@ -0,0 +1,30 @@
+/**
+ * Utility for resolving a module relative to another module
+ * @author Teddy Katz
+ */
+
+"use strict";
+
+const Module = require("module");
+const path = require("path");
+
+// Polyfill Node's `Module.createRequireFromPath` if not present (added in Node v10.12.0)
+const createRequireFromPath = Module.createRequireFromPath || (filename => {
+    const mod = new Module(filename, null);
+
+    mod.filename = filename;
+    mod.paths = Module._nodeModulePaths(path.dirname(filename)); // eslint-disable-line no-underscore-dangle
+    mod._compile("module.exports = require;", filename); // eslint-disable-line no-underscore-dangle
+    return mod.exports;
+});
+
+/**
+ * Resolves a Node module relative to another module
+ * @param {string} moduleName The name of a Node module, or a path to a Node module.
+ *
+ * @param {string} relativeToPath An absolute path indicating the module that `moduleName` should be resolved relative to. This must be
+ * a file rather than a directory, but the file need not actually exist.
+ * @returns {string} The absolute path that would result from calling `require.resolve(moduleName)` in a file located at `relativeToPath`
+ */
+module.exports = (moduleName, relativeToPath) =>
+    createRequireFromPath(relativeToPath).resolve(moduleName);
diff --git a/messages/plugin-missing.txt b/messages/plugin-missing.txt
index 766020b2b..aa9a298bf 100644
--- a/messages/plugin-missing.txt
+++ b/messages/plugin-missing.txt
@@ -1,11 +1,11 @@
-ESLint couldn't find the plugin "<%- pluginName %>". This can happen for a couple different reasons:
+ESLint couldn't find the plugin "<%- pluginName %>".
 
-1. If ESLint is installed globally, then make sure <%- pluginName %> is also installed globally. A globally-installed ESLint cannot find a locally-installed plugin.
+(The package "<%- pluginName %>" was not found when loaded as a Node module from the directory "<%- pluginRootPath %>".)
 
-2. If ESLint is installed locally, then it's likely that the plugin isn't installed correctly. Try reinstalling by running the following:
+It's likely that the plugin isn't installed correctly. Try reinstalling by running the following:
 
-    npm i <%- pluginName %>@latest --save-dev
+    npm install <%- pluginName %>@latest --save-dev
 
-Path to ESLint package: <%- eslintPath %>
+The plugin "<%- pluginName %>" was referenced from the config file in <%- configStack.join("\n\t...loaded in ") %>.
 
 If you still can't figure out the problem, please stop by https://gitter.im/eslint/eslint to chat with the team.
diff --git a/package.json b/package.json
index c2c315d69..d15aa4d1f 100644
--- a/package.json
+++ b/package.json
@@ -36,7 +36,7 @@
   "bugs": "https://github.com/eslint/eslint/issues/",
   "dependencies": {
     "@babel/code-frame": "^7.0.0",
-    "ajv": "^6.9.1",
+    "ajv": "^6.10.0",
     "chalk": "^2.1.0",
     "cross-spawn": "^6.0.5",
     "debug": "^4.0.1",
@@ -63,7 +63,6 @@
     "mkdirp": "^0.5.1",
     "natural-compare": "^1.4.0",
     "optionator": "^0.8.2",
-    "path-is-inside": "^1.0.2",
     "progress": "^2.0.0",
     "regexpp": "^2.0.1",
     "semver": "^5.5.1",
@@ -93,7 +92,7 @@
     "eslump": "^2.0.0",
     "esprima": "^4.0.1",
     "jsdoc": "^3.5.5",
-    "karma": "^3.1.4",
+    "karma": "^4.0.1",
     "karma-chrome-launcher": "^2.2.0",
     "karma-mocha": "^1.3.0",
     "karma-mocha-reporter": "^2.2.3",
@@ -123,6 +122,6 @@
   ],
   "license": "MIT",
   "engines": {
-    "node": "^6.14.0 || ^8.10.0 || >=9.10.0"
+    "node": "^8.10.0 || ^10.13.0 || >=11.10.1"
   }
 }
diff --git a/packages/eslint-config-eslint/default.yml b/packages/eslint-config-eslint/default.yml
index cad812907..0cfe3506b 100644
--- a/packages/eslint-config-eslint/default.yml
+++ b/packages/eslint-config-eslint/default.yml
@@ -13,7 +13,7 @@ rules:
     camelcase: "error"
     callback-return: ["error", ["cb", "callback", "next"]]
     class-methods-use-this: "error"
-    comma-dangle: ["error", { functions: "never" }]
+    comma-dangle: "error"
     comma-spacing: "error"
     comma-style: ["error", "last"]
     computed-property-spacing: "error"


Test patch:
diff --git a/lib/testers/rule-tester.js b/lib/testers/rule-tester.js
index f1d915531..2cad541ce 100644
--- a/lib/testers/rule-tester.js
+++ b/lib/testers/rule-tester.js
@@ -42,14 +42,16 @@
 
 const lodash = require("lodash"),
     assert = require("assert"),
+    path = require("path"),
     util = require("util"),
     validator = require("../config/config-validator"),
-    ajv = require("../util/ajv"),
     Linter = require("../linter"),
     Environments = require("../config/environments"),
     SourceCodeFixer = require("../util/source-code-fixer"),
     interpolate = require("../util/interpolate");
 
+const ajv = require("../util/ajv")({ strictDefaults: true });
+
 //------------------------------------------------------------------------------
 // Private Members
 //------------------------------------------------------------------------------
@@ -365,6 +367,11 @@ class RuleTester {
                 }
             }));
 
+            if (typeof config.parser === "string") {
+                assert(path.isAbsolute(config.parser), "Parsers provided as strings to RuleTester must be absolute paths");
+                linter.defineParser(config.parser, require(config.parser));
+            }
+
             if (schema) {
                 ajv.validateSchema(schema);
 
@@ -377,6 +384,18 @@ class RuleTester {
 
                     throw new Error([`Schema for rule ${ruleName} is invalid:`, errors]);
                 }
+
+                /*
+                 * `ajv.validateSchema` checks for errors in the structure of the schema (by comparing the schema against a "meta-schema"),
+                 * and it reports those errors individually. However, there are other types of schema errors that only occur when compiling
+                 * the schema (e.g. using invalid defaults in a schema), and only one of these errors can be reported at a time. As a result,
+                 * the schema is compiled here separately from checking for `validateSchema` errors.
+                 */
+                try {
+                    ajv.compile(schema);
+                } catch (err) {
+                    throw new Error(`Schema for rule ${ruleName} is invalid: ${err.message}`);
+                }
             }
 
             validator.validate(config, ruleMap.get.bind(ruleMap), new Environments(), "rule-tester");
diff --git a/tests/conf/config-schema.js b/tests/conf/config-schema.js
index 3fdb4bb03..09fa86061 100644
--- a/tests/conf/config-schema.js
+++ b/tests/conf/config-schema.js
@@ -9,10 +9,11 @@
 // Requirements
 //------------------------------------------------------------------------------
 
-const ajv = require("../../lib/util/ajv"),
-    configSchema = require("../../conf/config-schema.js"),
+const configSchema = require("../../conf/config-schema.js"),
     assert = require("assert");
 
+const ajv = require("../../lib/util/ajv")();
+
 //------------------------------------------------------------------------------
 // Tests
 //------------------------------------------------------------------------------
diff --git a/tests/conf/eslint-recommended.js b/tests/conf/eslint-recommended.js
new file mode 100644
index 000000000..89d9f4447
--- /dev/null
+++ b/tests/conf/eslint-recommended.js
@@ -0,0 +1,24 @@
+/**
+ * @fileoverview Tests for eslint:recommended.
+ * @author Kevin Partington
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const assert = require("chai").assert;
+const eslintRecommended = require("../../conf/eslint-recommended");
+const rules = eslintRecommended.rules;
+
+describe("eslint-recommended", () => {
+    it("should configure recommended rules as error", () => {
+        assert.strictEqual(rules["no-undef"], "error");
+    });
+
+    it("should not configure non-recommended rules", () => {
+        assert.notProperty(rules, "camelcase");
+    });
+});
diff --git a/tests/fixtures/cli-engine/overrides-with-dot/.eslintrc.yml b/tests/fixtures/cli-engine/overrides-with-dot/.eslintrc.yml
new file mode 100644
index 000000000..5acdae3fd
--- /dev/null
+++ b/tests/fixtures/cli-engine/overrides-with-dot/.eslintrc.yml
@@ -0,0 +1,6 @@
+root: true
+
+overrides:
+  - files: "*.js"
+    rules:
+      no-unused-vars: error
diff --git a/tests/fixtures/cli-engine/overrides-with-dot/.test-target.js b/tests/fixtures/cli-engine/overrides-with-dot/.test-target.js
new file mode 100644
index 000000000..ac09549ac
--- /dev/null
+++ b/tests/fixtures/cli-engine/overrides-with-dot/.test-target.js
@@ -0,0 +1,1 @@
+var a = 0
diff --git a/tests/fixtures/config-file/experimental-object-rest-spread/another-parser/.eslintrc.yml b/tests/fixtures/config-file/experimental-object-rest-spread/another-parser/.eslintrc.yml
deleted file mode 100644
index c64b2c309..000000000
--- a/tests/fixtures/config-file/experimental-object-rest-spread/another-parser/.eslintrc.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-root: true
-
-parser: ./parser.js
-parserOptions:
-  ecmaFeatures:
-    experimentalObjectRestSpread: true
diff --git a/tests/fixtures/config-file/experimental-object-rest-spread/another-parser/parser.js b/tests/fixtures/config-file/experimental-object-rest-spread/another-parser/parser.js
deleted file mode 100644
index f193589ef..000000000
--- a/tests/fixtures/config-file/experimental-object-rest-spread/another-parser/parser.js
+++ /dev/null
@@ -1,1 +0,0 @@
-module.exports.parse = () => ({ type: "Program", body: [] })
diff --git a/tests/fixtures/config-file/experimental-object-rest-spread/basic/.eslintrc.yml b/tests/fixtures/config-file/experimental-object-rest-spread/basic/.eslintrc.yml
deleted file mode 100644
index a5c9e4ebf..000000000
--- a/tests/fixtures/config-file/experimental-object-rest-spread/basic/.eslintrc.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-root: true
-
-parserOptions:
-  ecmaFeatures:
-    experimentalObjectRestSpread: true
diff --git a/tests/fixtures/config-file/experimental-object-rest-spread/extends/.eslintrc.yml b/tests/fixtures/config-file/experimental-object-rest-spread/extends/.eslintrc.yml
deleted file mode 100644
index 2daef0dea..000000000
--- a/tests/fixtures/config-file/experimental-object-rest-spread/extends/.eslintrc.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-root: true
-
-extends:
-  - ./common.yml
diff --git a/tests/fixtures/config-file/experimental-object-rest-spread/extends/common.yml b/tests/fixtures/config-file/experimental-object-rest-spread/extends/common.yml
deleted file mode 100644
index 99975a7bd..000000000
--- a/tests/fixtures/config-file/experimental-object-rest-spread/extends/common.yml
+++ /dev/null
@@ -1,3 +0,0 @@
-parserOptions:
-  ecmaFeatures:
-    experimentalObjectRestSpread: true
diff --git a/tests/fixtures/config-file/experimental-object-rest-spread/subdir/.eslintrc.yml b/tests/fixtures/config-file/experimental-object-rest-spread/subdir/.eslintrc.yml
deleted file mode 100644
index a5c9e4ebf..000000000
--- a/tests/fixtures/config-file/experimental-object-rest-spread/subdir/.eslintrc.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-root: true
-
-parserOptions:
-  ecmaFeatures:
-    experimentalObjectRestSpread: true
diff --git a/tests/fixtures/config-file/experimental-object-rest-spread/subdir/lib/.eslintrc.yml b/tests/fixtures/config-file/experimental-object-rest-spread/subdir/lib/.eslintrc.yml
deleted file mode 100644
index 78dc43786..000000000
--- a/tests/fixtures/config-file/experimental-object-rest-spread/subdir/lib/.eslintrc.yml
+++ /dev/null
@@ -1,2 +0,0 @@
-rules:
-  no-undef: off
diff --git a/tests/fixtures/config-file/extends-chain-2/parser.js b/tests/fixtures/config-file/extends-chain-2/parser.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/fixtures/config-file/node_modules/eslint-config-enable-browser-env.js b/tests/fixtures/config-file/node_modules/eslint-config-enable-browser-env.js
new file mode 100644
index 000000000..536f601c8
--- /dev/null
+++ b/tests/fixtures/config-file/node_modules/eslint-config-enable-browser-env.js
@@ -0,0 +1,1 @@
+module.exports = { env: { browser: true } };
diff --git a/tests/fixtures/config-file/node_modules/eslint-config-recursive-dependent/index.js b/tests/fixtures/config-file/node_modules/eslint-config-recursive-dependent/index.js
new file mode 100644
index 000000000..3c73f2e4e
--- /dev/null
+++ b/tests/fixtures/config-file/node_modules/eslint-config-recursive-dependent/index.js
@@ -0,0 +1,6 @@
+module.exports = {
+    env: {
+        browser: true
+    },
+    extends: 'recursive-dependency'
+};
diff --git a/tests/fixtures/config-file/node_modules/eslint-config-recursive-dependent/node_modules/eslint-config-recursive-dependency.js b/tests/fixtures/config-file/node_modules/eslint-config-recursive-dependent/node_modules/eslint-config-recursive-dependency.js
new file mode 100644
index 000000000..403945a31
--- /dev/null
+++ b/tests/fixtures/config-file/node_modules/eslint-config-recursive-dependent/node_modules/eslint-config-recursive-dependency.js
@@ -0,0 +1,5 @@
+module.exports = {
+    rules: {
+        bar: 2
+    }
+};
diff --git a/tests/fixtures/config-file/node_modules/eslint-plugin-enable-nonexistent-parser.js b/tests/fixtures/config-file/node_modules/eslint-plugin-enable-nonexistent-parser.js
new file mode 100644
index 000000000..e09b347f1
--- /dev/null
+++ b/tests/fixtures/config-file/node_modules/eslint-plugin-enable-nonexistent-parser.js
@@ -0,0 +1,5 @@
+exports.configs = {
+    bar: {
+        parser: "nonexistent-parser"
+    }
+};
diff --git a/tests/fixtures/config-file/node_modules/eslint-plugin-foo.js b/tests/fixtures/config-file/node_modules/eslint-plugin-foo.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/fixtures/config-file/node_modules/eslint-plugin-with-environment.js b/tests/fixtures/config-file/node_modules/eslint-plugin-with-environment.js
new file mode 100644
index 000000000..2b7d695d5
--- /dev/null
+++ b/tests/fixtures/config-file/node_modules/eslint-plugin-with-environment.js
@@ -0,0 +1,5 @@
+module.exports = {
+    environments: {
+        bar: { globals: { bar: true } }
+    }
+};
diff --git a/tests/fixtures/config-file/node_modules/eslint-plugin-with-two-configs.js b/tests/fixtures/config-file/node_modules/eslint-plugin-with-two-configs.js
new file mode 100644
index 000000000..016a35c85
--- /dev/null
+++ b/tests/fixtures/config-file/node_modules/eslint-plugin-with-two-configs.js
@@ -0,0 +1,6 @@
+module.exports = {
+    configs: {
+        foo: { rules: { semi: 2, quotes: 1 } },
+        bar: { rules: { quotes: 2, yoda: 2 } }
+    }
+};
diff --git a/tests/fixtures/config-file/plugins/.eslintrc.yml b/tests/fixtures/config-file/plugins/.eslintrc.yml
index 361805225..18addbd49 100644
--- a/tests/fixtures/config-file/plugins/.eslintrc.yml
+++ b/tests/fixtures/config-file/plugins/.eslintrc.yml
@@ -1,8 +1,8 @@
 plugins:
-    - test
+    - with-environment
 
 rules:
-    test/foo: 2
+    with-environment/foo: 2
 
 env:
-    test/bar: true
+    with-environment/bar: true
diff --git a/tests/fixtures/config-file/plugins/.eslintrc2.yml b/tests/fixtures/config-file/plugins/.eslintrc2.yml
index 973d77096..dc46e9312 100644
--- a/tests/fixtures/config-file/plugins/.eslintrc2.yml
+++ b/tests/fixtures/config-file/plugins/.eslintrc2.yml
@@ -1,3 +1,3 @@
 extends:
-    - plugin:test/foo
-    - plugin:test/bar
+    - plugin:with-two-configs/foo
+    - plugin:with-two-configs/bar
diff --git a/tests/fixtures/config-file/subdir/node_modules/@foo/eslint-config-bar.js b/tests/fixtures/config-file/subdir/node_modules/@foo/eslint-config-bar.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/fixtures/config-file/subdir/node_modules/@foo/eslint-config.js b/tests/fixtures/config-file/subdir/node_modules/@foo/eslint-config.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/fixtures/config-file/subdir/node_modules/eslint-config-eslint-configfoo.js b/tests/fixtures/config-file/subdir/node_modules/eslint-config-eslint-configfoo.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/fixtures/config-file/subdir/node_modules/eslint-config-foo.js b/tests/fixtures/config-file/subdir/node_modules/eslint-config-foo.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/fixtures/config-file/subdir/node_modules/eslint-plugin-foo.js b/tests/fixtures/config-file/subdir/node_modules/eslint-plugin-foo.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/fixtures/config-hierarchy/plugins/node_modules/eslint-plugin-another-plugin.js b/tests/fixtures/config-hierarchy/plugins/node_modules/eslint-plugin-another-plugin.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/fixtures/config-hierarchy/plugins/node_modules/eslint-plugin-example-with-rules-config.js b/tests/fixtures/config-hierarchy/plugins/node_modules/eslint-plugin-example-with-rules-config.js
new file mode 100644
index 000000000..b8b846e1e
--- /dev/null
+++ b/tests/fixtures/config-hierarchy/plugins/node_modules/eslint-plugin-example-with-rules-config.js
@@ -0,0 +1,6 @@
+module.exports = {
+    rules: { "example-rule": require("../../../rules/custom-rule") },
+
+    // rulesConfig support removed in 2.0.0, so this should have no effect
+    rulesConfig: { "example-rule": 1 }
+};
diff --git a/tests/fixtures/config-hierarchy/plugins/node_modules/eslint-plugin-example.js b/tests/fixtures/config-hierarchy/plugins/node_modules/eslint-plugin-example.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/fixtures/config-hierarchy/plugins/node_modules/eslint-plugin-test.js b/tests/fixtures/config-hierarchy/plugins/node_modules/eslint-plugin-test.js
new file mode 100644
index 000000000..39fac086a
--- /dev/null
+++ b/tests/fixtures/config-hierarchy/plugins/node_modules/eslint-plugin-test.js
@@ -0,0 +1,3 @@
+module.exports = {
+    environments: { example: { globals: { test: false } } }
+};
diff --git a/tests/fixtures/parsers/linter-test-parsers.js b/tests/fixtures/parsers/linter-test-parsers.js
new file mode 100644
index 000000000..dc81e34ec
--- /dev/null
+++ b/tests/fixtures/parsers/linter-test-parsers.js
@@ -0,0 +1,14 @@
+// Accumulates all of the parsers used by `tests/lib/linter.js`.
+// This file will get bundled so that those tests can be run in a browser.
+
+module.exports = {
+    enhancedParser: require("./enhanced-parser"),
+    stubParser: require("./stub-parser"),
+    unknownLogicalOperator: require("./unknown-operators/unknown-logical-operator"),
+    unknownLogicalOperatorNested: require("./unknown-operators/unknown-logical-operator-nested"),
+    lineError: require("./line-error"),
+    noLineError: require("./no-line-error"),
+    enhancedParser2: require("./enhanced-parser2"),
+    enhancedParser3: require("./enhanced-parser3"),
+    throwsWithOptions: require("./throws-with-options")
+};
diff --git a/tests/fixtures/plugins/node_modules/@scope/eslint-plugin-example.js b/tests/fixtures/plugins/node_modules/@scope/eslint-plugin-example.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/fixtures/plugins/node_modules/eslint-plugin-example.js b/tests/fixtures/plugins/node_modules/eslint-plugin-example.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/fixtures/plugins/node_modules/eslint-plugin-example1.js b/tests/fixtures/plugins/node_modules/eslint-plugin-example1.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/fixtures/plugins/node_modules/eslint-plugin-example2.js b/tests/fixtures/plugins/node_modules/eslint-plugin-example2.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/fixtures/plugins/node_modules/eslint-plugin-throws-on-load.js b/tests/fixtures/plugins/node_modules/eslint-plugin-throws-on-load.js
new file mode 100644
index 000000000..1e4791d3a
--- /dev/null
+++ b/tests/fixtures/plugins/node_modules/eslint-plugin-throws-on-load.js
@@ -0,0 +1,1 @@
+throw new Error('error thrown while loading this module');
diff --git a/tests/lib/cli-engine.js b/tests/lib/cli-engine.js
index ee582c83a..cff648d49 100644
--- a/tests/lib/cli-engine.js
+++ b/tests/lib/cli-engine.js
@@ -824,18 +824,14 @@ describe("CLIEngine", () => {
             assert.strictEqual(report.results[0].messages.length, 0);
         });
 
-        it("should report one fatal message when given a config file and a valid file and invalid parser", () => {
+        it("should throw an error when given a config file and a valid file and invalid parser", () => {
 
             engine = new CLIEngine({
                 parser: "test11",
                 useEslintrc: false
             });
 
-            const report = engine.executeOnFiles(["lib/cli.js"]);
-
-            assert.lengthOf(report.results, 1);
-            assert.lengthOf(report.results[0].messages, 1);
-            assert.isTrue(report.results[0].messages[0].fatal);
+            assert.throws(() => engine.executeOnFiles(["lib/cli.js"]), "Cannot find module 'test11'");
         });
 
         it("should report zero messages when given a directory with a .js2 file", () => {
@@ -1532,7 +1528,7 @@ describe("CLIEngine", () => {
 
             const report = engine.executeOnFiles(["lib/cli*.js"]);
 
-            assert.deepStrictEqual(
+            assert.sameDeepMembers(
                 report.usedDeprecatedRules,
                 [
                     { ruleId: "indent-legacy", replacedBy: ["indent"] },
@@ -2870,6 +2866,23 @@ describe("CLIEngine", () => {
                 }, "No files matching 'non-exist.js' were found.");
             });
         });
+
+        describe("overrides", () => {
+            beforeEach(() => {
+                engine = new CLIEngine({
+                    cwd: getFixturePath("cli-engine/overrides-with-dot"),
+                    ignore: false
+                });
+            });
+
+            it("should recognize dotfiles", () => {
+                const ret = engine.executeOnFiles([".test-target.js"]);
+
+                assert.strictEqual(ret.results.length, 1);
+                assert.strictEqual(ret.results[0].messages.length, 1);
+                assert.strictEqual(ret.results[0].messages[0].ruleId, "no-unused-vars");
+            });
+        });
     });
 
     describe("getConfigForFile", () => {
@@ -3026,19 +3039,21 @@ describe("CLIEngine", () => {
 
         it("should return null when a customer formatter doesn't exist", () => {
             const engine = new CLIEngine(),
-                formatterPath = getFixturePath("formatters", "doesntexist.js");
+                formatterPath = getFixturePath("formatters", "doesntexist.js"),
+                fullFormatterPath = path.resolve(formatterPath);
 
             assert.throws(() => {
                 engine.getFormatter(formatterPath);
-            }, `There was a problem loading formatter: ${formatterPath}\nError: Cannot find module '${formatterPath}'`);
+            }, `There was a problem loading formatter: ${fullFormatterPath}\nError: Cannot find module '${fullFormatterPath}'`);
         });
 
         it("should return null when a built-in formatter doesn't exist", () => {
             const engine = new CLIEngine();
+            const fullFormatterPath = path.resolve(__dirname, "..", "..", "lib", "formatters", "special");
 
             assert.throws(() => {
                 engine.getFormatter("special");
-            }, "There was a problem loading formatter: ./formatters/special\nError: Cannot find module './formatters/special'");
+            }, `There was a problem loading formatter: ${fullFormatterPath}\nError: Cannot find module '${fullFormatterPath}'`);
         });
 
         it("should throw if the required formatter exists but has an error", () => {
@@ -3096,6 +3111,20 @@ describe("CLIEngine", () => {
             assert.strictEqual(errorResults[0].messages[4].severity, 2);
         });
 
+        it("should not mutate passed report.results parameter", () => {
+            process.chdir(originalDir);
+            const engine = new CLIEngine({
+                rules: { quotes: [1, "double"] }
+            });
+
+            const report = engine.executeOnText("var foo = 'bar';");
+            const reportResultsLength = report.results[0].messages.length;
+
+            CLIEngine.getErrorResults(report.results);
+
+            assert.lengthOf(report.results[0].messages, reportResultsLength);
+        });
+
         it("should report a warningCount of 0 when looking for errors only", () => {
 
             process.chdir(originalDir);
diff --git a/tests/lib/cli.js b/tests/lib/cli.js
index 65c988365..a0a197d88 100644
--- a/tests/lib/cli.js
+++ b/tests/lib/cli.js
@@ -589,11 +589,10 @@ describe("cli", () => {
     });
 
     describe("when given an parser name", () => {
-        it("should exit with error if parser is invalid", () => {
+        it("should exit with a fatal error if parser is invalid", () => {
             const filePath = getFixturePath("passing.js");
-            const exit = cli.execute(`--no-ignore --parser test111 ${filePath}`);
 
-            assert.strictEqual(exit, 1);
+            assert.throws(() => cli.execute(`--no-ignore --parser test111 ${filePath}`), "Cannot find module 'test111'");
         });
 
         it("should exit with no error if parser is valid", () => {
diff --git a/tests/lib/config.js b/tests/lib/config.js
index 5381ce1eb..e400dd475 100644
--- a/tests/lib/config.js
+++ b/tests/lib/config.js
@@ -22,37 +22,10 @@ const DIRECTORY_CONFIG_HIERARCHY = require("../fixtures/config-hierarchy/file-st
 
 const linter = new Linter();
 
-require("shelljs/global");
+const { mkdir, rm, cp } = require("shelljs");
 
 const proxyquire = require("proxyquire").noCallThru().noPreserveCache();
 
-/* global mkdir, rm, cp */
-
-
-/**
- * Creates a stubbed Config object that will bypass normal require() to load
- * plugins by name from the objects specified.
- * @param {Object} plugins The keys are the package names, values are plugin objects.
- * @returns {Config} The stubbed instance of Config.
- * @private
- */
-function createStubbedConfigWithPlugins(plugins) {
-
-    // stub out plugins
-    const StubbedPlugins = proxyquire("../../lib/config/plugins", plugins);
-
-    // stub out config file to use stubbed plugins
-    const StubbedConfigFile = proxyquire("../../lib/config/config-file", {
-        "./plugins": StubbedPlugins
-    });
-
-    // stub out Config to use stub config file
-    return proxyquire("../../lib/config", {
-        "./config/config-file": StubbedConfigFile,
-        "./config/plugins": StubbedPlugins
-    });
-}
-
 /**
  * Asserts that two configs are equal. This is necessary because assert.deepStrictEqual()
  * gets confused when properties are in different orders.
@@ -136,6 +109,7 @@ describe("Config", () => {
         fixtureDir = `${os.tmpdir()}/eslint/fixtures`;
         mkdir("-p", fixtureDir);
         cp("-r", "./tests/fixtures/config-hierarchy", fixtureDir);
+        cp("-r", "./tests/fixtures/rules", fixtureDir);
     });
 
     beforeEach(() => {
@@ -154,11 +128,11 @@ describe("Config", () => {
 
         // https://github.com/eslint/eslint/issues/2380
         it("should not modify baseConfig when format is specified", () => {
-            const customBaseConfig = { foo: "bar" },
-                configHelper = new Config({ baseConfig: customBaseConfig, format: "foo" }, linter);
+            const customBaseConfig = {},
+                configHelper = new Config({ baseConfig: customBaseConfig, cwd: process.cwd(), format: "foo" }, linter);
 
             // at one point, customBaseConfig.format would end up equal to "foo"...that's bad
-            assert.deepStrictEqual(customBaseConfig, { foo: "bar" });
+            assert.deepStrictEqual(customBaseConfig, {});
             assert.strictEqual(configHelper.options.format, "foo");
         });
 
@@ -166,7 +140,7 @@ describe("Config", () => {
             const customBaseConfig = {
                 extends: path.resolve(__dirname, "..", "fixtures", "config-extends", "array", ".eslintrc")
             };
-            const configHelper = new Config({ baseConfig: customBaseConfig }, linter);
+            const configHelper = new Config({ baseConfig: customBaseConfig, cwd: process.cwd() }, linter);
 
             assert.deepStrictEqual(configHelper.baseConfig.env, {
                 browser: false,
@@ -207,7 +181,7 @@ describe("Config", () => {
         });
 
         it("should return the path when an .eslintrc file is found", () => {
-            const configHelper = new Config({}, linter),
+            const configHelper = new Config({ cwd: process.cwd() }, linter),
                 expected = getFakeFixturePath("broken", ".eslintrc"),
                 actual = Array.from(
                     configHelper.findLocalConfigFiles(getFakeFixturePath("broken"))
@@ -217,7 +191,7 @@ describe("Config", () => {
         });
 
         it("should return an empty array when an .eslintrc file is not found", () => {
-            const configHelper = new Config({}, linter),
+            const configHelper = new Config({ cwd: process.cwd() }, linter),
                 actual = Array.from(
                     configHelper.findLocalConfigFiles(getFakeFixturePath())
                 );
@@ -227,7 +201,7 @@ describe("Config", () => {
         });
 
         it("should return package.json only when no other config files are found", () => {
-            const configHelper = new Config({}, linter),
+            const configHelper = new Config({ cwd: process.cwd() }, linter),
                 expected0 = getFakeFixturePath("packagejson", "subdir", "package.json"),
                 expected1 = getFakeFixturePath("packagejson", ".eslintrc"),
                 actual = Array.from(
@@ -239,7 +213,7 @@ describe("Config", () => {
         });
 
         it("should return the only one config file even if there are multiple found", () => {
-            const configHelper = new Config({}, linter),
+            const configHelper = new Config({ cwd: process.cwd() }, linter),
                 expected = getFakeFixturePath("broken", ".eslintrc"),
 
                 // The first element of the array is the .eslintrc in the same directory.
@@ -252,7 +226,7 @@ describe("Config", () => {
         });
 
         it("should return all possible files when multiple are found", () => {
-            const configHelper = new Config({}, linter),
+            const configHelper = new Config({ cwd: process.cwd() }, linter),
                 expected = [
                     getFakeFixturePath("fileexts/subdir/subsubdir/", ".eslintrc.json"),
                     getFakeFixturePath("fileexts/subdir/", ".eslintrc.yml"),
@@ -268,7 +242,7 @@ describe("Config", () => {
         });
 
         it("should return an empty array when a package.json file is not found", () => {
-            const configHelper = new Config({}, linter),
+            const configHelper = new Config({ cwd: process.cwd() }, linter),
                 actual = Array.from(configHelper.findLocalConfigFiles(getFakeFixturePath()));
 
             assert.isArray(actual);
@@ -357,9 +331,9 @@ describe("Config", () => {
         // make sure JS-style comments don't throw an error
         it("should load the config file when there are JS-style comments in the text", () => {
             const configPath = path.resolve(__dirname, "..", "fixtures", "configurations", "comments.json"),
-                configHelper = new Config({ configFile: configPath }, linter),
-                semi = configHelper.specificConfig.rules.semi,
-                strict = configHelper.specificConfig.rules.strict;
+                configHelper = new Config({ configFile: configPath, cwd: process.cwd() }, linter),
+                semi = configHelper.specificConfigInfo.config.rules.semi,
+                strict = configHelper.specificConfigInfo.config.rules.strict;
 
             assert.strictEqual(semi, 1);
             assert.strictEqual(strict, 0);
@@ -368,9 +342,9 @@ describe("Config", () => {
         // make sure YAML files work correctly
         it("should load the config file when a YAML file is used", () => {
             const configPath = path.resolve(__dirname, "..", "fixtures", "configurations", "env-browser.yaml"),
-                configHelper = new Config({ configFile: configPath }, linter),
-                noAlert = configHelper.specificConfig.rules["no-alert"],
-                noUndef = configHelper.specificConfig.rules["no-undef"];
+                configHelper = new Config({ configFile: configPath, cwd: process.cwd() }, linter),
+                noAlert = configHelper.specificConfigInfo.config.rules["no-alert"],
+                noUndef = configHelper.specificConfigInfo.config.rules["no-undef"];
 
             assert.strictEqual(noAlert, 0);
             assert.strictEqual(noUndef, 2);
@@ -407,7 +381,7 @@ describe("Config", () => {
         // Default configuration - blank
         it("should return a blank config when using no .eslintrc", () => {
 
-            const configHelper = new Config({ useEslintrc: false }, linter),
+            const configHelper = new Config({ useEslintrc: false, cwd: process.cwd() }, linter),
                 file = getFixturePath("broken", "console-wrong-quotes.js"),
                 expected = {
                     rules: {},
@@ -420,7 +394,7 @@ describe("Config", () => {
         });
 
         it("should return a blank config when baseConfig is set to false and no .eslintrc", () => {
-            const configHelper = new Config({ baseConfig: false, useEslintrc: false }, linter),
+            const configHelper = new Config({ baseConfig: false, useEslintrc: false, cwd: process.cwd() }, linter),
                 file = getFixturePath("broken", "console-wrong-quotes.js"),
                 expected = {
                     rules: {},
@@ -435,7 +409,7 @@ describe("Config", () => {
         // No default configuration
         it("should return an empty config when not using .eslintrc", () => {
 
-            const configHelper = new Config({ useEslintrc: false }, linter),
+            const configHelper = new Config({ useEslintrc: false, cwd: process.cwd() }, linter),
                 file = getFixturePath("broken", "console-wrong-quotes.js"),
                 actual = configHelper.getConfig(file);
 
@@ -443,8 +417,6 @@ describe("Config", () => {
         });
 
         it("should return a modified config when baseConfig is set to an object and no .eslintrc", () => {
-
-
             const configHelper = new Config({
                     baseConfig: {
                         env: {
@@ -454,7 +426,8 @@ describe("Config", () => {
                             quotes: [2, "single"]
                         }
                     },
-                    useEslintrc: false
+                    useEslintrc: false,
+                    cwd: process.cwd()
                 }, linter),
                 file = getFixturePath("broken", "console-wrong-quotes.js"),
                 expected = {
@@ -471,19 +444,8 @@ describe("Config", () => {
         });
 
         it("should return a modified config without plugin rules enabled when baseConfig is set to an object with plugin and no .eslintrc", () => {
-            const customRule = require("../fixtures/rules/custom-rule");
-            const examplePluginName = "eslint-plugin-example";
-            const requireStubs = {};
-
-            requireStubs[examplePluginName] = {
-                rules: { "example-rule": customRule },
-
-                // rulesConfig support removed in 2.0.0, so this should have no effect
-                rulesConfig: { "example-rule": 1 }
-            };
-
-            const StubbedConfig = proxyquire("../../lib/config", requireStubs);
-            const configHelper = new StubbedConfig({
+            const examplePluginName = "eslint-plugin-example-with-rules-config";
+            const configHelper = new Config({
                     baseConfig: {
                         env: {
                             node: true
@@ -493,7 +455,8 @@ describe("Config", () => {
                         },
                         plugins: [examplePluginName]
                     },
-                    useEslintrc: false
+                    useEslintrc: false,
+                    cwd: getFixturePath("plugins")
                 }, linter),
                 file = getFixturePath("broken", "plugins", "console-wrong-quotes.js"),
                 expected = {
@@ -598,8 +561,6 @@ describe("Config", () => {
                 },
                 actual = configHelper.getConfig(file);
 
-            expected.env.node = true;
-
             assertConfigsEqual(actual, expected);
         });
 
@@ -703,16 +664,9 @@ describe("Config", () => {
 
         // Command line configuration - --plugin
         it("should merge command line plugin with local .eslintrc", () => {
-
-            // stub out Config to use stub config file
-            const StubbedConfig = createStubbedConfigWithPlugins({
-                "eslint-plugin-example": {},
-                "eslint-plugin-another-plugin": {}
-            });
-
-            const configHelper = new StubbedConfig({
+            const configHelper = new Config({
                     plugins: ["another-plugin"],
-                    cwd: process.cwd()
+                    cwd: getFixturePath("plugins")
                 }, linter),
                 file = getFixturePath("broken", "plugins", "console-wrong-quotes.js"),
                 expected = {
@@ -748,7 +702,7 @@ describe("Config", () => {
 
         it("should load user config globals", () => {
             const configPath = path.resolve(__dirname, "..", "fixtures", "globals", "conf.yaml"),
-                configHelper = new Config({ configFile: configPath, useEslintrc: false }, linter);
+                configHelper = new Config({ configFile: configPath, useEslintrc: false, cwd: process.cwd() }, linter);
 
             const expected = {
                 globals: {
@@ -764,7 +718,7 @@ describe("Config", () => {
         it("should not load disabled environments", () => {
             const configPath = path.resolve(__dirname, "..", "fixtures", "environments", "disable.yaml");
 
-            const configHelper = new Config({ configFile: configPath, useEslintrc: false }, linter);
+            const configHelper = new Config({ configFile: configPath, useEslintrc: false, cwd: process.cwd() }, linter);
 
             const config = configHelper.getConfig(configPath);
 
@@ -791,7 +745,7 @@ describe("Config", () => {
             const configPath = path.resolve(__dirname, "..", "fixtures", "config-extends", "error.json");
 
             assert.throws(() => {
-                const configHelper = new Config({ useEslintrc: false, configFile: configPath }, linter);
+                const configHelper = new Config({ useEslintrc: false, configFile: configPath, cwd: process.cwd() }, linter);
 
                 configHelper.getConfig(configPath);
             }, /Referenced from:.*?error\.json/u);
@@ -800,7 +754,7 @@ describe("Config", () => {
         // Keep order with the last array element taking highest precedence
         it("should make the last element in an array take the highest precedence", () => {
             const configPath = path.resolve(__dirname, "..", "fixtures", "config-extends", "array", ".eslintrc"),
-                configHelper = new Config({ useEslintrc: false, configFile: configPath }, linter),
+                configHelper = new Config({ useEslintrc: false, configFile: configPath, cwd: process.cwd() }, linter),
                 expected = {
                     rules: { "no-empty": 1, "comma-dangle": 2, "no-console": 2 },
                     env: { browser: false, node: true, es6: true }
@@ -872,7 +826,6 @@ describe("Config", () => {
                         parserOptions: {},
                         env: {},
                         globals: {},
-                        parser: void 0,
                         rules: {
                             "home-folder-rule": 2
                         }
@@ -901,7 +854,6 @@ describe("Config", () => {
                         parserOptions: {},
                         env: {},
                         globals: {},
-                        parser: void 0,
                         rules: {
                             "project-level-rule": 2
                         }
@@ -928,7 +880,6 @@ describe("Config", () => {
                         parserOptions: {},
                         env: {},
                         globals: {},
-                        parser: void 0,
                         rules: {
                             quotes: [2, "double"]
                         }
@@ -953,7 +904,6 @@ describe("Config", () => {
                         parserOptions: {},
                         env: {},
                         globals: {},
-                        parser: void 0,
                         rules: {
                             "project-level-rule": 2,
                             "subfolder-level-rule": 2
@@ -1158,9 +1108,9 @@ describe("Config", () => {
                             rules: {
                                 quotes: [1, "double"]
                             }
-                        }],
-                        useEslintrc: false
-                    }
+                        }]
+                    },
+                    useEslintrc: false
                 }, linter);
 
                 assert.throws(() => config.getConfig(targetPath), /Invalid override pattern/u);
@@ -1178,9 +1128,9 @@ describe("Config", () => {
                             rules: {
                                 quotes: [1, "single"]
                             }
-                        }],
-                        useEslintrc: false
-                    }
+                        }]
+                    },
+                    useEslintrc: false
                 }, linter);
 
                 assert.throws(() => config.getConfig(targetPath), /Invalid override pattern/u);
@@ -1397,89 +1347,17 @@ describe("Config", () => {
                         `The 'ecmaFeatures' config file property is deprecated, and has no effect. (found in "tests${path.sep}fixtures${path.sep}config-file${path.sep}ecma-features${path.sep}.eslintrc.yml")`
                     );
                 }));
-
-            it("should emit a deprecation warning if 'parserOptions.ecmaFeatures.experimentalObjectRestSpread' is given.", () => Promise.resolve()
-                .then(() => {
-                    const cwd = path.resolve(__dirname, "../fixtures/config-file/experimental-object-rest-spread/basic/");
-                    const config = new Config({ cwd }, linter);
-
-                    config.getConfig("test.js");
-
-                    // Wait for "warning" event.
-                    return nextTick();
-                })
-                .then(() => {
-                    assert.notStrictEqual(warning, null);
-                    assert.strictEqual(
-                        warning.message,
-                        `The 'parserOptions.ecmaFeatures.experimentalObjectRestSpread' option is deprecated. Use 'parserOptions.ecmaVersion' instead. (found in "tests${path.sep}fixtures${path.sep}config-file${path.sep}experimental-object-rest-spread${path.sep}basic${path.sep}.eslintrc.yml")`
-                    );
-                }));
-
-            it("should emit a deprecation warning if 'parserOptions.ecmaFeatures.experimentalObjectRestSpread' is given in a parent config.", () => Promise.resolve()
-                .then(() => {
-                    const cwd = path.resolve(__dirname, "../fixtures/config-file/experimental-object-rest-spread/subdir/");
-                    const config = new Config({ cwd }, linter);
-
-                    config.getConfig("lib/test.js");
-
-                    // Wait for "warning" event.
-                    return nextTick();
-                })
-                .then(() => {
-                    assert.notStrictEqual(warning, null);
-                    assert.strictEqual(
-                        warning.message,
-                        `The 'parserOptions.ecmaFeatures.experimentalObjectRestSpread' option is deprecated. Use 'parserOptions.ecmaVersion' instead. (found in "tests${path.sep}fixtures${path.sep}config-file${path.sep}experimental-object-rest-spread${path.sep}subdir${path.sep}.eslintrc.yml")`
-                    );
-                }));
-
-            it("should emit a deprecation warning if 'parserOptions.ecmaFeatures.experimentalObjectRestSpread' is given in a shareable config.", () => Promise.resolve()
-                .then(() => {
-                    const cwd = path.resolve(__dirname, "../fixtures/config-file/experimental-object-rest-spread/extends/");
-                    const config = new Config({ cwd }, linter);
-
-                    config.getConfig("test.js");
-
-                    // Wait for "warning" event.
-                    return nextTick();
-                })
-                .then(() => {
-                    assert.notStrictEqual(warning, null);
-                    assert.strictEqual(
-                        warning.message,
-                        `The 'parserOptions.ecmaFeatures.experimentalObjectRestSpread' option is deprecated. Use 'parserOptions.ecmaVersion' instead. (found in "tests${path.sep}fixtures${path.sep}config-file${path.sep}experimental-object-rest-spread${path.sep}extends${path.sep}common.yml")`
-                    );
-                }));
-
-            it("should NOT emit a deprecation warning even if 'parserOptions.ecmaFeatures.experimentalObjectRestSpread' is given, if parser is not espree.", () => Promise.resolve()
-                .then(() => {
-                    const cwd = path.resolve(__dirname, "../fixtures/config-file/experimental-object-rest-spread/another-parser/");
-                    const config = new Config({ cwd }, linter);
-
-                    config.getConfig("test.js");
-
-                    // Wait for "warning" event.
-                    return nextTick();
-                })
-                .then(() => {
-                    assert.strictEqual(warning, null);
-                }));
         });
     });
 
     describe("Plugin Environments", () => {
         it("should load environments from plugin", () => {
-
-            const StubbedConfig = createStubbedConfigWithPlugins({
-                "eslint-plugin-test": {
-                    environments: { example: { globals: { test: false } } }
-                }
-            });
-
             const configPath = path.resolve(__dirname, "..", "fixtures", "environments", "plugin.yaml"),
-                configHelper = new StubbedConfig({
-                    reset: true, configFile: configPath, useEslintrc: false
+                configHelper = new Config({
+                    reset: true,
+                    configFile: configPath,
+                    useEslintrc: false,
+                    cwd: getFixturePath("plugins")
                 }, linter),
                 expected = {
                     env: {
diff --git a/tests/lib/config/config-file.js b/tests/lib/config/config-file.js
index 91986bdcf..2d74b8ae8 100644
--- a/tests/lib/config/config-file.js
+++ b/tests/lib/config/config-file.js
@@ -8,39 +8,27 @@
 // Requirements
 //------------------------------------------------------------------------------
 
-const Module = require("module"),
-    assert = require("chai").assert,
+const assert = require("chai").assert,
     leche = require("leche"),
     sinon = require("sinon"),
     path = require("path"),
     fs = require("fs"),
-    os = require("os"),
     yaml = require("js-yaml"),
     shell = require("shelljs"),
     espree = require("espree"),
     ConfigFile = require("../../../lib/config/config-file"),
     Linter = require("../../../lib/linter"),
     CLIEngine = require("../../../lib/cli-engine"),
-    Config = require("../../../lib/config");
+    Config = require("../../../lib/config"),
+    relativeModuleResolver = require("../../../lib/util/relative-module-resolver");
 
-const userHome = os.homedir();
 const temp = require("temp").track();
 const proxyquire = require("proxyquire").noCallThru().noPreserveCache();
-let configContext;
 
 //------------------------------------------------------------------------------
 // Helpers
 //------------------------------------------------------------------------------
 
-/*
- * Project path is the project that is including ESLint as a dependency. In the
- * case of these tests, it will end up the parent of the "eslint" folder. That's
- * fine for the purposes of testing because the tests are just relative to an
- * ancestor location.
- */
-const PROJECT_PATH = path.resolve(__dirname, "../../../../"),
-    PROJECT_DEPS_PATH = path.join(PROJECT_PATH, "node_modules");
-
 /**
  * Helper function get easily get a path in the fixtures directory.
  * @param {string} filepath The path to find in the fixtures directory.
@@ -85,81 +73,15 @@ function writeTempJsConfigFile(config, filename, existingTmpDir) {
     return tmpFilePath;
 }
 
-/**
- * Creates a module path relative to the current working directory.
- * @param {string} moduleName The full module name.
- * @returns {string} A full path for the module local to cwd.
- * @private
- */
-function getProjectModulePath(moduleName) {
-    return path.resolve(PROJECT_PATH, "./node_modules", moduleName, "index.js");
-}
-
-/**
- * Creates a module path relative to the given directory.
- * @param {string} moduleName The full module name.
- * @returns {string} A full path for the module local to the given directory.
- * @private
- */
-function getRelativeModulePath(moduleName) {
-    return path.resolve("./node_modules", moduleName, "index.js");
-}
-
-/**
- * Creates a module resolver that always resolves the given mappings.
- * @param {Object<string,string>} mapping A mapping of module name to path.
- * @constructor
- * @private
- */
-function createStubModuleResolver(mapping) {
-
-    /**
-     * Stubbed module resolver for easier testing.
-     * @constructor
-     * @private
-     */
-    return class StubModuleResolver {
-        resolve(name) { // eslint-disable-line class-methods-use-this
-            if (Object.prototype.hasOwnProperty.call(mapping, name)) {
-                return mapping[name];
-            }
-
-            throw new Error(`Cannot find module '${name}'`);
-        }
-    };
-}
-
-/**
- * Overrides the native module resolver to resolve with the given mappings.
- * @param {Object<string,string>} mapping A mapping of module name to path.
- * @returns {void}
- * @private
- */
-function overrideNativeResolve(mapping) {
-    let originalFindPath;
-
-    beforeEach(() => {
-        originalFindPath = Module._findPath; // eslint-disable-line no-underscore-dangle
-        Module._findPath = function(request, paths, isMain) { // eslint-disable-line no-underscore-dangle
-            if (Object.prototype.hasOwnProperty.call(mapping, request)) {
-                return mapping[request];
-            }
-            return originalFindPath.call(this, request, paths, isMain);
-        };
-    });
-    afterEach(() => {
-        Module._findPath = originalFindPath; // eslint-disable-line no-underscore-dangle
-    });
-}
-
 //------------------------------------------------------------------------------
 // Tests
 //------------------------------------------------------------------------------
 
 describe("ConfigFile", () => {
+    let configContext;
 
     beforeEach(() => {
-        configContext = new Config({}, new Linter());
+        configContext = new Config({ cwd: getFixturePath(".") }, new Linter());
     });
 
     describe("CONFIG_FILES", () => {
@@ -169,54 +91,22 @@ describe("ConfigFile", () => {
     });
 
     describe("applyExtends()", () => {
-
-        overrideNativeResolve({
-            "eslint-plugin-test": getProjectModulePath("eslint-plugin-test")
-        });
-
         it("should apply extension 'foo' when specified from root directory config", () => {
-
-            const resolvedPath = path.resolve(PROJECT_PATH, "./node_modules/eslint-config-foo/index.js");
-
-            const configDeps = {
-
-                // Hacky: need to override isFile for each call for testing
-                "../util/module-resolver": createStubModuleResolver({ "eslint-config-foo": resolvedPath }),
-                "import-fresh"(filename) {
-                    return configDeps[filename];
-                }
-            };
-
-            configDeps[resolvedPath] = {
-                env: { browser: true }
-            };
-
-            const StubbedConfigFile = proxyquire("../../../lib/config/config-file", configDeps);
-
-            const config = StubbedConfigFile.applyExtends({
-                extends: "foo",
+            const config = ConfigFile.applyExtends({
+                extends: "enable-browser-env",
                 rules: { eqeqeq: 2 }
-            }, configContext, "/whatever");
+            }, configContext, getFixturePath(".eslintrc"));
 
             assert.deepStrictEqual(config, {
-                baseDirectory: path.dirname(resolvedPath),
-                filePath: resolvedPath,
-                extends: "foo",
                 parserOptions: {},
                 env: { browser: true },
                 globals: {},
                 rules: { eqeqeq: 2 }
             });
-
         });
 
         it("should apply all rules when extends config includes 'eslint:all'", () => {
-
-            const configDeps = {
-                "../util/module-resolver": createStubModuleResolver({})
-            };
-            const StubbedConfigFile = proxyquire("../../../lib/config/config-file", configDeps);
-            const config = StubbedConfigFile.applyExtends({
+            const config = ConfigFile.applyExtends({
                 extends: "eslint:all"
             }, configContext, "/whatever");
 
@@ -226,15 +116,8 @@ describe("ConfigFile", () => {
         });
 
         it("should throw an error when extends config module is not found", () => {
-
-            const configDeps = {
-                "../util/module-resolver": createStubModuleResolver({})
-            };
-
-            const StubbedConfigFile = proxyquire("../../../lib/config/config-file", configDeps);
-
             assert.throws(() => {
-                StubbedConfigFile.applyExtends({
+                ConfigFile.applyExtends({
                     extends: "foo",
                     rules: { eqeqeq: 2 }
                 }, configContext, "/whatever");
@@ -243,15 +126,8 @@ describe("ConfigFile", () => {
         });
 
         it("should throw an error when an eslint config is not found", () => {
-
-            const configDeps = {
-                "../util/module-resolver": createStubModuleResolver({})
-            };
-
-            const StubbedConfigFile = proxyquire("../../../lib/config/config-file", configDeps);
-
             assert.throws(() => {
-                StubbedConfigFile.applyExtends({
+                ConfigFile.applyExtends({
                     extends: "eslint:foo",
                     rules: { eqeqeq: 2 }
                 }, configContext, "/whatever");
@@ -260,105 +136,78 @@ describe("ConfigFile", () => {
         });
 
         it("should throw an error when a parser in a plugin config is not found", () => {
-            const resolvedPath = path.resolve(PROJECT_PATH, "./node_modules/eslint-plugin-test/index.js");
-
-            const configDeps = {
-                "../util/module-resolver": createStubModuleResolver({
-                    "eslint-plugin-test": resolvedPath
-                }),
-                "import-fresh"(filename) {
-                    return configDeps[filename];
-                }
-            };
-
-            configDeps[resolvedPath] = {
-                configs: {
-                    bar: {
-                        parser: "babel-eslint"
-                    }
-                }
-            };
-
-            const StubbedConfigFile = proxyquire("../../../lib/config/config-file", configDeps);
-
             assert.throws(() => {
-                StubbedConfigFile.applyExtends({
-                    extends: "plugin:test/bar",
+                ConfigFile.applyExtends({
+                    extends: "plugin:enable-nonexistent-parser/bar",
                     rules: { eqeqeq: 2 }
                 }, configContext, "/whatever");
-            }, /Cannot find module 'babel-eslint'/u);
+            }, /Failed to resolve parser 'nonexistent-parser' declared in '[-\w/.:\\]+'.\nReferenced from: \/whatever/u);
+        });
 
+        it("should fall back to default parser when a parser called 'espree' is not found", () => {
+            assert.deepStrictEqual(
+                ConfigFile.loadObject(configContext, { config: { parser: "espree" }, filePath: "/", configFullName: "configName" }),
+                { parser: require.resolve("espree") }
+            );
         });
 
         it("should throw an error when a plugin config is not found", () => {
-            const resolvedPath = path.resolve(PROJECT_PATH, "./node_modules/eslint-plugin-test/index.js");
-
-            const configDeps = {
-                "../util/module-resolver": createStubModuleResolver({
-                    "eslint-plugin-test": resolvedPath
-                }),
-                "import-fresh"(filename) {
-                    return configDeps[filename];
-                }
-            };
-
-            configDeps[resolvedPath] = {
-                configs: {
-                    baz: {}
-                }
-            };
-
-            const StubbedConfigFile = proxyquire("../../../lib/config/config-file", configDeps);
-
             assert.throws(() => {
-                StubbedConfigFile.applyExtends({
-                    extends: "plugin:test/bar",
+                ConfigFile.applyExtends({
+                    extends: "plugin:enable-nonexistent-parser/baz",
                     rules: { eqeqeq: 2 }
                 }, configContext, "/whatever");
-            }, /Failed to load config "plugin:test\/bar" to extend from./u);
-
+            }, /Failed to load config "plugin:enable-nonexistent-parser\/baz" to extend from./u);
         });
 
-        it("should apply extensions recursively when specified from package", () => {
-
-            const resolvedPaths = [
-                path.resolve(PROJECT_PATH, "./node_modules/eslint-config-foo/index.js"),
-                path.resolve(PROJECT_PATH, "./node_modules/eslint-config-bar/index.js")
-            ];
-
-            const configDeps = {
-
-                "../util/module-resolver": createStubModuleResolver({
-                    "eslint-config-foo": resolvedPaths[0],
-                    "eslint-config-bar": resolvedPaths[1]
-                }),
-                "import-fresh"(filename) {
-                    return configDeps[filename];
-                }
-            };
+        it("should throw an error with a message template when a plugin referenced for a plugin config is not found", () => {
+            try {
+                ConfigFile.applyExtends({
+                    extends: "plugin:nonexistent-plugin/baz",
+                    rules: { eqeqeq: 2 }
+                }, configContext, "/whatever");
+            } catch (err) {
+                assert.strictEqual(err.messageTemplate, "plugin-missing");
+                assert.deepStrictEqual(err.messageData, {
+                    pluginName: "eslint-plugin-nonexistent-plugin",
+                    pluginRootPath: getFixturePath("."),
+                    configStack: ["/whatever"]
+                });
 
-            configDeps[resolvedPaths[0]] = {
-                extends: "bar",
-                env: { browser: true }
-            };
+                return;
+            }
+            assert.fail("Expected to throw an error");
+        });
 
-            configDeps[resolvedPaths[1]] = {
-                rules: {
-                    bar: 2
-                }
-            };
+        it("should throw an error with a message template when a plugin in the plugins list is not found", () => {
+            try {
+                ConfigFile.loadObject(configContext, {
+                    config: {
+                        plugins: ["nonexistent-plugin"]
+                    },
+                    filePath: "/whatever",
+                    configFullName: "configName"
+                });
+            } catch (err) {
+                assert.strictEqual(err.messageTemplate, "plugin-missing");
+                assert.deepStrictEqual(err.messageData, {
+                    pluginName: "eslint-plugin-nonexistent-plugin",
+                    pluginRootPath: getFixturePath("."),
+                    configStack: ["/whatever"]
+                });
 
-            const StubbedConfigFile = proxyquire("../../../lib/config/config-file", configDeps);
+                return;
+            }
+            assert.fail("Expected to throw an error");
+        });
 
-            const config = StubbedConfigFile.applyExtends({
-                extends: "foo",
+        it("should apply extensions recursively when specified from package", () => {
+            const config = ConfigFile.applyExtends({
+                extends: "recursive-dependent",
                 rules: { eqeqeq: 2 }
-            }, configContext, "/whatever");
+            }, configContext, getFixturePath(".eslintrc.js"));
 
             assert.deepStrictEqual(config, {
-                baseDirectory: path.dirname(resolvedPaths[0]),
-                filePath: resolvedPaths[0],
-                extends: "foo",
                 parserOptions: {},
                 env: { browser: true },
                 globals: {},
@@ -381,9 +230,6 @@ describe("ConfigFile", () => {
             }, configContext, filePath);
 
             assert.deepStrictEqual(config, {
-                baseDirectory: path.dirname(filePath),
-                filePath: path.join(path.dirname(filePath), extendsFile),
-                extends: extendsFile,
                 parserOptions: {},
                 env: {},
                 globals: {},
@@ -406,9 +252,6 @@ describe("ConfigFile", () => {
             }, configContext, filePath);
 
             assert.deepStrictEqual(config, {
-                baseDirectory: path.dirname(filePath),
-                filePath: path.join(path.dirname(filePath), extendsFile),
-                extends: extendsFile,
                 parserOptions: {},
                 env: { browser: true },
                 globals: {},
@@ -430,9 +273,6 @@ describe("ConfigFile", () => {
             }, configContext, filePath);
 
             assert.deepStrictEqual(config, {
-                baseDirectory: path.dirname(filePath),
-                filePath: path.join(path.dirname(filePath), extendsFile),
-                extends: extendsFile,
                 parserOptions: {},
                 env: {},
                 globals: {},
@@ -455,9 +295,6 @@ describe("ConfigFile", () => {
             }, configContext, filePath);
 
             assert.deepStrictEqual(config, {
-                baseDirectory: path.dirname(path.resolve(path.dirname(filePath), extendsFile)),
-                filePath: path.resolve(path.dirname(filePath), extendsFile),
-                extends: extendsFile,
                 parserOptions: {},
                 env: { es6: true },
                 globals: {},
@@ -484,11 +321,9 @@ describe("ConfigFile", () => {
 
         it("should load information from a legacy file", () => {
             const configFilePath = getFixturePath("legacy/.eslintrc");
-            const config = ConfigFile.load(configFilePath, configContext);
+            const config = ConfigFile.load(configFilePath, configContext, getFixturePath("__placeholder__.js"));
 
             assert.deepStrictEqual(config, {
-                baseDirectory: path.dirname(configFilePath),
-                filePath: configFilePath,
                 parserOptions: {},
                 env: {},
                 globals: {},
@@ -500,11 +335,9 @@ describe("ConfigFile", () => {
 
         it("should load information from a JavaScript file", () => {
             const configFilePath = getFixturePath("js/.eslintrc.js");
-            const config = ConfigFile.load(configFilePath, configContext);
+            const config = ConfigFile.load(configFilePath, configContext, getFixturePath("__placeholder__.js"));
 
             assert.deepStrictEqual(config, {
-                baseDirectory: path.dirname(configFilePath),
-                filePath: configFilePath,
                 parserOptions: {},
                 env: {},
                 globals: {},
@@ -516,17 +349,15 @@ describe("ConfigFile", () => {
 
         it("should throw error when loading invalid JavaScript file", () => {
             assert.throws(() => {
-                ConfigFile.load(getFixturePath("js/.eslintrc.broken.js"), configContext);
+                ConfigFile.load(getFixturePath("js/.eslintrc.broken.js"), configContext, getFixturePath("__placeholder__.js"));
             }, /Cannot read config file/u);
         });
 
         it("should interpret parser module name when present in a JavaScript file", () => {
             const configFilePath = getFixturePath("js/.eslintrc.parser.js");
-            const config = ConfigFile.load(configFilePath, configContext);
+            const config = ConfigFile.load(configFilePath, configContext, getFixturePath("__placeholder__.js"));
 
             assert.deepStrictEqual(config, {
-                baseDirectory: path.dirname(configFilePath),
-                filePath: configFilePath,
                 parser: path.resolve(getFixturePath("js/node_modules/foo/index.js")),
                 parserOptions: {},
                 env: {},
@@ -539,11 +370,9 @@ describe("ConfigFile", () => {
 
         it("should interpret parser path when present in a JavaScript file", () => {
             const configFilePath = getFixturePath("js/.eslintrc.parser2.js");
-            const config = ConfigFile.load(configFilePath, configContext);
+            const config = ConfigFile.load(configFilePath, configContext, getFixturePath("__placeholder__.js"));
 
             assert.deepStrictEqual(config, {
-                baseDirectory: path.dirname(configFilePath),
-                filePath: configFilePath,
                 parser: path.resolve(getFixturePath("js/not-a-config.js")),
                 parserOptions: {},
                 env: {},
@@ -556,11 +385,9 @@ describe("ConfigFile", () => {
 
         it("should interpret parser module name or path when parser is set to default parser in a JavaScript file", () => {
             const configFilePath = getFixturePath("js/.eslintrc.parser3.js");
-            const config = ConfigFile.load(configFilePath, configContext);
+            const config = ConfigFile.load(configFilePath, configContext, getFixturePath("__placeholder__.js"));
 
             assert.deepStrictEqual(config, {
-                baseDirectory: path.dirname(configFilePath),
-                filePath: configFilePath,
                 parser: require.resolve("espree"),
                 parserOptions: {},
                 env: {},
@@ -573,11 +400,9 @@ describe("ConfigFile", () => {
 
         it("should load information from a JSON file", () => {
             const configFilePath = getFixturePath("json/.eslintrc.json");
-            const config = ConfigFile.load(configFilePath, configContext);
+            const config = ConfigFile.load(configFilePath, configContext, getFixturePath("__placeholder__.js"));
 
             assert.deepStrictEqual(config, {
-                baseDirectory: path.dirname(configFilePath),
-                filePath: configFilePath,
                 parserOptions: {},
                 env: {},
                 globals: {},
@@ -606,28 +431,20 @@ describe("ConfigFile", () => {
                 },
                 tmpFilename = "fresh-test.json",
                 tmpFilePath = writeTempConfigFile(initialConfig, tmpFilename);
-            let config = ConfigFile.load(tmpFilePath, configContext);
+            let config = ConfigFile.load(tmpFilePath, configContext, getFixturePath("__placeholder__.js"));
 
-            assert.deepStrictEqual(config, Object.assign({}, initialConfig, {
-                baseDirectory: path.dirname(tmpFilePath),
-                filePath: tmpFilePath
-            }));
+            assert.deepStrictEqual(config, initialConfig);
             writeTempConfigFile(updatedConfig, tmpFilename, path.dirname(tmpFilePath));
-            configContext = new Config({}, new Linter());
-            config = ConfigFile.load(tmpFilePath, configContext);
-            assert.deepStrictEqual(config, Object.assign({}, updatedConfig, {
-                baseDirectory: path.dirname(tmpFilePath),
-                filePath: tmpFilePath
-            }));
+            configContext = new Config({ cwd: getFixturePath(".") }, new Linter());
+            config = ConfigFile.load(tmpFilePath, configContext, getFixturePath("__placeholder__.js"));
+            assert.deepStrictEqual(config, updatedConfig);
         });
 
         it("should load information from a package.json file", () => {
             const configFilePath = getFixturePath("package-json/package.json");
-            const config = ConfigFile.load(configFilePath, configContext);
+            const config = ConfigFile.load(configFilePath, configContext, getFixturePath("__placeholder__.js"));
 
             assert.deepStrictEqual(config, {
-                baseDirectory: path.dirname(configFilePath),
-                filePath: configFilePath,
                 parserOptions: {},
                 env: { es6: true },
                 globals: {},
@@ -638,7 +455,7 @@ describe("ConfigFile", () => {
         it("should throw error when loading invalid package.json file", () => {
             assert.throws(() => {
                 try {
-                    ConfigFile.load(getFixturePath("broken-package-json/package.json"), configContext);
+                    ConfigFile.load(getFixturePath("broken-package-json/package.json"), configContext, getFixturePath("__placeholder__.js"));
                 } catch (error) {
                     assert.strictEqual(error.messageTemplate, "failed-to-read-json");
                     throw error;
@@ -669,19 +486,13 @@ describe("ConfigFile", () => {
                 },
                 tmpFilename = "package.json",
                 tmpFilePath = writeTempConfigFile(initialConfig, tmpFilename);
-            let config = ConfigFile.load(tmpFilePath, configContext);
+            let config = ConfigFile.load(tmpFilePath, configContext, getFixturePath("__placeholder__.js"));
 
-            assert.deepStrictEqual(config, Object.assign({}, initialConfig.eslintConfig, {
-                baseDirectory: path.dirname(tmpFilePath),
-                filePath: tmpFilePath
-            }));
+            assert.deepStrictEqual(config, initialConfig.eslintConfig);
             writeTempConfigFile(updatedConfig, tmpFilename, path.dirname(tmpFilePath));
-            configContext = new Config({}, new Linter());
-            config = ConfigFile.load(tmpFilePath, configContext);
-            assert.deepStrictEqual(config, Object.assign({}, updatedConfig.eslintConfig, {
-                baseDirectory: path.dirname(tmpFilePath),
-                filePath: tmpFilePath
-            }));
+            configContext = new Config({ cwd: getFixturePath(".") }, new Linter());
+            config = ConfigFile.load(tmpFilePath, configContext, getFixturePath("__placeholder__.js"));
+            assert.deepStrictEqual(config, updatedConfig.eslintConfig);
         });
 
         it("should load fresh information from a .eslintrc.js file", () => {
@@ -703,28 +514,20 @@ describe("ConfigFile", () => {
                 },
                 tmpFilename = ".eslintrc.js",
                 tmpFilePath = writeTempJsConfigFile(initialConfig, tmpFilename);
-            let config = ConfigFile.load(tmpFilePath, configContext);
+            let config = ConfigFile.load(tmpFilePath, configContext, getFixturePath("__placeholder__.js"));
 
-            assert.deepStrictEqual(config, Object.assign({}, initialConfig, {
-                baseDirectory: path.dirname(tmpFilePath),
-                filePath: tmpFilePath
-            }));
+            assert.deepStrictEqual(config, initialConfig);
             writeTempJsConfigFile(updatedConfig, tmpFilename, path.dirname(tmpFilePath));
-            configContext = new Config({}, new Linter());
-            config = ConfigFile.load(tmpFilePath, configContext);
-            assert.deepStrictEqual(config, Object.assign({}, updatedConfig, {
-                baseDirectory: path.dirname(tmpFilePath),
-                filePath: tmpFilePath
-            }));
+            configContext = new Config({ cwd: getFixturePath(".") }, new Linter());
+            config = ConfigFile.load(tmpFilePath, configContext, getFixturePath("__placeholder__.js"));
+            assert.deepStrictEqual(config, updatedConfig);
         });
 
         it("should load information from a YAML file", () => {
             const configFilePath = getFixturePath("yaml/.eslintrc.yaml");
-            const config = ConfigFile.load(configFilePath, configContext);
+            const config = ConfigFile.load(configFilePath, configContext, getFixturePath("__placeholder__.js"));
 
             assert.deepStrictEqual(config, {
-                baseDirectory: path.dirname(configFilePath),
-                filePath: configFilePath,
                 parserOptions: {},
                 env: { browser: true },
                 globals: {},
@@ -732,13 +535,11 @@ describe("ConfigFile", () => {
             });
         });
 
-        it("should load information from a YAML file", () => {
+        it("should load information from an empty YAML file", () => {
             const configFilePath = getFixturePath("yaml/.eslintrc.empty.yaml");
-            const config = ConfigFile.load(configFilePath, configContext);
+            const config = ConfigFile.load(configFilePath, configContext, getFixturePath("__placeholder__.js"));
 
             assert.deepStrictEqual(config, {
-                baseDirectory: path.dirname(configFilePath),
-                filePath: configFilePath,
                 parserOptions: {},
                 env: {},
                 globals: {},
@@ -748,11 +549,9 @@ describe("ConfigFile", () => {
 
         it("should load information from a YML file", () => {
             const configFilePath = getFixturePath("yml/.eslintrc.yml");
-            const config = ConfigFile.load(configFilePath, configContext);
+            const config = ConfigFile.load(configFilePath, configContext, getFixturePath("__placeholder__.js"));
 
             assert.deepStrictEqual(config, {
-                baseDirectory: path.dirname(configFilePath),
-                filePath: configFilePath,
                 parserOptions: {},
                 env: { node: true },
                 globals: {},
@@ -762,13 +561,10 @@ describe("ConfigFile", () => {
 
         it("should load information from a YML file and apply extensions", () => {
             const configFilePath = getFixturePath("extends/.eslintrc.yml");
-            const config = ConfigFile.load(configFilePath, configContext);
+            const config = ConfigFile.load(configFilePath, configContext, getFixturePath("__placeholder__.js"));
 
             assert.deepStrictEqual(config, {
-                baseDirectory: path.dirname(configFilePath),
-                filePath: configFilePath,
                 env: { es6: true },
-                extends: "../package-json/package.json",
                 globals: {},
                 parserOptions: {},
                 rules: { booya: 2 }
@@ -777,13 +573,10 @@ describe("ConfigFile", () => {
 
         it("should load information from `extends` chain.", () => {
             const configFilePath = getFixturePath("extends-chain/.eslintrc.json");
-            const config = ConfigFile.load(configFilePath, configContext);
+            const config = ConfigFile.load(configFilePath, configContext, getFixturePath("__placeholder__.js"));
 
             assert.deepStrictEqual(config, {
-                baseDirectory: path.dirname(configFilePath),
-                filePath: configFilePath,
                 env: {},
-                extends: "a",
                 globals: {},
                 parserOptions: {},
                 rules: {
@@ -796,13 +589,10 @@ describe("ConfigFile", () => {
 
         it("should load information from `extends` chain with relative path.", () => {
             const configFilePath = getFixturePath("extends-chain-2/.eslintrc.json");
-            const config = ConfigFile.load(configFilePath, configContext);
+            const config = ConfigFile.load(configFilePath, configContext, getFixturePath("__placeholder__.js"));
 
             assert.deepStrictEqual(config, {
-                baseDirectory: path.dirname(configFilePath),
-                filePath: configFilePath,
                 env: {},
-                extends: "a",
                 globals: {},
                 parserOptions: {},
                 rules: {
@@ -814,13 +604,10 @@ describe("ConfigFile", () => {
 
         it("should load information from `extends` chain in .eslintrc with relative path.", () => {
             const configFilePath = getFixturePath("extends-chain-2/relative.eslintrc.json");
-            const config = ConfigFile.load(configFilePath, configContext);
+            const config = ConfigFile.load(configFilePath, configContext, getFixturePath("__placeholder__.js"));
 
             assert.deepStrictEqual(config, {
-                baseDirectory: path.dirname(configFilePath),
-                filePath: configFilePath,
                 env: {},
-                extends: "./node_modules/eslint-config-a/index.js",
                 globals: {},
                 parserOptions: {},
                 rules: {
@@ -832,12 +619,10 @@ describe("ConfigFile", () => {
 
         it("should load information from `parser` in .eslintrc with relative path.", () => {
             const configFilePath = getFixturePath("extends-chain-2/parser.eslintrc.json");
-            const config = ConfigFile.load(configFilePath, configContext);
+            const config = ConfigFile.load(configFilePath, configContext, getFixturePath("__placeholder__.js"));
             const parserPath = getFixturePath("extends-chain-2/parser.js");
 
             assert.deepStrictEqual(config, {
-                baseDirectory: path.dirname(configFilePath),
-                filePath: configFilePath,
                 env: {},
                 globals: {},
                 parser: parserPath,
@@ -863,13 +648,10 @@ describe("ConfigFile", () => {
 
             it("should load information from `extends` chain in .eslintrc with relative path.", () => {
                 const configFilePath = path.join(fixturePath, "relative.eslintrc.json");
-                const config = ConfigFile.load(configFilePath, configContext);
+                const config = ConfigFile.load(configFilePath, configContext, getFixturePath("__placeholder__.js"));
 
                 assert.deepStrictEqual(config, {
-                    baseDirectory: path.dirname(configFilePath),
-                    filePath: configFilePath,
                     env: {},
-                    extends: "./node_modules/eslint-config-a/index.js",
                     globals: {},
                     parserOptions: {},
                     rules: {
@@ -881,12 +663,10 @@ describe("ConfigFile", () => {
 
             it("should load information from `parser` in .eslintrc with relative path.", () => {
                 const configFilePath = path.join(fixturePath, "parser.eslintrc.json");
-                const config = ConfigFile.load(configFilePath, configContext);
-                const parserPath = path.join(fixturePath, "parser.js");
+                const config = ConfigFile.load(configFilePath, configContext, getFixturePath("__placeholder__.js"));
+                const parserPath = fs.realpathSync(path.join(fixturePath, "parser.js"));
 
                 assert.deepStrictEqual(config, {
-                    baseDirectory: path.dirname(configFilePath),
-                    filePath: configFilePath,
                     env: {},
                     globals: {},
                     parser: parserPath,
@@ -897,62 +677,26 @@ describe("ConfigFile", () => {
         });
 
         describe("Plugins", () => {
-
-            overrideNativeResolve({
-                "eslint-plugin-test": getProjectModulePath("eslint-plugin-test")
-            });
-
             it("should load information from a YML file and load plugins", () => {
-
-                const stubConfig = new Config({}, new Linter());
-
-                stubConfig.plugins.define("eslint-plugin-test", {
-                    environments: {
-                        bar: { globals: { bar: true } }
-                    }
-                });
-
                 const configFilePath = getFixturePath("plugins/.eslintrc.yml");
-                const config = ConfigFile.load(configFilePath, stubConfig);
+                const config = ConfigFile.load(configFilePath, configContext, getFixturePath("__placeholder__.js"));
 
                 assert.deepStrictEqual(config, {
-                    baseDirectory: path.dirname(configFilePath),
-                    filePath: configFilePath,
                     parserOptions: {},
-                    env: { "test/bar": true },
+                    env: { "with-environment/bar": true },
                     globals: {},
-                    plugins: ["test"],
+                    plugins: ["with-environment"],
                     rules: {
-                        "test/foo": 2
+                        "with-environment/foo": 2
                     }
                 });
             });
 
             it("should load two separate configs from a plugin", () => {
-                const stubConfig = new Config({}, new Linter());
-                const resolvedPath = path.resolve(PROJECT_PATH, "./node_modules/eslint-plugin-test/index.js");
-
-                const configDeps = {
-                    "import-fresh"(filename) {
-                        return configDeps[filename];
-                    }
-                };
-
-                configDeps[resolvedPath] = {
-                    configs: {
-                        foo: { rules: { semi: 2, quotes: 1 } },
-                        bar: { rules: { quotes: 2, yoda: 2 } }
-                    }
-                };
-
-                const StubbedConfigFile = proxyquire("../../../lib/config/config-file", configDeps);
-
                 const configFilePath = getFixturePath("plugins/.eslintrc2.yml");
-                const config = StubbedConfigFile.load(configFilePath, stubConfig);
+                const config = ConfigFile.load(configFilePath, configContext, getFixturePath("__placeholder__.js"));
 
                 assert.deepStrictEqual(config, {
-                    baseDirectory: path.dirname(configFilePath),
-                    filePath: configFilePath,
                     parserOptions: {},
                     globals: {},
                     env: {},
@@ -960,11 +704,7 @@ describe("ConfigFile", () => {
                         semi: 2,
                         quotes: 2,
                         yoda: 2
-                    },
-                    extends: [
-                        "plugin:test/foo",
-                        "plugin:test/bar"
-                    ]
+                    }
                 });
             });
         });
@@ -972,11 +712,9 @@ describe("ConfigFile", () => {
         describe("even if config files have Unicode BOM,", () => {
             it("should read the JSON config file correctly.", () => {
                 const configFilePath = getFixturePath("bom/.eslintrc.json");
-                const config = ConfigFile.load(configFilePath, configContext);
+                const config = ConfigFile.load(configFilePath, configContext, getFixturePath("__placeholder__.js"));
 
                 assert.deepStrictEqual(config, {
-                    baseDirectory: path.dirname(configFilePath),
-                    filePath: configFilePath,
                     env: {},
                     globals: {},
                     parserOptions: {},
@@ -988,11 +726,9 @@ describe("ConfigFile", () => {
 
             it("should read the YAML config file correctly.", () => {
                 const configFilePath = getFixturePath("bom/.eslintrc.yaml");
-                const config = ConfigFile.load(configFilePath, configContext);
+                const config = ConfigFile.load(configFilePath, configContext, getFixturePath("__placeholder__.js"));
 
                 assert.deepStrictEqual(config, {
-                    baseDirectory: path.dirname(configFilePath),
-                    filePath: configFilePath,
                     env: {},
                     globals: {},
                     parserOptions: {},
@@ -1004,11 +740,9 @@ describe("ConfigFile", () => {
 
             it("should read the config in package.json correctly.", () => {
                 const configFilePath = getFixturePath("bom/package.json");
-                const config = ConfigFile.load(configFilePath, configContext);
+                const config = ConfigFile.load(configFilePath, configContext, getFixturePath("__placeholder__.js"));
 
                 assert.deepStrictEqual(config, {
-                    baseDirectory: path.dirname(configFilePath),
-                    filePath: configFilePath,
                     env: {},
                     globals: {},
                     parserOptions: {},
@@ -1023,7 +757,7 @@ describe("ConfigFile", () => {
             const configFilePath = getFixturePath("invalid/invalid-top-level-property.yml");
 
             try {
-                ConfigFile.load(configFilePath, configContext);
+                ConfigFile.load(configFilePath, configContext, getFixturePath("__placeholder__.js"));
             } catch (err) {
                 assert.include(err.message, `ESLint configuration in ${configFilePath} is invalid`);
                 return;
@@ -1032,169 +766,22 @@ describe("ConfigFile", () => {
         });
     });
 
-    describe("resolve()", () => {
-
-        describe("Relative to CWD", () => {
-            overrideNativeResolve({
-                "eslint-plugin-foo": getProjectModulePath("eslint-plugin-foo")
-            });
-
-            leche.withData([
-                [".eslintrc", path.resolve(".eslintrc")],
-                ["eslint-config-foo", getProjectModulePath("eslint-config-foo")],
-                ["foo", getProjectModulePath("eslint-config-foo")],
-                ["eslint-configfoo", getProjectModulePath("eslint-config-eslint-configfoo")],
-                ["@foo/eslint-config", getProjectModulePath("@foo/eslint-config")],
-                ["@foo/bar", getProjectModulePath("@foo/eslint-config-bar")],
-                ["plugin:foo/bar", getProjectModulePath("eslint-plugin-foo")]
-            ], (input, expected) => {
-                it(`should return ${expected} when passed ${input}`, () => {
-
-                    const configDeps = {
-                        "eslint-config-foo": getProjectModulePath("eslint-config-foo"),
-                        "eslint-config-eslint-configfoo": getProjectModulePath("eslint-config-eslint-configfoo"),
-                        "@foo/eslint-config": getProjectModulePath("@foo/eslint-config"),
-                        "@foo/eslint-config-bar": getProjectModulePath("@foo/eslint-config-bar"),
-                        "eslint-plugin-foo": getProjectModulePath("eslint-plugin-foo")
-                    };
-
-                    const StubbedConfigFile = proxyquire("../../../lib/config/config-file", {
-                        "../util/module-resolver": createStubModuleResolver(configDeps)
-                    });
-
-                    const result = StubbedConfigFile.resolve(input);
-
-                    assert.strictEqual(result.filePath, expected);
-                });
-            });
-        });
-
-        describe("Relative to config file", () => {
-
-            const relativePath = path.resolve("./foo/bar");
-
-            overrideNativeResolve({
-                "@foo/eslint-plugin-bar": getProjectModulePath("@foo/eslint-plugin-bar")
-            });
-
-            leche.withData([
-                [".eslintrc", path.resolve("./foo/bar", ".eslintrc"), relativePath],
-                ["eslint-config-foo", getRelativeModulePath("eslint-config-foo", relativePath), relativePath],
-                ["foo", getRelativeModulePath("eslint-config-foo", relativePath), relativePath],
-                ["eslint-configfoo", getRelativeModulePath("eslint-config-eslint-configfoo", relativePath), relativePath],
-                ["@foo/eslint-config", getRelativeModulePath("@foo/eslint-config", relativePath), relativePath],
-                ["@foo/bar", getRelativeModulePath("@foo/eslint-config-bar", relativePath), relativePath],
-                ["plugin:@foo/bar/baz", getProjectModulePath("@foo/eslint-plugin-bar"), relativePath]
-            ], (input, expected, relativeTo) => {
-                it(`should return ${expected} when passed ${input}`, () => {
-
-                    const configDeps = {
-                        "eslint-config-foo": getRelativeModulePath("eslint-config-foo", relativePath),
-                        "eslint-config-eslint-configfoo": getRelativeModulePath("eslint-config-eslint-configfoo", relativePath),
-                        "@foo/eslint-config": getRelativeModulePath("@foo/eslint-config", relativePath),
-                        "@foo/eslint-config-bar": getRelativeModulePath("@foo/eslint-config-bar", relativePath)
-                    };
-
-                    const StubbedConfigFile = proxyquire("../../../lib/config/config-file", {
-                        "../util/module-resolver": createStubModuleResolver(configDeps)
-                    });
-
-                    const result = StubbedConfigFile.resolve(input, relativeTo);
-
-                    assert.strictEqual(result.filePath, expected);
-                });
-            });
-
-            leche.withData([
-                ["eslint-config-foo/bar", path.resolve("./node_modules", "eslint-config-foo/bar", "index.json"), relativePath],
-                ["eslint-config-foo/bar", path.resolve("./node_modules", "eslint-config-foo", "bar.json"), relativePath],
-                ["eslint-config-foo/bar", path.resolve("./node_modules", "eslint-config-foo/bar", "index.js"), relativePath],
-                ["eslint-config-foo/bar", path.resolve("./node_modules", "eslint-config-foo", "bar.js"), relativePath]
-            ], (input, expected, relativeTo) => {
-                it(`should return ${expected} when passed ${input}`, () => {
-
-                    const configDeps = {
-                        "eslint-config-foo/bar": expected
-                    };
-
-                    const StubbedConfigFile = proxyquire("../../../lib/config/config-file", {
-                        "../util/module-resolver": createStubModuleResolver(configDeps)
-                    });
-
-                    const result = StubbedConfigFile.resolve(input, relativeTo);
-
-                    assert.strictEqual(result.filePath, expected);
-                });
-            });
-
-        });
-
-    });
-
-    describe("getBaseDir()", () => {
-
-        // can only run this test if there's a home directory
-        if (userHome) {
-
-            it("should return project path when config file is in home directory", () => {
-                const result = ConfigFile.getBaseDir(userHome);
-
-                assert.strictEqual(result, PROJECT_PATH);
-            });
-        }
-
-        it("should return project path when config file is in an ancestor directory of the project path", () => {
-            const result = ConfigFile.getBaseDir(path.resolve(PROJECT_PATH, "../../"));
-
-            assert.strictEqual(result, PROJECT_PATH);
-        });
-
-        it("should return config file path when config file is in a descendant directory of the project path", () => {
-            const configFilePath = path.resolve(PROJECT_PATH, "./foo/bar/"),
-                result = ConfigFile.getBaseDir(path.resolve(PROJECT_PATH, "./foo/bar/"));
-
-            assert.strictEqual(result, configFilePath);
-        });
-
-        it("should return project path when config file is not an ancestor or descendant of the project path", () => {
-            const result = ConfigFile.getBaseDir(path.resolve("/tmp/foo"));
-
-            assert.strictEqual(result, PROJECT_PATH);
-        });
-
-    });
-
-    describe("getLookupPath()", () => {
-
-        // can only run this test if there's a home directory
-        if (userHome) {
-
-            it("should return project path when config file is in home directory", () => {
-                const result = ConfigFile.getLookupPath(userHome);
+    describe("resolve() relative to config file", () => {
+        leche.withData([
+            [".eslintrc", getFixturePath("subdir/.eslintrc")],
+            ["eslint-config-foo", relativeModuleResolver("eslint-config-foo", getFixturePath("subdir/__placeholder__.js"))],
+            ["foo", relativeModuleResolver("eslint-config-foo", getFixturePath("subdir/__placeholder__.js"))],
+            ["eslint-configfoo", relativeModuleResolver("eslint-config-eslint-configfoo", getFixturePath("subdir/__placeholder__.js"))],
+            ["@foo/eslint-config", relativeModuleResolver("@foo/eslint-config", getFixturePath("subdir/__placeholder__.js"))],
+            ["@foo/bar", relativeModuleResolver("@foo/eslint-config-bar", getFixturePath("subdir/__placeholder__.js"))],
+            ["plugin:foo/bar", relativeModuleResolver("eslint-plugin-foo", getFixturePath("__placeholder__.js"))]
+        ], (input, expected) => {
+            it(`should return ${expected} when passed ${input}`, () => {
+                const result = ConfigFile.resolve(input, getFixturePath("subdir/__placeholder__.js"), getFixturePath("."));
 
-                assert.strictEqual(result, PROJECT_DEPS_PATH);
+                assert.strictEqual(result.filePath, expected);
             });
-        }
-
-        it("should return project path when config file is in an ancestor directory of the project path", () => {
-            const result = ConfigFile.getLookupPath(path.resolve(PROJECT_DEPS_PATH, "../../"));
-
-            assert.strictEqual(result, PROJECT_DEPS_PATH);
         });
-
-        it("should return config file path when config file is in a descendant directory of the project path", () => {
-            const configFilePath = path.resolve(PROJECT_DEPS_PATH, "./foo/bar/node_modules"),
-                result = ConfigFile.getLookupPath(path.resolve(PROJECT_DEPS_PATH, "./foo/bar/"));
-
-            assert.strictEqual(result, configFilePath);
-        });
-
-        it("should return project path when config file is not an ancestor or descendant of the project path", () => {
-            const result = ConfigFile.getLookupPath(path.resolve("/tmp/foo"));
-
-            assert.strictEqual(result, PROJECT_DEPS_PATH);
-        });
-
     });
 
     describe("getFilenameFromDirectory()", () => {
diff --git a/tests/lib/config/config-initializer.js b/tests/lib/config/config-initializer.js
index d78651b1b..d306db1b1 100644
--- a/tests/lib/config/config-initializer.js
+++ b/tests/lib/config/config-initializer.js
@@ -42,17 +42,11 @@ describe("configInitializer", () => {
     };
     const requireStubs = {
         "../util/logging": log,
-        "../util/module-resolver": class ModuleResolver {
-
-            /**
-             * @returns {string} The path to local eslint to test.
-             */
-            resolve() { // eslint-disable-line class-methods-use-this
-                if (localESLintVersion) {
-                    return `local-eslint-${localESLintVersion}`;
-                }
-                throw new Error("Cannot find module");
+        "../util/relative-module-resolver"() {
+            if (localESLintVersion) {
+                return `local-eslint-${localESLintVersion}`;
             }
+            throw new Error("Cannot find module");
         },
         "local-eslint-3.18.0": { linter: { version: "3.18.0" }, "@noCallThru": true },
         "local-eslint-3.19.0": { linter: { version: "3.19.0" }, "@noCallThru": true },
diff --git a/tests/lib/config/config-ops.js b/tests/lib/config/config-ops.js
index 8d5ad5fa5..ffa65660d 100644
--- a/tests/lib/config/config-ops.js
+++ b/tests/lib/config/config-ops.js
@@ -911,12 +911,18 @@ describe("ConfigOps", () => {
             ["writable", "writeable"],
             ["readable", "readable"],
             ["readonly", "readable"],
-            ["writable", "writeable"],
-            ["something else", "writeable"]
+            ["writable", "writeable"]
         ].forEach(([input, output]) => {
             it(util.inspect(input), () => {
                 assert.strictEqual(ConfigOps.normalizeConfigGlobal(input), output);
             });
         });
+
+        it("throws on other inputs", () => {
+            assert.throws(
+                () => ConfigOps.normalizeConfigGlobal("something else"),
+                /^'something else' is not a valid configuration for a global \(use 'readonly', 'writable', or 'off'\)$/u
+            );
+        });
     });
 });
diff --git a/tests/lib/config/config-validator.js b/tests/lib/config/config-validator.js
index 9d4c39baf..da4d79f32 100644
--- a/tests/lib/config/config-validator.js
+++ b/tests/lib/config/config-validator.js
@@ -115,7 +115,7 @@ describe("Validator", () => {
             validator.validate(
                 {
                     root: true,
-                    globals: { globalFoo: "bar" },
+                    globals: { globalFoo: "readonly" },
                     parser: "parserFoo",
                     env: { browser: true },
                     plugins: ["pluginFoo", "pluginBar"],
@@ -325,6 +325,15 @@ describe("Validator", () => {
             });
         });
 
+        describe("globals", () => {
+            it("should disallow globals set to invalid values", () => {
+                assert.throws(
+                    () => validator.validate({ globals: { foo: "AAAAA" } }, () => {}, linter.environments, "tests"),
+                    "ESLint configuration of global 'foo' in tests is invalid:\n'AAAAA' is not a valid configuration for a global (use 'readonly', 'writable', or 'off')"
+                );
+            });
+        });
+
         describe("overrides", () => {
             it("should not throw with an empty overrides array", () => {
                 validator.validate({ overrides: [] }, ruleMapper, linter.environments, "tests");
diff --git a/tests/lib/config/plugins.js b/tests/lib/config/plugins.js
index cb5e14b35..ecaaccc9e 100644
--- a/tests/lib/config/plugins.js
+++ b/tests/lib/config/plugins.js
@@ -9,11 +9,10 @@
 //------------------------------------------------------------------------------
 
 const assert = require("chai").assert,
+    path = require("path"),
     Plugins = require("../../../lib/config/plugins"),
     Environments = require("../../../lib/config/environments");
 
-const proxyquire = require("proxyquire").noCallThru().noPreserveCache();
-
 //------------------------------------------------------------------------------
 // Tests
 //------------------------------------------------------------------------------
@@ -23,42 +22,37 @@ describe("Plugins", () => {
     describe("get", () => {
 
         it("should return null when plugin doesn't exist", () => {
-            assert.isNull((new Plugins(new Environments(), () => {})).get("foo"));
+            assert.isNull((new Plugins(new Environments(), { defineRule: () => {}, pluginRootPath: process.cwd() })).get("foo"));
         });
     });
 
     describe("load()", () => {
 
-        let StubbedPlugins,
+        let RelativeLoadedPlugins,
             rules,
             environments,
             plugin,
             scopedPlugin;
 
         beforeEach(() => {
-            plugin = {};
-            scopedPlugin = {};
+            plugin = require("../../fixtures/plugins/node_modules/eslint-plugin-example");
+            scopedPlugin = require("../../fixtures/plugins/node_modules/@scope/eslint-plugin-example");
             environments = new Environments();
             rules = new Map();
-            StubbedPlugins = new (proxyquire("../../../lib/config/plugins", {
-                "eslint-plugin-example": plugin,
-                "@scope/eslint-plugin-example": scopedPlugin,
-                "eslint-plugin-throws-on-load": {
-                    get rules() {
-                        throw new Error("error thrown while loading this module");
-                    }
-                }
-            }))(environments, rules.set.bind(rules));
+            RelativeLoadedPlugins = new Plugins(environments, {
+                defineRule: rules.set.bind(rules),
+                pluginRootPath: path.resolve(__dirname, "..", "..", "fixtures", "plugins")
+            });
         });
 
         it("should load a plugin when referenced by short name", () => {
-            StubbedPlugins.load("example");
-            assert.strictEqual(StubbedPlugins.get("example"), plugin);
+            RelativeLoadedPlugins.load("example");
+            assert.strictEqual(RelativeLoadedPlugins.get("example"), plugin);
         });
 
         it("should load a plugin when referenced by long name", () => {
-            StubbedPlugins.load("eslint-plugin-example");
-            assert.strictEqual(StubbedPlugins.get("example"), plugin);
+            RelativeLoadedPlugins.load("eslint-plugin-example");
+            assert.strictEqual(RelativeLoadedPlugins.get("example"), plugin);
         });
 
         it("should register environments when plugin has environments", () => {
@@ -71,7 +65,7 @@ describe("Plugins", () => {
                 }
             };
 
-            StubbedPlugins.load("eslint-plugin-example");
+            RelativeLoadedPlugins.load("eslint-plugin-example");
 
             assert.deepStrictEqual(environments.get("example/foo"), plugin.environments.foo);
             assert.deepStrictEqual(environments.get("example/bar"), plugin.environments.bar);
@@ -83,7 +77,7 @@ describe("Plugins", () => {
                 qux: {}
             };
 
-            StubbedPlugins.load("eslint-plugin-example");
+            RelativeLoadedPlugins.load("eslint-plugin-example");
 
             assert.deepStrictEqual(rules.get("example/baz"), plugin.rules.baz);
             assert.deepStrictEqual(rules.get("example/qux"), plugin.rules.qux);
@@ -91,28 +85,28 @@ describe("Plugins", () => {
 
         it("should throw an error when a plugin has whitespace", () => {
             assert.throws(() => {
-                StubbedPlugins.load("whitespace ");
+                RelativeLoadedPlugins.load("whitespace ");
             }, /Whitespace found in plugin name 'whitespace '/u);
             assert.throws(() => {
-                StubbedPlugins.load("whitespace\t");
+                RelativeLoadedPlugins.load("whitespace\t");
             }, /Whitespace found in plugin name/u);
             assert.throws(() => {
-                StubbedPlugins.load("whitespace\n");
+                RelativeLoadedPlugins.load("whitespace\n");
             }, /Whitespace found in plugin name/u);
             assert.throws(() => {
-                StubbedPlugins.load("whitespace\r");
+                RelativeLoadedPlugins.load("whitespace\r");
             }, /Whitespace found in plugin name/u);
         });
 
         it("should throw an error when a plugin doesn't exist", () => {
             assert.throws(() => {
-                StubbedPlugins.load("nonexistentplugin");
+                RelativeLoadedPlugins.load("nonexistentplugin");
             }, /Failed to load plugin/u);
         });
 
         it("should rethrow an error that a plugin throws on load", () => {
             try {
-                StubbedPlugins.load("throws-on-load");
+                RelativeLoadedPlugins.load("throws-on-load");
             } catch (err) {
                 assert.strictEqual(
                     err.message,
@@ -126,20 +120,20 @@ describe("Plugins", () => {
         });
 
         it("should load a scoped plugin when referenced by short name", () => {
-            StubbedPlugins.load("@scope/example");
-            assert.strictEqual(StubbedPlugins.get("@scope/example"), scopedPlugin);
+            RelativeLoadedPlugins.load("@scope/example");
+            assert.strictEqual(RelativeLoadedPlugins.get("@scope/example"), scopedPlugin);
         });
 
         it("should load a scoped plugin when referenced by long name", () => {
-            StubbedPlugins.load("@scope/eslint-plugin-example");
-            assert.strictEqual(StubbedPlugins.get("@scope/example"), scopedPlugin);
+            RelativeLoadedPlugins.load("@scope/eslint-plugin-example");
+            assert.strictEqual(RelativeLoadedPlugins.get("@scope/example"), scopedPlugin);
         });
 
         it("should register environments when scoped plugin has environments", () => {
             scopedPlugin.environments = {
                 foo: {}
             };
-            StubbedPlugins.load("@scope/eslint-plugin-example");
+            RelativeLoadedPlugins.load("@scope/eslint-plugin-example");
 
             assert.strictEqual(environments.get("@scope/example/foo"), scopedPlugin.environments.foo);
         });
@@ -148,27 +142,27 @@ describe("Plugins", () => {
             scopedPlugin.rules = {
                 foo: {}
             };
-            StubbedPlugins.load("@scope/eslint-plugin-example");
+            RelativeLoadedPlugins.load("@scope/eslint-plugin-example");
 
             assert.strictEqual(rules.get("@scope/example/foo"), scopedPlugin.rules.foo);
         });
 
         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.strictEqual(StubbedPlugins.get("example"), null);
+                RelativeLoadedPlugins.load("@scope/example");
+                assert.strictEqual(RelativeLoadedPlugins.get("example"), null);
             });
 
             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.strictEqual(StubbedPlugins.get("example"), null);
+                RelativeLoadedPlugins.load("@scope/eslint-plugin-example");
+                assert.strictEqual(RelativeLoadedPlugins.get("example"), null);
             });
 
             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");
+                RelativeLoadedPlugins.load("@scope/eslint-plugin-example");
 
                 assert.strictEqual(environments.get("example/foo"), null);
             });
@@ -177,7 +171,7 @@ describe("Plugins", () => {
                 scopedPlugin.rules = {
                     foo: {}
                 };
-                StubbedPlugins.load("@scope/eslint-plugin-example");
+                RelativeLoadedPlugins.load("@scope/eslint-plugin-example");
 
                 assert.isFalse(rules.has("example/foo"));
             });
@@ -186,26 +180,26 @@ describe("Plugins", () => {
 
     describe("loadAll()", () => {
 
-        let StubbedPlugins,
+        let RelativeLoadedPlugins,
             plugin1,
             plugin2,
             rules;
         const environments = new Environments();
 
         beforeEach(() => {
-            plugin1 = {};
-            plugin2 = {};
+            plugin1 = require("../../fixtures/plugins/node_modules/eslint-plugin-example1");
+            plugin2 = require("../../fixtures/plugins/node_modules/eslint-plugin-example2");
             rules = new Map();
-            StubbedPlugins = new (proxyquire("../../../lib/config/plugins", {
-                "eslint-plugin-example1": plugin1,
-                "eslint-plugin-example2": plugin2
-            }))(environments, rules.set.bind(rules));
+            RelativeLoadedPlugins = new Plugins(environments, {
+                defineRule: rules.set.bind(rules),
+                pluginRootPath: path.resolve(__dirname, "..", "..", "fixtures", "plugins")
+            });
         });
 
         it("should load plugins when passed multiple plugins", () => {
-            StubbedPlugins.loadAll(["example1", "example2"]);
-            assert.strictEqual(StubbedPlugins.get("example1"), plugin1);
-            assert.strictEqual(StubbedPlugins.get("example2"), plugin2);
+            RelativeLoadedPlugins.loadAll(["example1", "example2"]);
+            assert.strictEqual(RelativeLoadedPlugins.get("example1"), plugin1);
+            assert.strictEqual(RelativeLoadedPlugins.get("example2"), plugin2);
         });
 
         it("should load environments from plugins when passed multiple plugins", () => {
@@ -217,7 +211,7 @@ describe("Plugins", () => {
                 bar: {}
             };
 
-            StubbedPlugins.loadAll(["example1", "example2"]);
+            RelativeLoadedPlugins.loadAll(["example1", "example2"]);
             assert.strictEqual(environments.get("example1/foo"), plugin1.environments.foo);
             assert.strictEqual(environments.get("example2/bar"), plugin2.environments.bar);
         });
@@ -231,13 +225,13 @@ describe("Plugins", () => {
                 bar: {}
             };
 
-            StubbedPlugins.loadAll(["example1", "example2"]);
+            RelativeLoadedPlugins.loadAll(["example1", "example2"]);
             assert.strictEqual(rules.get("example1/foo"), plugin1.rules.foo);
             assert.strictEqual(rules.get("example2/bar"), plugin2.rules.bar);
         });
 
         it("should throw an error if plugins is not an array", () => {
-            assert.throws(() => StubbedPlugins.loadAll("example1"), "\"plugins\" value must be an array");
+            assert.throws(() => RelativeLoadedPlugins.loadAll("example1"), "\"plugins\" value must be an array");
         });
 
     });
diff --git a/tests/lib/linter.js b/tests/lib/linter.js
index e31b61d95..bf8812c7b 100644
--- a/tests/lib/linter.js
+++ b/tests/lib/linter.js
@@ -2,7 +2,6 @@
  * @fileoverview Tests for eslint object.
  * @author Nicholas C. Zakas
  */
-/* globals window */
 
 "use strict";
 
@@ -18,14 +17,13 @@
  * @private
  */
 function compatRequire(name, windowName) {
-    if (typeof window === "object") {
-        return window[windowName || name];
+    if (typeof window === "object") { // eslint-disable-line no-undef
+        return window[windowName || name]; // eslint-disable-line no-undef
     }
     if (typeof require === "function") {
         return require(name);
     }
     throw new Error(`Cannot find object '${name}'.`);
-
 }
 
 //------------------------------------------------------------------------------
@@ -34,7 +32,8 @@ function compatRequire(name, windowName) {
 
 const assert = require("chai").assert,
     sinon = require("sinon"),
-    path = require("path");
+    esprima = require("esprima"),
+    testParsers = require("../fixtures/parsers/linter-test-parsers");
 
 const Linter = compatRequire("../../lib/linter", "eslint");
 
@@ -60,6 +59,14 @@ function getVariable(scope, name) {
     return scope.variables.find(v => v.name === name) || null;
 }
 
+/**
+ * `eslint-env` comments are processed by doing a full source text match before parsing.
+ * As a result, if this source file contains `eslint- env` followed by an environment in a string,
+ * it will actually enable the given envs for this source file. This variable is used to avoid having a string
+ * like that appear in the code.
+ */
+const ESLINT_ENV = "eslint-env";
+
 //------------------------------------------------------------------------------
 // Tests
 //------------------------------------------------------------------------------
@@ -947,7 +954,7 @@ describe("Linter", () => {
             const parser = {
                 parseForESLint: function parse(code, options) {
                     return {
-                        ast: require("espree").parse(code, options),
+                        ast: esprima.parse(code, options),
                         services: {
                             test: {
                                 getMessage() {
@@ -970,74 +977,69 @@ describe("Linter", () => {
 
     describe("when config has parser", () => {
 
-        // custom parser unsupported in browser, only test in Node
-        if (typeof window === "undefined") {
-            it("should pass parser as parserPath to all rules when provided on config", () => {
-
-                const alternateParser = "esprima";
+        it("should pass parser as parserPath to all rules when provided on config", () => {
 
-                linter.defineRule("test-rule", sandbox.mock().withArgs(
-                    sinon.match({ parserPath: alternateParser })
-                ).returns({}));
+            const alternateParser = "esprima";
 
-                const config = { rules: { "test-rule": 2 }, parser: alternateParser };
-
-                linter.verify("0", config, filename);
-            });
-
-            it("should use parseForESLint() in custom parser when custom parser is specified", () => {
+            linter.defineParser("esprima", esprima);
+            linter.defineRule("test-rule", sandbox.mock().withArgs(
+                sinon.match({ parserPath: alternateParser })
+            ).returns({}));
 
-                const alternateParser = path.resolve(__dirname, "../fixtures/parsers/enhanced-parser.js");
-                const config = { rules: {}, parser: alternateParser };
-                const messages = linter.verify("0", config, filename);
+            const config = { rules: { "test-rule": 2 }, parser: alternateParser };
 
-                assert.strictEqual(messages.length, 0);
-            });
+            linter.verify("0", config, filename);
+        });
 
-            it("should expose parser services when using parseForESLint() and services are specified", () => {
+        it("should use parseForESLint() in custom parser when custom parser is specified", () => {
+            const config = { rules: {}, parser: "enhanced-parser" };
 
-                const alternateParser = path.resolve(__dirname, "../fixtures/parsers/enhanced-parser.js");
+            linter.defineParser("enhanced-parser", testParsers.enhancedParser);
+            const messages = linter.verify("0", config, filename);
 
-                linter.defineRule("test-service-rule", context => ({
-                    Literal(node) {
-                        context.report({
-                            node,
-                            message: context.parserServices.test.getMessage()
-                        });
-                    }
-                }));
+            assert.strictEqual(messages.length, 0);
+        });
 
-                const config = { rules: { "test-service-rule": 2 }, parser: alternateParser };
-                const messages = linter.verify("0", config, filename);
+        it("should expose parser services when using parseForESLint() and services are specified", () => {
+            linter.defineParser("enhanced-parser", testParsers.enhancedParser);
+            linter.defineRule("test-service-rule", context => ({
+                Literal(node) {
+                    context.report({
+                        node,
+                        message: context.parserServices.test.getMessage()
+                    });
+                }
+            }));
 
-                assert.strictEqual(messages.length, 1);
-                assert.strictEqual(messages[0].message, "Hi!");
-            });
+            const config = { rules: { "test-service-rule": 2 }, parser: "enhanced-parser" };
+            const messages = linter.verify("0", config, filename);
 
-            it("should use the same parserServices if source code object is reused", () => {
-                const parser = path.resolve(__dirname, "../fixtures/parsers/enhanced-parser.js");
+            assert.strictEqual(messages.length, 1);
+            assert.strictEqual(messages[0].message, "Hi!");
+        });
 
-                linter.defineRule("test-service-rule", context => ({
-                    Literal(node) {
-                        context.report({
-                            node,
-                            message: context.parserServices.test.getMessage()
-                        });
-                    }
-                }));
+        it("should use the same parserServices if source code object is reused", () => {
+            linter.defineParser("enhanced-parser", testParsers.enhancedParser);
+            linter.defineRule("test-service-rule", context => ({
+                Literal(node) {
+                    context.report({
+                        node,
+                        message: context.parserServices.test.getMessage()
+                    });
+                }
+            }));
 
-                const config = { rules: { "test-service-rule": 2 }, parser };
-                const messages = linter.verify("0", config, filename);
+            const config = { rules: { "test-service-rule": 2 }, parser: "enhanced-parser" };
+            const messages = linter.verify("0", config, filename);
 
-                assert.strictEqual(messages.length, 1);
-                assert.strictEqual(messages[0].message, "Hi!");
+            assert.strictEqual(messages.length, 1);
+            assert.strictEqual(messages[0].message, "Hi!");
 
-                const messages2 = linter.verify(linter.getSourceCode(), config, filename);
+            const messages2 = linter.verify(linter.getSourceCode(), config, filename);
 
-                assert.strictEqual(messages2.length, 1);
-                assert.strictEqual(messages2[0].message, "Hi!");
-            });
-        }
+            assert.strictEqual(messages2.length, 1);
+            assert.strictEqual(messages2[0].message, "Hi!");
+        });
 
         it("should pass parser as parserPath to all rules when default parser is used", () => {
             linter.defineRule("test-rule", sandbox.mock().withArgs(
@@ -1207,7 +1209,7 @@ describe("Linter", () => {
 
     describe("when evaluating code containing /*eslint-env */ block", () => {
         it("variables should be available in global scope", () => {
-            const code = "/*eslint-env node*/ function f() {} /*eslint-env browser, foo*/";
+            const code = `/*${ESLINT_ENV} node*/ function f() {} /*${ESLINT_ENV} browser, foo*/`;
             const config = { rules: { checker: "error" } };
             let spy;
 
@@ -1230,7 +1232,7 @@ describe("Linter", () => {
     });
 
     describe("when evaluating code containing /*eslint-env */ block with sloppy whitespace", () => {
-        const code = "/* eslint-env ,, node  , no-browser ,,  */";
+        const code = `/* ${ESLINT_ENV} ,, node  , no-browser ,,  */`;
 
         it("variables should be available in global scope", () => {
             const config = { rules: { checker: "error" } };
@@ -2890,7 +2892,7 @@ describe("Linter", () => {
         });
 
         it("should not report a violation", () => {
-            const code = "/*eslint-env mocha,node */ require();describe();";
+            const code = `/*${ESLINT_ENV} mocha,node */ require();describe();`;
 
             const config = { rules: { "no-undef": 1 } };
 
@@ -2910,7 +2912,7 @@ describe("Linter", () => {
         });
 
         it("should not report a violation", () => {
-            const code = "/*eslint-env amd */ define();require();";
+            const code = `/*${ESLINT_ENV} amd */ define();require();`;
 
             const config = { rules: { "no-undef": 1 } };
 
@@ -2920,7 +2922,7 @@ describe("Linter", () => {
         });
 
         it("should not report a violation", () => {
-            const code = "/*eslint-env jasmine */ expect();spyOn();";
+            const code = `/*${ESLINT_ENV} jasmine */ expect();spyOn();`;
 
             const config = { rules: { "no-undef": 1 } };
 
@@ -2930,7 +2932,7 @@ describe("Linter", () => {
         });
 
         it("should not report a violation", () => {
-            const code = "/*globals require: true */ /*eslint-env node */ require = 1;";
+            const code = `/*globals require: true */ /*${ESLINT_ENV} node */ require = 1;`;
 
             const config = { rules: { "no-undef": 1 } };
 
@@ -2940,7 +2942,7 @@ describe("Linter", () => {
         });
 
         it("should not report a violation", () => {
-            const code = "/*eslint-env node */ process.exit();";
+            const code = `/*${ESLINT_ENV} node */ process.exit();`;
 
             const config = { rules: {} };
 
@@ -2950,7 +2952,7 @@ describe("Linter", () => {
         });
 
         it("should not report a violation", () => {
-            const code = "/*eslint no-process-exit: 0 */ /*eslint-env node */ process.exit();";
+            const code = `/*eslint no-process-exit: 0 */ /*${ESLINT_ENV} node */ process.exit();`;
 
             const config = { rules: { "no-undef": 1 } };
 
@@ -3075,7 +3077,7 @@ describe("Linter", () => {
 
         it("should report a violation for env changes", () => {
             const code = [
-                "/*eslint-env browser*/"
+                `/*${ESLINT_ENV} browser*/`
             ].join("\n");
             const config = {
                 rules: {
@@ -3445,8 +3447,8 @@ describe("Linter", () => {
             assert.strictEqual(messages.length, 0);
         });
 
-        it("should be able to return in global if there is a comment which has \"eslint-env node\"", () => {
-            const messages = linter.verify("/* eslint-env node */ return;", null, "eslint-env node");
+        it("should be able to return in global if there is a comment which enables the node environment with a comment", () => {
+            const messages = linter.verify(`/* ${ESLINT_ENV} node */ return;`, null, "node environment");
 
             assert.strictEqual(messages.length, 0);
         });
@@ -3490,6 +3492,34 @@ describe("Linter", () => {
             assert(ok);
         });
 
+        it("should report a linting error when a global is set to an invalid value", () => {
+            const results = linter.verify("/* global foo: AAAAA, bar: readonly */\nfoo;\nbar;", { rules: { "no-undef": "error" } });
+
+            assert.deepStrictEqual(results, [
+                {
+                    ruleId: null,
+                    severity: 2,
+                    message: "'AAAAA' is not a valid configuration for a global (use 'readonly', 'writable', or 'off')",
+                    line: 1,
+                    column: 1,
+                    endLine: 1,
+                    endColumn: 39,
+                    nodeType: null
+                },
+                {
+                    ruleId: "no-undef",
+                    messageId: "undef",
+                    severity: 2,
+                    message: "'foo' is not defined.",
+                    line: 2,
+                    column: 1,
+                    endLine: 2,
+                    endColumn: 4,
+                    nodeType: "Identifier"
+                }
+            ]);
+        });
+
         it("should not crash when we reuse the SourceCode object", () => {
             linter.verify("function render() { return <div className='test'>{hello}</div> }", { parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } } });
             linter.verify(linter.getSourceCode(), { parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } } });
@@ -3545,15 +3575,6 @@ describe("Linter", () => {
             linter.verify("x", { rules: { "foo-bar-baz": "error" } });
             assert(spy.calledOnce);
         });
-
-        it("should parse ES2018 code for backward compatibility if 'parserOptions.ecmaFeatures.experimentalObjectRestSpread' is given.", () => {
-            const messages = linter.verify(
-                "async function* f() { let {a, ...rest} = { a, ...obj }; }",
-                { parserOptions: { ecmaFeatures: { experimentalObjectRestSpread: true } } }
-            );
-
-            assert(messages.length === 0);
-        });
     });
 
     describe("context.getScope()", () => {
@@ -4472,196 +4493,186 @@ describe("Linter", () => {
         });
     });
 
-    // only test in Node.js, not browser
-    if (typeof window === "undefined") {
-        const escope = require("eslint-scope");
-        const vk = require("eslint-visitor-keys");
+    describe("Custom parser", () => {
 
-        describe("Custom parser", () => {
+        const errorPrefix = "Parsing error: ";
 
-            const parserFixtures = path.join(__dirname, "../fixtures/parsers"),
-                errorPrefix = "Parsing error: ";
+        it("should have file path passed to it", () => {
+            const code = "/* this is code */";
+            const parseSpy = sinon.spy(testParsers.stubParser, "parse");
 
-            it("should have file path passed to it", () => {
-                const code = "/* this is code */";
-                const parser = path.join(parserFixtures, "stub-parser.js");
-                const parseSpy = sinon.spy(require(parser), "parse");
+            linter.defineParser("stub-parser", testParsers.stubParser);
+            linter.verify(code, { parser: "stub-parser" }, filename, true);
 
-                linter.verify(code, { parser }, filename, true);
-
-                sinon.assert.calledWithMatch(parseSpy, "", { filePath: filename });
-            });
+            sinon.assert.calledWithMatch(parseSpy, "", { filePath: filename });
+        });
 
-            it("should not report an error when JSX code contains a spread operator and JSX is enabled", () => {
-                const code = "var myDivElement = <div {...this.props} />;";
-                const messages = linter.verify(code, { parser: "esprima", parserOptions: { jsx: true } }, "filename");
+        it("should not report an error when JSX code contains a spread operator and JSX is enabled", () => {
+            const code = "var myDivElement = <div {...this.props} />;";
 
-                assert.strictEqual(messages.length, 0);
-            });
+            linter.defineParser("esprima", esprima);
+            const messages = linter.verify(code, { parser: "esprima", parserOptions: { jsx: true } }, "filename");
 
-            it("should return an error when the custom parser can't be found", () => {
-                const code = "var myDivElement = <div {...this.props} />;";
-                const messages = linter.verify(code, { parser: "esprima-xyz" }, "filename");
+            assert.strictEqual(messages.length, 0);
+        });
 
-                assert.strictEqual(messages.length, 1);
-                assert.strictEqual(messages[0].severity, 2);
-                assert.strictEqual(messages[0].message, "Cannot find module 'esprima-xyz'");
-            });
+        it("should return an error when the custom parser can't be found", () => {
+            const code = "var myDivElement = <div {...this.props} />;";
+            const messages = linter.verify(code, { parser: "esprima-xyz" }, "filename");
 
-            it("should not throw or report errors when the custom parser returns unrecognized operators (https://github.com/eslint/eslint/issues/10475)", () => {
-                const code = "null %% 'foo'";
-                const parser = path.join(parserFixtures, "unknown-operators", "unknown-logical-operator.js");
+            assert.strictEqual(messages.length, 1);
+            assert.strictEqual(messages[0].severity, 2);
+            assert.strictEqual(messages[0].message, "Configured parser 'esprima-xyz' was not found.");
+        });
 
-                // This shouldn't throw
-                const messages = linter.verify(code, { parser }, filename, true);
+        it("should not throw or report errors when the custom parser returns unrecognized operators (https://github.com/eslint/eslint/issues/10475)", () => {
+            const code = "null %% 'foo'";
 
-                assert.strictEqual(messages.length, 0);
-            });
+            linter.defineParser("unknown-logical-operator", testParsers.unknownLogicalOperator);
 
-            it("should not throw or report errors when the custom parser returns nested unrecognized operators (https://github.com/eslint/eslint/issues/10560)", () => {
-                const code = "foo && bar %% baz";
-                const parser = path.join(parserFixtures, "unknown-operators", "unknown-logical-operator-nested.js");
+            // This shouldn't throw
+            const messages = linter.verify(code, { parser: "unknown-logical-operator" }, filename, true);
 
-                // This shouldn't throw
-                const messages = linter.verify(code, { parser }, filename, true);
+            assert.strictEqual(messages.length, 0);
+        });
 
-                assert.strictEqual(messages.length, 0);
-            });
+        it("should not throw or report errors when the custom parser returns nested unrecognized operators (https://github.com/eslint/eslint/issues/10560)", () => {
+            const code = "foo && bar %% baz";
 
-            it("should strip leading line: prefix from parser error", () => {
-                const parser = path.join(parserFixtures, "line-error.js");
-                const messages = linter.verify(";", { parser }, "filename");
+            linter.defineParser("unknown-logical-operator-nested", testParsers.unknownLogicalOperatorNested);
 
-                assert.strictEqual(messages.length, 1);
-                assert.strictEqual(messages[0].severity, 2);
-                assert.strictEqual(messages[0].message, errorPrefix + require(parser).expectedError);
-            });
+            // This shouldn't throw
+            const messages = linter.verify(code, { parser: "unknown-logical-operator-nested" }, filename, true);
 
-            it("should not modify a parser error message without a leading line: prefix", () => {
-                const parser = path.join(parserFixtures, "no-line-error.js");
-                const messages = linter.verify(";", { parser }, "filename");
+            assert.strictEqual(messages.length, 0);
+        });
 
-                assert.strictEqual(messages.length, 1);
-                assert.strictEqual(messages[0].severity, 2);
-                assert.strictEqual(messages[0].message, errorPrefix + require(parser).expectedError);
-            });
+        it("should strip leading line: prefix from parser error", () => {
+            linter.defineParser("line-error", testParsers.lineError);
+            const messages = linter.verify(";", { parser: "line-error" }, "filename");
 
-            describe("if a parser provides 'visitorKeys'", () => {
-                let types = [];
-                let scopeAnalyzeStub = null;
-                let sourceCode = null;
+            assert.strictEqual(messages.length, 1);
+            assert.strictEqual(messages[0].severity, 2);
+            assert.strictEqual(messages[0].message, errorPrefix + testParsers.lineError.expectedError);
+        });
 
-                beforeEach(() => {
-                    scopeAnalyzeStub = sandbox.spy(escope, "analyze");
+        it("should not modify a parser error message without a leading line: prefix", () => {
+            linter.defineParser("no-line-error", testParsers.noLineError);
+            const messages = linter.verify(";", { parser: "no-line-error" }, "filename");
 
-                    const parser = path.join(parserFixtures, "enhanced-parser2.js");
+            assert.strictEqual(messages.length, 1);
+            assert.strictEqual(messages[0].severity, 2);
+            assert.strictEqual(messages[0].message, errorPrefix + testParsers.noLineError.expectedError);
+        });
 
-                    types = [];
-                    linter.defineRule("collect-node-types", () => ({
-                        "*"(node) {
-                            types.push(node.type);
-                        }
-                    }));
-                    linter.verify("@foo class A {}", {
-                        parser,
-                        rules: {
-                            "collect-node-types": "error"
-                        }
-                    });
+        describe("if a parser provides 'visitorKeys'", () => {
+            let types = [];
+            let sourceCode;
+            let scopeManager;
 
-                    sourceCode = linter.getSourceCode();
-                });
+            beforeEach(() => {
+                types = [];
+                linter.defineRule("collect-node-types", () => ({
+                    "*"(node) {
+                        types.push(node.type);
+                    }
+                }));
+                linter.defineRule("save-scope-manager", context => {
+                    scopeManager = context.getSourceCode().scopeManager;
 
-                it("Traverser should use the visitorKeys (so 'types' includes 'Decorator')", () => {
-                    assert.deepStrictEqual(
-                        types,
-                        ["Program", "ClassDeclaration", "Decorator", "Identifier", "Identifier", "ClassBody"]
-                    );
+                    return {};
                 });
-
-                it("eslint-scope should use the visitorKeys (so 'childVisitorKeys.ClassDeclaration' includes 'experimentalDecorators')", () => {
-                    assert(scopeAnalyzeStub.calledOnce);
-                    assert.deepStrictEqual(
-                        scopeAnalyzeStub.firstCall.args[1].childVisitorKeys.ClassDeclaration,
-                        vk.unionWith({ ClassDeclaration: ["experimentalDecorators"] }).ClassDeclaration
-                    );
+                linter.defineParser("enhanced-parser2", testParsers.enhancedParser2);
+                linter.verify("@foo class A {}", {
+                    parser: "enhanced-parser2",
+                    rules: {
+                        "collect-node-types": "error",
+                        "save-scope-manager": "error"
+                    }
                 });
 
-                it("should use the same visitorKeys if the source code object is reused", () => {
-                    const types2 = [];
-
-                    linter.defineRule("collect-node-types", () => ({
-                        "*"(node) {
-                            types2.push(node.type);
-                        }
-                    }));
-                    linter.verify(sourceCode, {
-                        rules: {
-                            "collect-node-types": "error"
-                        }
-                    });
-
-                    assert.deepStrictEqual(
-                        types2,
-                        ["Program", "ClassDeclaration", "Decorator", "Identifier", "Identifier", "ClassBody"]
-                    );
-                });
+                sourceCode = linter.getSourceCode();
             });
 
-            describe("if a parser provides 'scope'", () => {
-                let scopeAnalyzeStub = null;
-                let scope = null;
-                let sourceCode = null;
-
-                beforeEach(() => {
-                    scopeAnalyzeStub = sandbox.spy(escope, "analyze");
+            it("Traverser should use the visitorKeys (so 'types' includes 'Decorator')", () => {
+                assert.deepStrictEqual(
+                    types,
+                    ["Program", "ClassDeclaration", "Decorator", "Identifier", "Identifier", "ClassBody"]
+                );
+            });
 
-                    const parser = path.join(parserFixtures, "enhanced-parser3.js");
+            it("eslint-scope should use the visitorKeys (so 'childVisitorKeys.ClassDeclaration' includes 'experimentalDecorators')", () => {
+                assert.deepStrictEqual(
+                    scopeManager.__options.childVisitorKeys.ClassDeclaration, // eslint-disable-line no-underscore-dangle
+                    ["experimentalDecorators", "id", "superClass", "body"]
+                );
+            });
 
-                    linter.defineRule("save-scope1", context => ({
-                        Program() {
-                            scope = context.getScope();
-                        }
-                    }));
-                    linter.verify("@foo class A {}", { parser, rules: { "save-scope1": 2 } });
+            it("should use the same visitorKeys if the source code object is reused", () => {
+                const types2 = [];
 
-                    sourceCode = linter.getSourceCode();
+                linter.defineRule("collect-node-types", () => ({
+                    "*"(node) {
+                        types2.push(node.type);
+                    }
+                }));
+                linter.verify(sourceCode, {
+                    rules: {
+                        "collect-node-types": "error"
+                    }
                 });
 
-                it("should not use eslint-scope analyzer", () => {
-                    assert(scopeAnalyzeStub.notCalled);
-                });
+                assert.deepStrictEqual(
+                    types2,
+                    ["Program", "ClassDeclaration", "Decorator", "Identifier", "Identifier", "ClassBody"]
+                );
+            });
+        });
 
-                it("should use the scope (so the global scope has the reference of '@foo')", () => {
-                    assert.strictEqual(scope.references.length, 1);
-                    assert.deepStrictEqual(
-                        scope.references[0].identifier.name,
-                        "foo"
-                    );
-                });
+        describe("if a parser provides 'scope'", () => {
+            let scope = null;
+            let sourceCode = null;
 
-                it("should use the same scope if the source code object is reused", () => {
-                    let scope2 = null;
+            beforeEach(() => {
+                linter.defineParser("enhanced-parser3", testParsers.enhancedParser3);
+                linter.defineRule("save-scope1", context => ({
+                    Program() {
+                        scope = context.getScope();
+                    }
+                }));
+                linter.verify("@foo class A {}", { parser: "enhanced-parser3", rules: { "save-scope1": 2 } });
 
-                    linter.defineRule("save-scope2", context => ({
-                        Program() {
-                            scope2 = context.getScope();
-                        }
-                    }));
-                    linter.verify(sourceCode, { rules: { "save-scope2": 2 } }, "test.js");
+                sourceCode = linter.getSourceCode();
+            });
 
-                    assert(scope2 !== null);
-                    assert(scope2 === scope);
-                });
+            it("should use the scope (so the global scope has the reference of '@foo')", () => {
+                assert.strictEqual(scope.references.length, 1);
+                assert.deepStrictEqual(
+                    scope.references[0].identifier.name,
+                    "foo"
+                );
             });
 
-            it("should not pass any default parserOptions to the parser", () => {
-                const parser = path.join(parserFixtures, "throws-with-options.js");
+            it("should use the same scope if the source code object is reused", () => {
+                let scope2 = null;
 
-                const messages = linter.verify(";", { parser }, "filename");
+                linter.defineRule("save-scope2", context => ({
+                    Program() {
+                        scope2 = context.getScope();
+                    }
+                }));
+                linter.verify(sourceCode, { rules: { "save-scope2": 2 } }, "test.js");
 
-                assert.strictEqual(messages.length, 0);
+                assert(scope2 !== null);
+                assert(scope2 === scope);
             });
         });
-    }
+
+        it("should not pass any default parserOptions to the parser", () => {
+            linter.defineParser("throws-with-options", testParsers.throwsWithOptions);
+            const messages = linter.verify(";", { parser: "throws-with-options" }, "filename");
+
+            assert.strictEqual(messages.length, 0);
+        });
+    });
 });
diff --git a/tests/lib/rules/comma-dangle.js b/tests/lib/rules/comma-dangle.js
index 1f1fc33cf..c4fab40c3 100644
--- a/tests/lib/rules/comma-dangle.js
+++ b/tests/lib/rules/comma-dangle.js
@@ -1433,6 +1433,14 @@ let d = 0;export {d,};
             options: [{ functions: "never" }],
             parser: parser("return-type-2"),
             errors: [{ messageId: "unexpected" }]
+        },
+
+        // https://github.com/eslint/eslint/issues/11502
+        {
+            code: "foo(a,)",
+            output: "foo(a)",
+            parserOptions: { ecmaVersion: 8 },
+            errors: [{ messageId: "unexpected" }]
         }
     ]
 });
diff --git a/tests/lib/rules/function-paren-newline.js b/tests/lib/rules/function-paren-newline.js
index 5a062a1bf..522182734 100644
--- a/tests/lib/rules/function-paren-newline.js
+++ b/tests/lib/rules/function-paren-newline.js
@@ -20,6 +20,7 @@ const LEFT_MISSING_ERROR = { messageId: "expectedAfter", type: "Punctuator" };
 const LEFT_UNEXPECTED_ERROR = { messageId: "unexpectedAfter", type: "Punctuator" };
 const RIGHT_MISSING_ERROR = { messageId: "expectedBefore", type: "Punctuator" };
 const RIGHT_UNEXPECTED_ERROR = { messageId: "unexpectedBefore", type: "Punctuator" };
+const EXPECTED_BETWEEN = { messageId: "expectedBetween", type: "Identifier" };
 
 const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
 
@@ -82,12 +83,182 @@ ruleTester.run("function-paren-newline", rule, {
               return value;
             })
         `,
-
-        // always option
         {
             code: "function baz(foo, bar) {}",
             options: ["multiline"]
         },
+
+        // multiline-arguments
+        {
+            code: "function baz(foo, bar) {}",
+            options: ["multiline-arguments"]
+        },
+        {
+            code: "function baz(foo) {}",
+            options: ["multiline-arguments"]
+        },
+        {
+            code: "(function(foo, bar) {});",
+            options: ["multiline-arguments"]
+        },
+        {
+            code: "(function(foo) {});",
+            options: ["multiline-arguments"]
+        },
+        {
+            code: "(function baz(foo, bar) {});",
+            options: ["multiline-arguments"]
+        },
+        {
+            code: "(function baz(foo) {});",
+            options: ["multiline-arguments"]
+        },
+        {
+            code: "(foo, bar) => {};",
+            options: ["multiline-arguments"]
+        },
+        {
+            code: "foo => {};",
+            options: ["multiline-arguments"]
+        },
+        {
+            code: "baz(foo, bar);",
+            options: ["multiline-arguments"]
+        },
+        {
+            code: "baz(foo);",
+            options: ["multiline-arguments"]
+        },
+        {
+            code: "function baz() {}",
+            options: ["multiline-arguments"]
+        },
+        {
+            code: `
+                function baz(
+                    foo,
+                    bar
+                ) {}
+            `,
+            options: ["multiline-arguments"]
+        },
+        {
+            code: `
+                function baz(
+                    foo
+                ) {}
+            `,
+            options: ["multiline-arguments"]
+        },
+        {
+            code: `
+                (function(
+                    foo,
+                    bar
+                ) {});
+            `,
+            options: ["multiline-arguments"]
+        },
+        {
+            code: `
+                (function(
+                    foo
+                ) {});
+            `,
+            options: ["multiline-arguments"]
+        },
+        {
+            code: `
+                (function baz(
+                    foo,
+                    bar
+                ) {});
+            `,
+            options: ["multiline-arguments"]
+        },
+        {
+            code: `
+                (function baz(
+                    foo
+                ) {});
+            `,
+            options: ["multiline-arguments"]
+        },
+        {
+            code: `
+                (
+                    foo,
+                    bar
+                ) => {};
+            `,
+            options: ["multiline-arguments"]
+        },
+        {
+            code: `
+                (
+                    foo
+                ) => {};
+            `,
+            options: ["multiline-arguments"]
+        },
+        {
+            code: `
+                baz(
+                    foo,
+                    bar
+                );
+            `,
+            options: ["multiline-arguments"]
+        },
+        {
+            code: `
+                baz(
+                    foo
+                );
+            `,
+            options: ["multiline-arguments"]
+        },
+        {
+            code: `
+                baz(\`foo
+                    bar\`)
+            `,
+            options: ["multiline-arguments"]
+        },
+        {
+            code: "new Foo(bar, baz)",
+            options: ["multiline-arguments"]
+        },
+        {
+            code: "new Foo(bar)",
+            options: ["multiline-arguments"]
+        },
+        {
+            code: "new Foo",
+            options: ["multiline-arguments"]
+        },
+        {
+            code: "new (Foo)",
+            options: ["multiline-arguments"]
+        },
+
+        {
+            code: `
+                (foo)
+                (bar)
+            `,
+            options: ["multiline-arguments"]
+        },
+        {
+            code: `
+                foo.map(value => {
+                  return value;
+                })
+            `,
+            options: ["multiline-arguments"]
+        },
+
+        // always option
         {
             code: `
                 function baz(
@@ -344,6 +515,193 @@ ruleTester.run("function-paren-newline", rule, {
             errors: [RIGHT_UNEXPECTED_ERROR]
         },
 
+        // multiline-arguments
+        {
+            code: `
+                function baz(foo,
+                    bar
+                ) {}
+            `,
+            output: `
+                function baz(\nfoo,
+                    bar
+                ) {}
+            `,
+            options: ["multiline-arguments"],
+            errors: [LEFT_MISSING_ERROR]
+        },
+        {
+            code: `
+                (function(
+                    foo,
+                    bar) {})
+            `,
+            output: `
+                (function(
+                    foo,
+                    bar\n) {})
+            `,
+            options: ["multiline-arguments"],
+            errors: [RIGHT_MISSING_ERROR]
+        },
+        {
+            code: `
+                (function baz(foo,
+                    bar) {})
+            `,
+            output: `
+                (function baz(\nfoo,
+                    bar\n) {})
+            `,
+            options: ["multiline-arguments"],
+            errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR]
+        },
+        {
+            code: `
+                baz(
+                    foo, bar);
+            `,
+            output: `
+                baz(foo, bar);
+            `,
+            options: ["multiline-arguments"],
+            errors: [LEFT_UNEXPECTED_ERROR]
+        },
+        {
+            code: `
+                (foo, bar
+                ) => {};
+            `,
+            output: `
+                (foo, bar) => {};
+            `,
+            options: ["multiline-arguments"],
+            errors: [RIGHT_UNEXPECTED_ERROR]
+        },
+        {
+            code: `
+                function baz(
+                    foo, bar
+                ) {}
+            `,
+            output: `
+                function baz(foo, bar) {}
+            `,
+            options: ["multiline-arguments"],
+            errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR]
+        },
+        {
+            code: `
+                function baz(
+                ) {}
+            `,
+            output: `
+                function baz() {}
+            `,
+            options: ["multiline-arguments"],
+            errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR]
+        },
+        {
+            code: `
+                new Foo(bar,
+                    baz);
+            `,
+            output: `
+                new Foo(\nbar,
+                    baz\n);
+            `,
+            options: ["multiline-arguments"],
+            errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR]
+        },
+        {
+            code: `
+                function baz(/* not fixed due to comment */
+                foo) {}
+            `,
+            output: `
+                function baz(/* not fixed due to comment */
+                foo\n) {}
+            `,
+            options: ["multiline-arguments"],
+            errors: [RIGHT_MISSING_ERROR]
+        },
+        {
+            code: `
+                function baz(foo
+                /* not fixed due to comment */) {}
+            `,
+            output: null,
+            options: ["multiline-arguments"],
+            errors: [RIGHT_UNEXPECTED_ERROR]
+        },
+        {
+            code: `
+                function baz(
+                    qwe,
+                    foo, bar
+                ) {}
+            `,
+            output: `
+                function baz(
+                    qwe,
+                    foo, \nbar
+                ) {}
+            `,
+            options: ["multiline-arguments"],
+            errors: [EXPECTED_BETWEEN]
+        },
+        {
+            code: `
+                function baz(
+                    qwe, foo,
+                    bar
+                ) {}
+            `,
+            output: `
+                function baz(
+                    qwe, \nfoo,
+                    bar
+                ) {}
+            `,
+            options: ["multiline-arguments"],
+            errors: [EXPECTED_BETWEEN]
+        },
+        {
+            code: `
+                function baz(qwe, foo,
+                    bar) {}
+            `,
+            output: `
+                function baz(\nqwe, \nfoo,
+                    bar\n) {}
+            `,
+            options: ["multiline-arguments"],
+            errors: [LEFT_MISSING_ERROR, EXPECTED_BETWEEN, RIGHT_MISSING_ERROR]
+        },
+        {
+            code: `
+                baz(
+                    foo);
+            `,
+            output: `
+                baz(
+                    foo\n);
+            `,
+            options: ["multiline-arguments"],
+            errors: [RIGHT_MISSING_ERROR]
+        },
+        {
+            code: `
+                baz(foo
+                    );
+            `,
+            output: `
+                baz(foo);
+            `,
+            options: ["multiline-arguments"],
+            errors: [RIGHT_UNEXPECTED_ERROR]
+        },
+
         // always option
         {
             code: `
diff --git a/tests/lib/rules/max-len.js b/tests/lib/rules/max-len.js
index a7080f477..0af7965b6 100644
--- a/tests/lib/rules/max-len.js
+++ b/tests/lib/rules/max-len.js
@@ -178,6 +178,17 @@ ruleTester.run("max-len", rule, {
         {
             code: "'🙂😀😆😎😊😜😉👍'",
             options: [10]
+        },
+
+        // Astral symbols in pattern (only matched by unicode regexes)
+        {
+            code: "var longNameLongName = '𝌆𝌆'",
+            options: [5, { ignorePattern: "𝌆{2}" }]
+        },
+
+        {
+            code: "\tfoo",
+            options: [4, { tabWidth: 0 }]
         }
     ],
 
@@ -619,6 +630,20 @@ ruleTester.run("max-len", rule, {
                     column: 1
                 }
             ]
+        },
+
+        {
+            code: "a",
+            options: [0],
+            errors: [
+                {
+                    messageId: "max",
+                    data: { lineNumber: 1, maxLength: 0 },
+                    type: "Program",
+                    line: 1,
+                    column: 1
+                }
+            ]
         }
     ]
 });
diff --git a/tests/lib/rules/max-lines-per-function.js b/tests/lib/rules/max-lines-per-function.js
index 9046a1406..2879c2b62 100644
--- a/tests/lib/rules/max-lines-per-function.js
+++ b/tests/lib/rules/max-lines-per-function.js
@@ -205,6 +205,15 @@ if ( x === y ) {
             ]
         },
 
+        // Test that option defaults work as expected
+        {
+            code: `() => {${"foo\n".repeat(60)}}`,
+            options: [{}],
+            errors: [
+                { messageId: "exceed", data: { name: "arrow function", lineCount: 61, maxLines: 50 } }
+            ]
+        },
+
         // Test skipBlankLines: false
         {
             code: "function name() {\nvar x = 5;\n\t\n \n\nvar x = 2;\n}",
diff --git a/tests/lib/rules/no-confusing-arrow.js b/tests/lib/rules/no-confusing-arrow.js
index 7df2538a2..8c7efe6e2 100644
--- a/tests/lib/rules/no-confusing-arrow.js
+++ b/tests/lib/rules/no-confusing-arrow.js
@@ -21,47 +21,55 @@ const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
 ruleTester.run("no-confusing-arrow", rule, {
     valid: [
         "a => { return 1 ? 2 : 3; }",
+        { code: "a => { return 1 ? 2 : 3; }", options: [{ allowParens: false }] },
+
         "var x = a => { return 1 ? 2 : 3; }",
+        { code: "var x = a => { return 1 ? 2 : 3; }", options: [{ allowParens: false }] },
+
         "var x = (a) => { return 1 ? 2 : 3; }",
+        { code: "var x = (a) => { return 1 ? 2 : 3; }", options: [{ allowParens: false }] },
+
+        "var x = a => (1 ? 2 : 3)",
         { code: "var x = a => (1 ? 2 : 3)", options: [{ allowParens: true }] }
     ],
     invalid: [
         {
             code: "a => 1 ? 2 : 3",
-            output: null,
+            output: "a => (1 ? 2 : 3)",
             errors: [{ messageId: "confusing" }]
         },
         {
-            code: "var x = a => 1 ? 2 : 3",
-            output: null,
+            code: "a => 1 ? 2 : 3",
+            output: "a => (1 ? 2 : 3)",
+            options: [{ allowParens: true }],
             errors: [{ messageId: "confusing" }]
         },
         {
-            code: "var x = (a) => 1 ? 2 : 3",
+            code: "a => 1 ? 2 : 3",
             output: null,
+            options: [{ allowParens: false }],
             errors: [{ messageId: "confusing" }]
         },
         {
-            code: "var x = a => (1 ? 2 : 3)",
-            output: null,
+            code: "var x = a => 1 ? 2 : 3",
+            output: "var x = a => (1 ? 2 : 3)",
             errors: [{ messageId: "confusing" }]
         },
         {
-            code: "a => 1 ? 2 : 3",
-            output: "a => (1 ? 2 : 3)",
+            code: "var x = a => 1 ? 2 : 3",
+            output: "var x = a => (1 ? 2 : 3)",
             options: [{ allowParens: true }],
             errors: [{ messageId: "confusing" }]
         },
         {
             code: "var x = a => 1 ? 2 : 3",
-            output: "var x = a => (1 ? 2 : 3)",
-            options: [{ allowParens: true }],
+            output: null,
+            options: [{ allowParens: false }],
             errors: [{ messageId: "confusing" }]
         },
         {
             code: "var x = (a) => 1 ? 2 : 3",
             output: "var x = (a) => (1 ? 2 : 3)",
-            options: [{ allowParens: true }],
             errors: [{ messageId: "confusing" }]
         }
     ]
diff --git a/tests/lib/rules/no-unused-vars.js b/tests/lib/rules/no-unused-vars.js
index 3012dec63..ba8e57dcc 100644
--- a/tests/lib/rules/no-unused-vars.js
+++ b/tests/lib/rules/no-unused-vars.js
@@ -330,33 +330,33 @@ ruleTester.run("no-unused-vars", rule, {
         {
             code: "var _a; var b;",
             options: [{ vars: "all", varsIgnorePattern: "^_" }],
-            errors: [{ message: "'b' is defined but never used. Allowed unused vars must match /^_/.", line: 1, column: 13 }]
+            errors: [{ message: "'b' is defined but never used. Allowed unused vars must match /^_/u.", line: 1, column: 13 }]
         },
         {
             code: "var a; function foo() { var _b; var c_; } foo();",
             options: [{ vars: "local", varsIgnorePattern: "^_" }],
-            errors: [{ message: "'c_' is defined but never used. Allowed unused vars must match /^_/.", line: 1, column: 37 }]
+            errors: [{ message: "'c_' is defined but never used. Allowed unused vars must match /^_/u.", line: 1, column: 37 }]
         },
         {
             code: "function foo(a, _b) { } foo();",
             options: [{ args: "all", argsIgnorePattern: "^_" }],
-            errors: [{ message: "'a' is defined but never used. Allowed unused args must match /^_/.", line: 1, column: 14 }]
+            errors: [{ message: "'a' is defined but never used. Allowed unused args must match /^_/u.", line: 1, column: 14 }]
         },
         {
             code: "function foo(a, _b, c) { return a; } foo();",
             options: [{ args: "after-used", argsIgnorePattern: "^_" }],
-            errors: [{ message: "'c' is defined but never used. Allowed unused args must match /^_/.", line: 1, column: 21 }]
+            errors: [{ message: "'c' is defined but never used. Allowed unused args must match /^_/u.", line: 1, column: 21 }]
         },
         {
             code: "function foo(_a) { } foo();",
             options: [{ args: "all", argsIgnorePattern: "[iI]gnored" }],
-            errors: [{ message: "'_a' is defined but never used. Allowed unused args must match /[iI]gnored/.", line: 1, column: 14 }]
+            errors: [{ message: "'_a' is defined but never used. Allowed unused args must match /[iI]gnored/u.", line: 1, column: 14 }]
         },
         {
             code: "var [ firstItemIgnored, secondItem ] = items;",
             options: [{ vars: "all", varsIgnorePattern: "[iI]gnored" }],
             parserOptions: { ecmaVersion: 6 },
-            errors: [{ message: "'secondItem' is assigned a value but never used. Allowed unused vars must match /[iI]gnored/.", line: 1, column: 25 }]
+            errors: [{ message: "'secondItem' is assigned a value but never used. Allowed unused vars must match /[iI]gnored/u.", line: 1, column: 25 }]
         },
 
         // for-in loops (see #2342)
@@ -524,14 +524,14 @@ ruleTester.run("no-unused-vars", rule, {
         {
             code: "try{}catch(err){};",
             options: [{ caughtErrors: "all", caughtErrorsIgnorePattern: "^ignore" }],
-            errors: [{ message: "'err' is defined but never used. Allowed unused args must match /^ignore/." }]
+            errors: [{ message: "'err' is defined but never used. Allowed unused args must match /^ignore/u." }]
         },
 
         // multiple try catch with one success
         {
             code: "try{}catch(ignoreErr){}try{}catch(err){};",
             options: [{ caughtErrors: "all", caughtErrorsIgnorePattern: "^ignore" }],
-            errors: [{ message: "'err' is defined but never used. Allowed unused args must match /^ignore/." }]
+            errors: [{ message: "'err' is defined but never used. Allowed unused args must match /^ignore/u." }]
         },
 
         // multiple try catch both fail
@@ -539,8 +539,8 @@ ruleTester.run("no-unused-vars", rule, {
             code: "try{}catch(error){}try{}catch(err){};",
             options: [{ caughtErrors: "all", caughtErrorsIgnorePattern: "^ignore" }],
             errors: [
-                { message: "'error' is defined but never used. Allowed unused args must match /^ignore/." },
-                { message: "'err' is defined but never used. Allowed unused args must match /^ignore/." }
+                { message: "'error' is defined but never used. Allowed unused args must match /^ignore/u." },
+                { message: "'err' is defined but never used. Allowed unused args must match /^ignore/u." }
             ]
         },
 
@@ -613,10 +613,10 @@ ruleTester.run("no-unused-vars", rule, {
             options: [{ argsIgnorePattern: "c" }],
             errors: [
                 {
-                    message: "'a' is defined but never used. Allowed unused args must match /c/."
+                    message: "'a' is defined but never used. Allowed unused args must match /c/u."
                 },
                 {
-                    message: "'b' is defined but never used. Allowed unused args must match /c/."
+                    message: "'b' is defined but never used. Allowed unused args must match /c/u."
                 }
             ]
         },
@@ -626,10 +626,10 @@ ruleTester.run("no-unused-vars", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [
                 {
-                    message: "'a' is defined but never used. Allowed unused args must match /[cd]/."
+                    message: "'a' is defined but never used. Allowed unused args must match /[cd]/u."
                 },
                 {
-                    message: "'b' is defined but never used. Allowed unused args must match /[cd]/."
+                    message: "'b' is defined but never used. Allowed unused args must match /[cd]/u."
                 }
             ]
         },
@@ -639,13 +639,13 @@ ruleTester.run("no-unused-vars", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [
                 {
-                    message: "'a' is defined but never used. Allowed unused args must match /c/."
+                    message: "'a' is defined but never used. Allowed unused args must match /c/u."
                 },
                 {
-                    message: "'b' is defined but never used. Allowed unused args must match /c/."
+                    message: "'b' is defined but never used. Allowed unused args must match /c/u."
                 },
                 {
-                    message: "'d' is defined but never used. Allowed unused args must match /c/."
+                    message: "'d' is defined but never used. Allowed unused args must match /c/u."
                 }
             ]
         },
@@ -655,13 +655,13 @@ ruleTester.run("no-unused-vars", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [
                 {
-                    message: "'a' is defined but never used. Allowed unused args must match /d/."
+                    message: "'a' is defined but never used. Allowed unused args must match /d/u."
                 },
                 {
-                    message: "'b' is defined but never used. Allowed unused args must match /d/."
+                    message: "'b' is defined but never used. Allowed unused args must match /d/u."
                 },
                 {
-                    message: "'c' is defined but never used. Allowed unused args must match /d/."
+                    message: "'c' is defined but never used. Allowed unused args must match /d/u."
                 }
             ]
         },
diff --git a/tests/lib/testers/rule-tester.js b/tests/lib/testers/rule-tester.js
index 7f5eb6135..2ff61f1a5 100644
--- a/tests/lib/testers/rule-tester.js
+++ b/tests/lib/testers/rule-tester.js
@@ -621,12 +621,12 @@ describe("RuleTester", () => {
             invalid: [
                 {
                     code: "eval(foo)",
-                    parser: "esprima",
+                    parser: require.resolve("esprima"),
                     errors: [{}]
                 }
             ]
         });
-        assert.strictEqual(spy.args[1][1].parser, "esprima");
+        assert.strictEqual(spy.args[1][1].parser, require.resolve("esprima"));
     });
 
     it("should prevent invalid options schemas", () => {
@@ -659,6 +659,43 @@ describe("RuleTester", () => {
 
     });
 
+    it("should disallow invalid defaults in rules", () => {
+        const ruleWithInvalidDefaults = {
+            meta: {
+                schema: [
+                    {
+                        oneOf: [
+                            { enum: ["foo"] },
+                            {
+                                type: "object",
+                                properties: {
+                                    foo: {
+                                        enum: ["foo", "bar"],
+                                        default: "foo"
+                                    }
+                                },
+                                additionalProperties: false
+                            }
+                        ]
+                    }
+                ]
+            },
+            create: () => ({})
+        };
+
+        assert.throws(() => {
+            ruleTester.run("invalid-defaults", ruleWithInvalidDefaults, {
+                valid: [
+                    {
+                        code: "foo",
+                        options: [{}]
+                    }
+                ],
+                invalid: []
+            });
+        }, /Schema for rule invalid-defaults is invalid: default is ignored for: data1\.foo/u);
+    });
+
     it("throw an error when an unknown config option is included", () => {
         assert.throws(() => {
             ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
diff --git a/tests/lib/util/module-resolver.js b/tests/lib/util/module-resolver.js
deleted file mode 100644
index dea96dd74..000000000
--- a/tests/lib/util/module-resolver.js
+++ /dev/null
@@ -1,49 +0,0 @@
-/**
- * @fileoverview Tests for ModuleResolver
- * @author Nicholas C. Zakas
- */
-"use strict";
-
-//------------------------------------------------------------------------------
-// Requirements
-//------------------------------------------------------------------------------
-
-const path = require("path"),
-    assert = require("chai").assert,
-    leche = require("leche"),
-    ModuleResolver = require("../../../lib/util/module-resolver");
-
-//------------------------------------------------------------------------------
-// Data
-//------------------------------------------------------------------------------
-
-const FIXTURES_PATH = path.resolve(__dirname, "../../fixtures/module-resolver");
-
-//------------------------------------------------------------------------------
-// Tests
-//------------------------------------------------------------------------------
-
-describe("ModuleResolver", () => {
-
-    describe("resolve()", () => {
-
-        leche.withData([
-
-            // resolve just like Node.js
-            ["leche", "", require.resolve("leche")],
-
-            // resolve with a different location
-            ["foo", path.resolve(FIXTURES_PATH, "node_modules"), path.resolve(FIXTURES_PATH, "node_modules/foo.js")]
-
-        ], (name, lookupPath, expected) => {
-            it("should find the correct location of a file", () => {
-                const resolver = new ModuleResolver(),
-                    result = resolver.resolve(name, lookupPath);
-
-                assert.strictEqual(result, expected);
-            });
-        });
-
-    });
-
-});