Repository: eslint/eslint (js)

Issue:
Wrongly resolving plugins in extends
Here:

https://github.com/eslint/eslint/blob/084351bedb1001fc460e9acea4952b0b6139ec23/lib/config/config-file.js#L466

This is the code to resolve the plugin that `extends: "plugin:foo/recommended"` specified. That code looks to use the same logic as shareable config's.

However, the logic to resolve plugins is different to shareable config's. It's:

https://github.com/eslint/eslint/blob/084351bedb1001fc460e9acea4952b0b6139ec23/lib/config/plugins.js#L106

We should fix the resolving logic of plugins in `extends`.

----

Should we address this change as a breaking change?

Pull request:
Breaking: fix plugin resolver in extends (fixes #9904)
**What is the purpose of this pull request? (put an "X" next to item)**

[X] Bug fix ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/bug-report.md))

Fixes #9904.

**What changes did you make? (Give an overview)**

This PR fixes plugin resolver in `extends` setting.

ESLint can handle `plugin:` prefix in `extends` setting, but it had resolved `plugin:` path with shareable config's resolver rather than plugin resolver. So it could load different plugin entity between `extends` setting and `rules` setting.

Now ESLint handle `plugin:` prefix with plugin resolver correctly.

This PR doesn't add any new test, but fixes some existing tests.

**Is there anything you'd like reviewers to focus on?**

Nothing in particular.


Patch:
diff --git a/conf/eslint-recommended.js b/conf/eslint-recommended.js
index 0acee234a..df07c2e3c 100644
--- a/conf/eslint-recommended.js
+++ b/conf/eslint-recommended.js
@@ -39,14 +39,14 @@ module.exports = {
         "dot-notation": "off",
         "eol-last": "off",
         eqeqeq: "off",
-        "for-direction": "off",
+        "for-direction": "error",
         "func-call-spacing": "off",
         "func-name-matching": "off",
         "func-names": "off",
         "func-style": "off",
         "function-paren-newline": "off",
         "generator-star-spacing": "off",
-        "getter-return": "off",
+        "getter-return": "error",
         "global-require": "off",
         "guard-for-in": "off",
         "handle-callback-err": "off",
diff --git a/docs/rules/no-mixed-spaces-and-tabs.md b/docs/rules/no-mixed-spaces-and-tabs.md
index 4f76b2617..ba34dbe49 100644
--- a/docs/rules/no-mixed-spaces-and-tabs.md
+++ b/docs/rules/no-mixed-spaces-and-tabs.md
@@ -41,7 +41,7 @@ function add(x, y) {
 
 This rule has a string option.
 
-* `"smart-tabs"` allows mixed spaces and tabs when the latter are used for alignment.
+* `"smart-tabs"` allows mixed tabs and spaces when the spaces are used for alignment.
 
 ### smart-tabs
 
diff --git a/docs/rules/valid-jsdoc.md b/docs/rules/valid-jsdoc.md
index 69375bf27..670f90fb9 100644
--- a/docs/rules/valid-jsdoc.md
+++ b/docs/rules/valid-jsdoc.md
@@ -158,7 +158,7 @@ This rule has an object option:
 * `"preferType"` enforces consistent type strings specified by an object whose properties mean instead of key use value (for example, `"object": "Object"` means instead of `object` use `Object`)
 * `"requireReturn"` requires a return tag:
     * `true` (default) **even if** the function or method does not have a `return` statement (this option value does not apply to constructors)
-    * `false` **if and only if** the function or method has a `return` statement (this option value does apply to constructors)
+    * `false` **if and only if** the function or method has a `return` statement or returns a value e.g. `async` function (this option value does apply to constructors)
 * `"requireReturnType": false` allows missing type in return tags
 * `"matchDescription"` specifies (as a string) a regular expression to match the description in each JSDoc comment (for example, `".+"` requires a description; this option does not apply to descriptions in parameter or return tags)
 * `"requireParamDescription": false` allows missing description in parameter tags
diff --git a/docs/user-guide/migrating-to-5.0.0.md b/docs/user-guide/migrating-to-5.0.0.md
index 48288e60d..4942b8749 100644
--- a/docs/user-guide/migrating-to-5.0.0.md
+++ b/docs/user-guide/migrating-to-5.0.0.md
@@ -7,6 +7,7 @@ The lists below are ordered roughly by the number of users each change is expect
 ### Breaking changes for users
 
 1. [Node.js 4 is no longer supported](#drop-node-4)
+1. [New rules have been added to `eslint:recommended`](#eslint-recommended-changes)
 1. [The `experimentalObjectRestSpread` option has been deprecated](#experimental-object-rest-spread)
 1. [Linting nonexistent files from the command line is now a fatal error](#nonexistent-files)
 1. [Deprecated globals have been removed from the `node`, `browser`, and `jest` environments](#deprecated-globals)
@@ -39,6 +40,26 @@ As of April 30th, 2018, Node.js 4 will be at EOL and will no longer be receiving
 
 **To address:** Make sure you upgrade to at least Node.js 6 when using ESLint v5. If you are unable to upgrade, we recommend continuing to use ESLint v4.x until you are able to upgrade Node.js.
 
+## <a name="eslint-recommended-changes"/> `eslint:recommended` changes
+
+Two new rules have been added to the [`eslint:recommended`](https://eslint.org/docs/user-guide/configuring#using-eslintrecommended) config:
+
+* [`for-direction`](/docs/rules/for-direction) enforce `for` loop update clause moving the counter in the right direction.
+* [`getter-return`](/docs/rules/getter-return) enforce a `return` statement is present in property getters.
+
+**To address:** To mimic the `eslint:recommended` behavior from 4.x, you can disable these rules in a config file:
+
+```json
+{
+  "extends": "eslint:recommended",
+
+  "rules": {
+    "for-direction": "off",
+    "getter-return": "off"
+  }
+}
+```
+
 ## <a name="experimental-object-rest-spread"></a> The `experimentalObjectRestSpread` option has been deprecated
 
 Previously, when using the default parser it was possible to use the `experimentalObjectRestSpread` option to enable support for [rest/spread properties](https://developers.google.com/web/updates/2017/06/object-rest-spread), as follows:
diff --git a/lib/config/config-file.js b/lib/config/config-file.js
index 37ac84831..46d561516 100644
--- a/lib/config/config-file.js
+++ b/lib/config/config-file.js
@@ -467,7 +467,7 @@ function resolve(filePath, relativeTo) {
         debug(`Attempting to resolve ${normalizedPackageName}`);
 
         return {
-            filePath: resolver.resolve(normalizedPackageName, getLookupPath(relativeTo)),
+            filePath: require.resolve(normalizedPackageName),
             configName,
             configFullName
         };
diff --git a/lib/config/config-validator.js b/lib/config/config-validator.js
index 1a5b3ef13..ed9a34cb0 100644
--- a/lib/config/config-validator.js
+++ b/lib/config/config-validator.js
@@ -9,7 +9,8 @@
 // Requirements
 //------------------------------------------------------------------------------
 
-const ajv = require("../util/ajv"),
+const path = require("path"),
+    ajv = require("../util/ajv"),
     lodash = require("lodash"),
     configSchema = require("../../conf/config-schema.js"),
     util = require("util");
@@ -21,6 +22,12 @@ const ruleValidators = new WeakMap();
 //------------------------------------------------------------------------------
 let validateSchema;
 
+// Defitions for deprecation warnings.
+const deprecationWarningMessages = Object.freeze({
+    ESLINT_LEGACY_ECMAFEATURES: "The 'ecmaFeatures' config file property is deprecated, and has no effect.",
+    ESLINT_LEGACY_OBJECT_REST_SPREAD: "The 'parserOptions.ecmaFeatures.experimentalObjectRestSpread' option is deprecated. Use 'parserOptions.ecmaVersion' instead."
+});
+
 /**
  * Gets a complete options schema for a rule.
  * @param {{create: Function, schema: (Array|null)}} rule A new-style rule object
@@ -194,18 +201,18 @@ function formatErrors(errors) {
  * for each unique file path, but repeated invocations with the same file path have no effect.
  * No warnings are emitted if the `--no-deprecation` or `--no-warnings` Node runtime flags are active.
  * @param {string} source The name of the configuration source to report the warning for.
+ * @param {string} errorCode The warning message to show.
  * @returns {void}
  */
-const emitEcmaFeaturesWarning = lodash.memoize(source => {
-
-    /*
-     * util.deprecate seems to be the only way to emit a warning in Node 4.x while respecting the --no-warnings flag.
-     * (In Node 6+, process.emitWarning could be used instead.)
-     */
-    util.deprecate(
-        () => {},
-        `[eslint] The 'ecmaFeatures' config file property is deprecated, and has no effect. (found in ${source})`
-    )();
+const emitDeprecationWarning = lodash.memoize((source, errorCode) => {
+    const rel = path.relative(process.cwd(), source);
+    const message = deprecationWarningMessages[errorCode];
+
+    process.emitWarning(
+        `${message} (found in "${rel}")`,
+        "DeprecationWarning",
+        errorCode
+    );
 });
 
 /**
@@ -221,8 +228,17 @@ function validateConfigSchema(config, source) {
         throw new Error(`ESLint configuration in ${source} is invalid:\n${formatErrors(validateSchema.errors)}`);
     }
 
-    if (Object.prototype.hasOwnProperty.call(config, "ecmaFeatures")) {
-        emitEcmaFeaturesWarning(source);
+    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");
     }
 }
 
diff --git a/lib/linter.js b/lib/linter.js
index 7ca835c15..ec4b4f6d9 100644
--- a/lib/linter.js
+++ b/lib/linter.js
@@ -410,11 +410,12 @@ function normalizeVerifyOptions(providedOptions) {
 
 /**
  * Combines the provided parserOptions with the options from environments
+ * @param {string} parserName The parser name which uses this options.
  * @param {Object} providedOptions The provided 'parserOptions' key in a config
  * @param {Environment[]} enabledEnvironments The environments enabled in configuration and with inline comments
  * @returns {Object} Resulting parser options after merge
  */
-function resolveParserOptions(providedOptions, enabledEnvironments) {
+function resolveParserOptions(parserName, providedOptions, enabledEnvironments) {
     const parserOptionsFromEnv = enabledEnvironments
         .filter(env => env.parserOptions)
         .reduce((parserOptions, env) => ConfigOps.merge(parserOptions, env.parserOptions), {});
@@ -431,6 +432,16 @@ function resolveParserOptions(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;
 }
 
@@ -923,9 +934,9 @@ module.exports = class Linter {
             .map(envName => this.environments.get(envName))
             .filter(env => env);
 
-        const parserOptions = resolveParserOptions(config.parserOptions || {}, enabledEnvs);
-        const configuredGlobals = resolveGlobals(config.globals || {}, enabledEnvs);
         const parserName = config.parser || DEFAULT_PARSER_NAME;
+        const parserOptions = resolveParserOptions(parserName, config.parserOptions || {}, enabledEnvs);
+        const configuredGlobals = resolveGlobals(config.globals || {}, enabledEnvs);
         const settings = config.settings || {};
 
         if (!lastSourceCodes.get(this)) {
diff --git a/lib/rules/for-direction.js b/lib/rules/for-direction.js
index c91888da2..817e2c111 100644
--- a/lib/rules/for-direction.js
+++ b/lib/rules/for-direction.js
@@ -14,7 +14,7 @@ module.exports = {
         docs: {
             description: "enforce \"for\" loop update clause moving the counter in the right direction.",
             category: "Possible Errors",
-            recommended: false,
+            recommended: true,
             url: "https://eslint.org/docs/rules/for-direction"
         },
         fixable: null,
diff --git a/lib/rules/getter-return.js b/lib/rules/getter-return.js
index 58f2a3978..fe40e5f09 100644
--- a/lib/rules/getter-return.js
+++ b/lib/rules/getter-return.js
@@ -47,7 +47,7 @@ module.exports = {
         docs: {
             description: "enforce `return` statements in getters",
             category: "Possible Errors",
-            recommended: false,
+            recommended: true,
             url: "https://eslint.org/docs/rules/getter-return"
         },
         fixable: null,
diff --git a/lib/rules/object-shorthand.js b/lib/rules/object-shorthand.js
index c5239a71d..71a320a0e 100644
--- a/lib/rules/object-shorthand.js
+++ b/lib/rules/object-shorthand.js
@@ -233,6 +233,10 @@ module.exports = {
             const keyText = sourceCode.text.slice(firstKeyToken.range[0], lastKeyToken.range[1]);
             let keyPrefix = "";
 
+            if (sourceCode.commentsExistBetween(lastKeyToken, node.value)) {
+                return null;
+            }
+
             if (node.value.async) {
                 keyPrefix += "async ";
             }
diff --git a/lib/rules/valid-jsdoc.js b/lib/rules/valid-jsdoc.js
index 4038f70e5..971e755c4 100644
--- a/lib/rules/valid-jsdoc.js
+++ b/lib/rules/valid-jsdoc.js
@@ -102,7 +102,7 @@ module.exports = {
         function startFunction(node) {
             fns.push({
                 returnPresent: (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement") ||
-                    isTypeClass(node)
+                    isTypeClass(node) || node.async
             });
         }
 
diff --git a/package.json b/package.json
index e1199bb32..7007d48ba 100644
--- a/package.json
+++ b/package.json
@@ -90,7 +90,7 @@
     "eslint-plugin-rulesdir": "^0.1.0",
     "eslint-release": "^0.11.1",
     "eslint-rule-composer": "^0.1.1",
-    "eslump": "^1.6.1",
+    "eslump": "^1.6.2",
     "esprima": "^4.0.0",
     "istanbul": "^0.4.5",
     "jsdoc": "^3.5.5",
diff --git a/packages/eslint-config-eslint/default.yml b/packages/eslint-config-eslint/default.yml
index 8c5ac071c..611594067 100644
--- a/packages/eslint-config-eslint/default.yml
+++ b/packages/eslint-config-eslint/default.yml
@@ -24,12 +24,10 @@ rules:
     dot-notation: ["error", { allowKeywords: true }]
     eol-last: "error"
     eqeqeq: "error"
-    for-direction: "error"
     func-call-spacing: "error"
     func-style: ["error", "declaration"]
     function-paren-newline: ["error", "consistent"]
     generator-star-spacing: "error"
-    getter-return: "error"
     guard-for-in: "error"
     handle-callback-err: ["error", "err"]
     key-spacing: ["error", { beforeColon: false, afterColon: true }]
diff --git a/tools/eslint-fuzzer.js b/tools/eslint-fuzzer.js
index ea84e994b..342dee3e4 100644
--- a/tools/eslint-fuzzer.js
+++ b/tools/eslint-fuzzer.js
@@ -110,7 +110,7 @@ function fuzz(options) {
         const text = codeGenerator({ sourceType });
         const config = {
             rules: lodash.mapValues(ruleConfigs, lodash.sample),
-            parserOptions: { sourceType, ecmaVersion: 2017 }
+            parserOptions: { sourceType, ecmaVersion: 2018 }
         };
 
         let autofixResult;


Test patch:
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
new file mode 100644
index 000000000..c64b2c309
--- /dev/null
+++ b/tests/fixtures/config-file/experimental-object-rest-spread/another-parser/.eslintrc.yml
@@ -0,0 +1,6 @@
+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
new file mode 100644
index 000000000..f193589ef
--- /dev/null
+++ b/tests/fixtures/config-file/experimental-object-rest-spread/another-parser/parser.js
@@ -0,0 +1,1 @@
+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
new file mode 100644
index 000000000..a5c9e4ebf
--- /dev/null
+++ b/tests/fixtures/config-file/experimental-object-rest-spread/basic/.eslintrc.yml
@@ -0,0 +1,5 @@
+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
new file mode 100644
index 000000000..2daef0dea
--- /dev/null
+++ b/tests/fixtures/config-file/experimental-object-rest-spread/extends/.eslintrc.yml
@@ -0,0 +1,4 @@
+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
new file mode 100644
index 000000000..99975a7bd
--- /dev/null
+++ b/tests/fixtures/config-file/experimental-object-rest-spread/extends/common.yml
@@ -0,0 +1,3 @@
+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
new file mode 100644
index 000000000..a5c9e4ebf
--- /dev/null
+++ b/tests/fixtures/config-file/experimental-object-rest-spread/subdir/.eslintrc.yml
@@ -0,0 +1,5 @@
+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
new file mode 100644
index 000000000..78dc43786
--- /dev/null
+++ b/tests/fixtures/config-file/experimental-object-rest-spread/subdir/lib/.eslintrc.yml
@@ -0,0 +1,2 @@
+rules:
+  no-undef: off
diff --git a/tests/lib/config.js b/tests/lib/config.js
index 1f9690339..6baae0e05 100644
--- a/tests/lib/config.js
+++ b/tests/lib/config.js
@@ -83,6 +83,14 @@ function assertConfigsEqual(actual, expected) {
     }
 }
 
+/**
+ * Wait for the next tick.
+ * @returns {Promise<void>} -
+ */
+function nextTick() {
+    return new Promise(resolve => process.nextTick(resolve));
+}
+
 //------------------------------------------------------------------------------
 // Tests
 //------------------------------------------------------------------------------
@@ -1086,7 +1094,6 @@ describe("Config", () => {
             });
         });
 
-
         describe("with overrides", () => {
 
             /**
@@ -1359,6 +1366,112 @@ describe("Config", () => {
                 assertConfigsEqual(actual, expected);
             });
         });
+
+        describe("deprecation warnings", () => {
+            let warning = null;
+
+            function onWarning(w) { // eslint-disable-line require-jsdoc
+
+                // Node.js 6.x does not have 'w.code' property.
+                if (!w.hasOwnProperty("code") || typeof w.code === "string" && w.code.startsWith("ESLINT_")) {
+                    warning = w;
+                }
+            }
+
+            beforeEach(() => {
+                warning = null;
+                process.on("warning", onWarning);
+            });
+            afterEach(() => {
+                process.removeListener("warning", onWarning);
+            });
+
+            it("should emit a deprecation warning if 'ecmaFeatures' is given.", () => Promise.resolve()
+                .then(() => {
+                    const cwd = path.resolve(__dirname, "../fixtures/config-file/ecma-features/");
+                    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 '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", () => {
diff --git a/tests/lib/config/config-file.js b/tests/lib/config/config-file.js
index 91c6508d4..287a7d67d 100644
--- a/tests/lib/config/config-file.js
+++ b/tests/lib/config/config-file.js
@@ -8,7 +8,8 @@
 // Requirements
 //------------------------------------------------------------------------------
 
-const assert = require("chai").assert,
+const Module = require("module"),
+    assert = require("chai").assert,
     leche = require("leche"),
     sinon = require("sinon"),
     path = require("path"),
@@ -127,6 +128,29 @@ function createStubModuleResolver(mapping) {
     };
 }
 
+/**
+ * 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 (mapping.hasOwnProperty(request)) {
+                return mapping[request];
+            }
+            return originalFindPath.call(this, request, paths, isMain);
+        };
+    });
+    afterEach(() => {
+        Module._findPath = originalFindPath; // eslint-disable-line no-underscore-dangle
+    });
+}
+
 //------------------------------------------------------------------------------
 // Tests
 //------------------------------------------------------------------------------
@@ -145,6 +169,10 @@ 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");
@@ -864,6 +892,10 @@ 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());
@@ -895,9 +927,6 @@ describe("ConfigFile", () => {
                 const resolvedPath = path.resolve(PROJECT_PATH, "./node_modules/eslint-plugin-test/index.js");
 
                 const configDeps = {
-                    "../util/module-resolver": createStubModuleResolver({
-                        "eslint-plugin-test": resolvedPath
-                    }),
                     "require-uncached"(filename) {
                         return configDeps[filename];
                     }
@@ -1000,6 +1029,9 @@ describe("ConfigFile", () => {
     describe("resolve()", () => {
 
         describe("Relative to CWD", () => {
+            overrideNativeResolve({
+                "eslint-plugin-foo": getProjectModulePath("eslint-plugin-foo")
+            });
 
             leche.withData([
                 [".eslintrc", path.resolve(".eslintrc")],
@@ -1035,6 +1067,10 @@ describe("ConfigFile", () => {
 
             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],
@@ -1042,7 +1078,7 @@ describe("ConfigFile", () => {
                 ["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", getRelativeModulePath("@foo/eslint-plugin-bar", relativePath), relativePath]
+                ["plugin:@foo/bar/baz", getProjectModulePath("@foo/eslint-plugin-bar"), relativePath]
             ], (input, expected, relativeTo) => {
                 it(`should return ${expected} when passed ${input}`, () => {
 
@@ -1050,9 +1086,7 @@ describe("ConfigFile", () => {
                         "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),
-                        "eslint-plugin-foo": getRelativeModulePath("eslint-plugin-foo", relativePath),
-                        "@foo/eslint-plugin-bar": getRelativeModulePath("@foo/eslint-plugin-bar", relativePath)
+                        "@foo/eslint-config-bar": getRelativeModulePath("@foo/eslint-config-bar", relativePath)
                     };
 
                     const StubbedConfigFile = proxyquire("../../../lib/config/config-file", {
diff --git a/tests/lib/linter.js b/tests/lib/linter.js
index 53290c1ed..667e75e5b 100644
--- a/tests/lib/linter.js
+++ b/tests/lib/linter.js
@@ -3492,6 +3492,15 @@ 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("Variables and references", () => {
diff --git a/tests/lib/rules/object-shorthand.js b/tests/lib/rules/object-shorthand.js
index 31abe8faa..c52f43af0 100644
--- a/tests/lib/rules/object-shorthand.js
+++ b/tests/lib/rules/object-shorthand.js
@@ -427,6 +427,16 @@ ruleTester.run("object-shorthand", rule, {
             output: "var x = {\n  *f() {\n    /* comment */\n    a(b);\n    }\n  }",
             errors: [METHOD_ERROR]
         },
+        {
+            code: "var x = {\n  f: /* comment */ function() {\n  }\n  }",
+            output: null,
+            errors: [METHOD_ERROR]
+        },
+        {
+            code: "var x = {\n f /* comment */: function() {\n  }\n  }",
+            output: null,
+            errors: [METHOD_ERROR]
+        },
         {
             code: "var x = {y: function() {}}",
             output: "var x = {y() {}}",
diff --git a/tests/lib/rules/valid-jsdoc.js b/tests/lib/rules/valid-jsdoc.js
index d6e0219c5..7304192be 100644
--- a/tests/lib/rules/valid-jsdoc.js
+++ b/tests/lib/rules/valid-jsdoc.js
@@ -267,6 +267,32 @@ ruleTester.run("valid-jsdoc", rule, {
             options: [{ requireReturn: false }]
         },
 
+        // async function
+        {
+            code:
+              "/**\n" +
+              " * An async function. Options requires return.\n" +
+              " * @returns {Promise} that is empty\n" +
+              " */\n" +
+              "async function a() {}",
+            options: [{ requireReturn: true }],
+            parserOptions: {
+                ecmaVersion: 2017
+            }
+        },
+        {
+            code:
+              "/**\n" +
+              " * An async function. Options do not require return.\n" +
+              " * @returns {Promise} that is empty\n" +
+              " */\n" +
+              "async function a() {}",
+            options: [{ requireReturn: false }],
+            parserOptions: {
+                ecmaVersion: 2017
+            }
+        },
+
         // type validations
         {
             code: