Repository: eslint/eslint
Pull request #8974 (2017-07-21 03:45:00)

Update: emit a warning for ecmaFeatures rather than throwing an error
<!--
    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] Add something to the core

<!--
    If the item you've checked above has a template, please paste the template questions below and answer them. (If this pull request is addressing an issue, you can just paste a link to the issue here instead.)
-->

<!--
    Please ensure your pull request is ready:

    - Read the pull request guide (http://eslint.org/docs/developer-guide/contributing/pull-requests)
    - Include tests for this change
    - Update documentation for this change (if appropriate)
-->

<!--
    The following is required for all pull requests:
-->

**What changes did you make? (Give an overview)**

(refs https://github.com/eslint/tsc-meetings/issues/51#issuecomment-315669556)

Starting in 4.0.0, ESLint has been throwing a fatal error when encountering unexpected config file properties, including `ecmaFeatures`. However, many old configs still have the top-level `ecmaFeatures` option, which has had no effect since ESLint 1.x. This commit updates ESLint to emit a warning when it encounters a config with `ecmaFeatures`, rather than throwing an error.

This was approved by the TSC in [today's meeting](https://github.com/eslint/tsc-meetings/blob/4a238489c2f035711070e8beaa2ba11827bcc521/notes/2017/2017-07-20.md#stop-throwing-for-ecmafeatures).

**Is there anything you'd like reviewers to focus on?**

Nothing in particular

eslintbot: LGTM
eslintbot: LGTM

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3866a56b3..5121911a0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,32 @@
+v4.3.0 - July 21, 2017
+
+* 91dccdf Update: support more options in prefer-destructuring (#8796) (Victor Hom)
+* 3bebcfd Update: Support generator yields in no constant condition (#8762) (Victor Hom)
+* 96df8c9 Fix: Handle fixing objects containing comments (fixes #8484) (#8944) (Brian Schemp)
+* e39d41d Docs: Make `peerDependencies` package.json snippet valid JSON (#8971) (Sam Adams)
+* a5fd101 Fix: duplicated error message if a crash occurs (fixes #8964) (#8965) (Teddy Katz)
+* f8d122c Docs: trailing commas not allowed in json (#8969) (Scott Fletcher)
+* d09288a Chore: Use `output: null` to assert that a test case is not autofixed. (#8960) (薛定谔的猫)
+* e639358 Update: add question to confirm downgrade (fixes #8870) (#8911) (Toru Nagashima)
+* 601039d Docs: fix badge in eslint-config-eslint readme (#8954) (Teddy Katz)
+* 3c231fa Update: add enforceInMethodNames to no-underscore-dangle (fixes #7065) (#7234) (Gabriele Petronella)
+* 128591f Update: prefer-numeric-literals warns Number.parseInt (fixes #8913) (#8929) (Kevin Partington)
+* 846f8b1 Docs: Clarified that core PRs require issue in maintainer guide (#8927) (Kevin Partington)
+* 55bc35d Fix: Avoid shell mangling during eslint --init (#8936) (Anders Kaseorg)
+* 10c3d78 Chore: fix misleading `indent` test (#8925) (Teddy Katz)
+* fb8005d Update: no-restricted-globals custom error messages (fixes #8315) (#8932) (Kevin Partington)
+* a747b6f Chore: make minor improvements to `indent` internals (#8947) (Teddy Katz)
+* 1ea3723 Update: fix indentation of parenthesized MemberExpressions (fixes #8924) (#8928) (Teddy Katz)
+* 9abc6f7 Update: fix BinaryExpression indentation edge case (fixes #8914) (#8930) (Teddy Katz)
+* 0e90453 Docs: Fixing broken cyclomatic complexity link (fixes #8396) (#8937) (Chris Bargren)
+* a8a8350 Chore: improve performance of `indent` rule (#8905) (Teddy Katz)
+* 764b2a9 Chore: update header info in `indent` (#8926) (Teddy Katz)
+* 597c217 Fix: confusing error if plugins from config is not an array (#8888) (Calvin Freitas)
+* 3c1dd6d Docs: add description of no-sync `allowAtRootLevel` option (fixes #8902) (#8906) (Teddy Katz)
+* 933a9cf Chore: add a fuzzer to detect bugs in core rules (#8422) (Teddy Katz)
+* 45f8cd9 Docs: fix verifyAndFix result property name (#8903) (Tino Vyatkin)
+* 1a89e1c Docs: Fix always-multiline example in multiline-ternary docs (#8904) (Nathan Woltman)
+
 v4.2.0 - July 8, 2017
 
 * e0f0101 Update: fix indentation of nested function parameters (fixes #8892) (#8900) (Teddy Katz)
diff --git a/conf/config-schema.js b/conf/config-schema.js
index 4ef958c40..626e1d54c 100644
--- a/conf/config-schema.js
+++ b/conf/config-schema.js
@@ -12,7 +12,9 @@ const baseConfigProperties = {
     parserOptions: { type: "object" },
     plugins: { type: "array" },
     rules: { type: "object" },
-    settings: { type: "object" }
+    settings: { type: "object" },
+
+    ecmaFeatures: { type: "object" } // deprecated; logs a warning when used
 };
 
 const overrideProperties = Object.assign(
diff --git a/docs/rules/prefer-destructuring.md b/docs/rules/prefer-destructuring.md
index 2925225d3..46a49393d 100644
--- a/docs/rules/prefer-destructuring.md
+++ b/docs/rules/prefer-destructuring.md
@@ -4,6 +4,18 @@ With JavaScript ES6, a new syntax was added for creating variables from an array
 
 ## Rule Details
 
+### Options
+
+This rule takes two sets of configuration objects. The first object parameter determines what types of destructuring the rule applies to.
+
+The two properties, `array` and `object`, can be used to turn on or off the destructuring requirement for each of those types independently. By default, both are true.
+
+Alternatively, you can use separate configurations for different assignment types. It accepts 2 other keys instead of `array` and `object`.
+
+One key is `VariableDeclarator` and the other is `AssignmentExpression`, which can be used to control the destructuring requirement for each of those types independently. Each property accepts an object that accepts two properties, `array` and `object`, which can be used to control the destructuring requirement for each of `array` and `object` independently for variable declarations and assignment expressions.  By default, `array` and `object` are set to true for both `VariableDeclarator` and `AssignmentExpression`.
+
+The rule has a second object with a single key, `enforceForRenamedProperties`, which determines whether the `object` destructuring applies to renamed variables.
+
 Examples of **incorrect** code for this rule:
 
 ```javascript
@@ -27,14 +39,6 @@ var { foo } = object;
 var foo = object.bar;
 ```
 
-### Options
-
-This rule takes two sets of configuration objects; the first controls the types that the rule is applied to, and the second controls the way those objects are evaluated.
-
-The first has two properties, `array` and `object`, which can be used to turn on or off the destructuring requirement for each of those types independently.  By default, both are `true`.
-
-The second has a single property, `enforceForRenamedProperties`, that controls whether or not the `object` destructuring rules are applied in cases where the variable requires the property being access to be renamed.
-
 Examples of **incorrect** code when `enforceForRenamedProperties` is enabled:
 
 ```javascript
@@ -47,7 +51,7 @@ Examples of **correct** code when `enforceForRenamedProperties` is enabled:
 var { bar: foo } = object;
 ```
 
-An example configuration, with the defaults filled in, looks like this:
+An example configuration, with the defaults `array` and `object` filled in, looks like this:
 
 ```json
 {
@@ -62,6 +66,77 @@ An example configuration, with the defaults filled in, looks like this:
 }
 ```
 
+The two properties, `array` and `object`, which can be used to turn on or off the destructuring requirement for each of those types independently. By default, both are true.
+
+For example, the following configuration enforces only object destructuring, but not array destructuring:
+
+```json
+{
+  "rules": {
+    "prefer-destructuring": ["error", {"object": true, "array": false}]
+  }
+}
+```
+
+An example configuration, with the defaults `VariableDeclarator` and `AssignmentExpression` filled in, looks like this:
+
+```json
+{
+  "rules": {
+    "prefer-destructuring": ["error", {
+      "VariableDeclarator": {
+        "array": false,
+        "object": true
+      },
+      "AssignmentExpression": {
+        "array": true,
+        "object": true
+      }
+    }, {
+      "enforceForRenamedProperties": false
+    }]
+  }
+}
+```
+
+The two properties, `VariableDeclarator` and `AssignmentExpression`, which can be used to turn on or off the destructuring requirement for `array` and `object`. By default, all values are true.
+
+For example, the following configuration enforces object destructuring in variable declarations and enforces array destructuring in assignment expressions.
+
+```json
+{
+  "rules": {
+    "prefer-destructuring": ["error", {
+      "VariableDeclarator": {
+        "array": false,
+        "object": true
+      },
+      "AssignmentExpression": {
+        "array": true,
+        "object": false
+      }
+    }, {
+      "enforceForRenamedProperties": false
+    }]
+  }
+}
+
+```
+
+Examples of **correct** code when object destructuring in `VariableDeclarator` is enforced:
+
+```javascript
+/* eslint prefer-destructuring: ["error", {VariableDeclarator: {object: true}}] */
+var {bar: foo} = object;
+```
+
+Examples of **correct** code when array destructuring in `AssignmentExpression` is enforced:
+
+```javascript
+/* eslint prefer-destructuring: ["error", {AssignmentExpression: {array: true}}] */
+[bar] = array;
+```
+
 ## When Not To Use It
 
 If you want to be able to access array indices or object properties directly, you can either configure the rule to your tastes or disable the rule entirely.
diff --git a/lib/config/config-file.js b/lib/config/config-file.js
index 3c790cf3b..091a24f34 100644
--- a/lib/config/config-file.js
+++ b/lib/config/config-file.js
@@ -546,7 +546,7 @@ function loadFromDisk(resolvedPath, configContext) {
         }
 
         // validate the configuration before continuing
-        validator.validate(config, resolvedPath, configContext.linterContext.rules, configContext.linterContext.environments);
+        validator.validate(config, resolvedPath.configFullName, configContext.linterContext.rules, configContext.linterContext.environments);
 
         /*
          * If an `extends` property is defined, it represents a configuration file to use as
diff --git a/lib/config/config-validator.js b/lib/config/config-validator.js
index fb5a96564..22bc1efbd 100644
--- a/lib/config/config-validator.js
+++ b/lib/config/config-validator.js
@@ -10,6 +10,7 @@
 //------------------------------------------------------------------------------
 
 const ajv = require("../util/ajv"),
+    lodash = require("lodash"),
     configSchema = require("../../conf/config-schema.js"),
     util = require("util");
 
@@ -179,6 +180,25 @@ function formatErrors(errors) {
     }).map(message => `\t- ${message}.\n`).join("");
 }
 
+/**
+ * Emits a deprecation warning containing a given filepath. A new deprecation warning is emitted
+ * 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.
+ * @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})`
+    )();
+});
+
 /**
  * Validates the top level properties of the config object.
  * @param {Object} config The config object to validate.
@@ -189,7 +209,11 @@ function validateConfigSchema(config, source) {
     validateSchema = validateSchema || ajv.compile(configSchema);
 
     if (!validateSchema(config)) {
-        throw new Error(`${source}:\n\tESLint configuration is invalid:\n${formatErrors(validateSchema.errors)}`);
+        throw new Error(`ESLint configuration in ${source} is invalid:\n${formatErrors(validateSchema.errors)}`);
+    }
+
+    if (Object.prototype.hasOwnProperty.call(config, "ecmaFeatures")) {
+        emitEcmaFeaturesWarning(source);
     }
 }
 
diff --git a/lib/rules/no-constant-condition.js b/lib/rules/no-constant-condition.js
index 7178d5dbe..31e5b372c 100644
--- a/lib/rules/no-constant-condition.js
+++ b/lib/rules/no-constant-condition.js
@@ -33,7 +33,10 @@ module.exports = {
 
     create(context) {
         const options = context.options[0] || {},
-            checkLoops = options.checkLoops !== false;
+            checkLoops = options.checkLoops !== false,
+            loopSetStack = [];
+
+        let loopsInCurrentScope = new Set();
 
         //--------------------------------------------------------------------------
         // Helpers
@@ -114,18 +117,64 @@ module.exports = {
             return false;
         }
 
+        /**
+         * Tracks when the given node contains a constant condition.
+         * @param {ASTNode} node The AST node to check.
+         * @returns {void}
+         * @private
+         */
+        function trackConstantConditionLoop(node) {
+            if (node.test && isConstant(node.test, true)) {
+                loopsInCurrentScope.add(node);
+            }
+        }
+
+        /**
+         * Reports when the set contains the given constant condition node
+         * @param {ASTNode} node The AST node to check.
+         * @returns {void}
+         * @private
+         */
+        function checkConstantConditionLoopInSet(node) {
+            if (loopsInCurrentScope.has(node)) {
+                loopsInCurrentScope.delete(node);
+                context.report({ node, message: "Unexpected constant condition." });
+            }
+        }
+
         /**
          * Reports when the given node contains a constant condition.
          * @param {ASTNode} node The AST node to check.
          * @returns {void}
          * @private
          */
-        function checkConstantCondition(node) {
+        function reportIfConstant(node) {
             if (node.test && isConstant(node.test, true)) {
                 context.report({ node, message: "Unexpected constant condition." });
             }
         }
 
+        /**
+         * Stores current set of constant loops in loopSetStack temporarily
+         * and uses a new set to track constant loops
+         * @returns {void}
+         * @private
+         */
+        function enterFunction() {
+            loopSetStack.push(loopsInCurrentScope);
+            loopsInCurrentScope = new Set();
+        }
+
+        /**
+         * Reports when the set still contains stored constant conditions
+         * @param {ASTNode} node The AST node to check.
+         * @returns {void}
+         * @private
+         */
+        function exitFunction() {
+            loopsInCurrentScope = loopSetStack.pop();
+        }
+
         /**
          * Checks node when checkLoops option is enabled
          * @param {ASTNode} node The AST node to check.
@@ -134,7 +183,7 @@ module.exports = {
          */
         function checkLoop(node) {
             if (checkLoops) {
-                checkConstantCondition(node);
+                trackConstantConditionLoop(node);
             }
         }
 
@@ -143,11 +192,18 @@ module.exports = {
         //--------------------------------------------------------------------------
 
         return {
-            ConditionalExpression: checkConstantCondition,
-            IfStatement: checkConstantCondition,
+            ConditionalExpression: reportIfConstant,
+            IfStatement: reportIfConstant,
             WhileStatement: checkLoop,
+            "WhileStatement:exit": checkConstantConditionLoopInSet,
             DoWhileStatement: checkLoop,
-            ForStatement: checkLoop
+            "DoWhileStatement:exit": checkConstantConditionLoopInSet,
+            ForStatement: checkLoop,
+            "ForStatement > .test": node => checkLoop(node.parent),
+            "ForStatement:exit": checkConstantConditionLoopInSet,
+            FunctionDeclaration: enterFunction,
+            "FunctionDeclaration:exit": exitFunction,
+            YieldExpression: () => loopsInCurrentScope.clear()
         };
 
     }
diff --git a/lib/rules/object-curly-newline.js b/lib/rules/object-curly-newline.js
index b78cb9cfc..42f277873 100644
--- a/lib/rules/object-curly-newline.js
+++ b/lib/rules/object-curly-newline.js
@@ -143,6 +143,8 @@ module.exports = {
                     first.loc.start.line !== last.loc.end.line
                 )
             );
+            const hasCommentsFirstToken = astUtils.isCommentToken(first);
+            const hasCommentsLastToken = astUtils.isCommentToken(last);
 
             /*
              * Use tokens or comments to check multiline or not.
@@ -162,6 +164,10 @@ module.exports = {
                         node,
                         loc: openBrace.loc.start,
                         fix(fixer) {
+                            if (hasCommentsFirstToken) {
+                                return null;
+                            }
+
                             return fixer.insertTextAfter(openBrace, "\n");
                         }
                     });
@@ -172,6 +178,10 @@ module.exports = {
                         node,
                         loc: closeBrace.loc.start,
                         fix(fixer) {
+                            if (hasCommentsLastToken) {
+                                return null;
+                            }
+
                             return fixer.insertTextBefore(closeBrace, "\n");
                         }
                     });
@@ -190,6 +200,10 @@ module.exports = {
                         node,
                         loc: openBrace.loc.start,
                         fix(fixer) {
+                            if (hasCommentsFirstToken) {
+                                return null;
+                            }
+
                             return fixer.removeRange([
                                 openBrace.range[1],
                                 first.range[0]
@@ -206,6 +220,10 @@ module.exports = {
                         node,
                         loc: closeBrace.loc.start,
                         fix(fixer) {
+                            if (hasCommentsLastToken) {
+                                return null;
+                            }
+
                             return fixer.removeRange([
                                 last.range[1],
                                 closeBrace.range[0]
diff --git a/lib/rules/prefer-destructuring.js b/lib/rules/prefer-destructuring.js
index 7f472b423..ebf4e713b 100644
--- a/lib/rules/prefer-destructuring.js
+++ b/lib/rules/prefer-destructuring.js
@@ -15,19 +15,55 @@ module.exports = {
             category: "ECMAScript 6",
             recommended: false
         },
-
         schema: [
             {
-                type: "object",
-                properties: {
-                    array: {
-                        type: "boolean"
+
+                // old support {array: Boolean, object: Boolean}
+                // new support {VariableDeclarator: {}, AssignmentExpression: {}}
+                oneOf: [
+                    {
+                        type: "object",
+                        properties: {
+                            VariableDeclarator: {
+                                type: "object",
+                                properties: {
+                                    array: {
+                                        type: "boolean"
+                                    },
+                                    object: {
+                                        type: "boolean"
+                                    }
+                                },
+                                additionalProperties: false
+                            },
+                            AssignmentExpression: {
+                                type: "object",
+                                properties: {
+                                    array: {
+                                        type: "boolean"
+                                    },
+                                    object: {
+                                        type: "boolean"
+                                    }
+                                },
+                                additionalProperties: false
+                            }
+                        },
+                        additionalProperties: false
                     },
-                    object: {
-                        type: "boolean"
+                    {
+                        type: "object",
+                        properties: {
+                            array: {
+                                type: "boolean"
+                            },
+                            object: {
+                                type: "boolean"
+                            }
+                        },
+                        additionalProperties: false
                     }
-                },
-                additionalProperties: false
+                ]
             },
             {
                 type: "object",
@@ -42,26 +78,17 @@ module.exports = {
     },
     create(context) {
 
-        let checkArrays = true;
-        let checkObjects = true;
-        let enforceForRenamedProperties = false;
         const enabledTypes = context.options[0];
-        const additionalOptions = context.options[1];
+        const enforceForRenamedProperties = context.options[1] && context.options[1].enforceForRenamedProperties;
+        let normalizedOptions = {
+            VariableDeclarator: { array: true, object: true },
+            AssignmentExpression: { array: true, object: true }
+        };
 
         if (enabledTypes) {
-            if (typeof enabledTypes.array !== "undefined") {
-                checkArrays = enabledTypes.array;
-            }
-
-            if (typeof enabledTypes.object !== "undefined") {
-                checkObjects = enabledTypes.object;
-            }
-        }
-
-        if (additionalOptions) {
-            if (typeof additionalOptions.enforceForRenamedProperties !== "undefined") {
-                enforceForRenamedProperties = additionalOptions.enforceForRenamedProperties;
-            }
+            normalizedOptions = typeof enabledTypes.array !== "undefined" || typeof enabledTypes.object !== "undefined"
+                ? { VariableDeclarator: enabledTypes, AssignmentExpression: enabledTypes }
+                : enabledTypes;
         }
 
         //--------------------------------------------------------------------------
@@ -69,7 +96,18 @@ module.exports = {
         //--------------------------------------------------------------------------
 
         /**
-         * Determines if the given node node is accessing an array index
+         * @param {string} nodeType "AssignmentExpression" or "VariableDeclarator"
+         * @param {string} destructuringType "array" or "object"
+         * @returns {boolean} `true` if the destructuring type should be checked for the given node
+         */
+        function shouldCheck(nodeType, destructuringType) {
+            return normalizedOptions &&
+                normalizedOptions[nodeType] &&
+                normalizedOptions[nodeType][destructuringType];
+        }
+
+        /**
+         * Determines if the given node is accessing an array index
          *
          * This is used to differentiate array index access from object property
          * access.
@@ -110,22 +148,22 @@ module.exports = {
             }
 
             if (isArrayIndexAccess(rightNode)) {
-                if (checkArrays) {
+                if (shouldCheck(reportNode.type, "array")) {
                     report(reportNode, "array");
                 }
                 return;
             }
 
-            if (checkObjects && enforceForRenamedProperties) {
+            if (shouldCheck(reportNode.type, "object") && enforceForRenamedProperties) {
                 report(reportNode, "object");
                 return;
             }
 
-            if (checkObjects) {
+            if (shouldCheck(reportNode.type, "object")) {
                 const property = rightNode.property;
 
-                if ((property.type === "Literal" && leftNode.name === property.value) ||
-                    (property.type === "Identifier" && leftNode.name === property.name)) {
+                if ((property.type === "Literal" && leftNode.name === property.value) || (property.type === "Identifier" &&
+                    leftNode.name === property.name)) {
                     report(reportNode, "object");
                 }
             }
diff --git a/package.json b/package.json
index ca6e3cd91..984592b1f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "eslint",
-  "version": "4.2.0",
+  "version": "4.3.0",
   "author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
   "description": "An AST-based pattern checker for JavaScript.",
   "bin": {


Test patch:
diff --git a/lib/testers/rule-tester.js b/lib/testers/rule-tester.js
index fc6781fdc..41d2a3109 100644
--- a/lib/testers/rule-tester.js
+++ b/lib/testers/rule-tester.js
@@ -9,7 +9,7 @@
 /*
  * This is a wrapper around mocha to allow for DRY unittests for eslint
  * Format:
- * RuleTester.add("{ruleName}", {
+ * RuleTester.run("{ruleName}", {
  *      valid: [
  *          "{code}",
  *          { code: "{code}", options: {options}, globals: {globals}, parser: "{parser}", settings: {settings} }
diff --git a/tests/bin/eslint.js b/tests/bin/eslint.js
index 0774500f0..1c45aa429 100644
--- a/tests/bin/eslint.js
+++ b/tests/bin/eslint.js
@@ -280,6 +280,28 @@ describe("bin/eslint.js", () => {
         });
     });
 
+
+    describe("emitting a warning for ecmaFeatures", () => {
+        it("does not emit a warning when it does not find an ecmaFeatures option", () => {
+            const child = runESLint(["Makefile.js"]);
+
+            const exitCodePromise = assertExitCode(child, 0);
+            const outputPromise = getOutput(child).then(output => assert.strictEqual(output.stderr, ""));
+
+            return Promise.all([exitCodePromise, outputPromise]);
+        });
+        it("emits a warning when it finds an ecmaFeatures option", () => {
+            const child = runESLint(["-c", "tests/fixtures/config-file/ecma-features/.eslintrc.yml", "Makefile.js"]);
+
+            const exitCodePromise = assertExitCode(child, 0);
+            const outputPromise = getOutput(child).then(output => {
+                assert.include(output.stderr, "The 'ecmaFeatures' config file property is deprecated, and has no effect.");
+            });
+
+            return Promise.all([exitCodePromise, outputPromise]);
+        });
+    });
+
     afterEach(() => {
 
         // Clean up all the processes after every test.
diff --git a/tests/fixtures/config-file/ecma-features/.eslintrc.yml b/tests/fixtures/config-file/ecma-features/.eslintrc.yml
new file mode 100644
index 000000000..c8538f3c4
--- /dev/null
+++ b/tests/fixtures/config-file/ecma-features/.eslintrc.yml
@@ -0,0 +1,1 @@
+ecmaFeatures: {}
diff --git a/tests/fixtures/config-file/invalid/invalid-top-level-property.yml b/tests/fixtures/config-file/invalid/invalid-top-level-property.yml
new file mode 100644
index 000000000..7ea8ce744
--- /dev/null
+++ b/tests/fixtures/config-file/invalid/invalid-top-level-property.yml
@@ -0,0 +1,1 @@
+invalidProperty: 3
diff --git a/tests/lib/config/config-file.js b/tests/lib/config/config-file.js
index 23a206b05..210a9de35 100644
--- a/tests/lib/config/config-file.js
+++ b/tests/lib/config/config-file.js
@@ -993,6 +993,18 @@ describe("ConfigFile", () => {
                 });
             });
         });
+
+        it("throws an error including the config file name if the config file is invalid", () => {
+            const configFilePath = getFixturePath("invalid/invalid-top-level-property.yml");
+
+            try {
+                ConfigFile.load(configFilePath, configContext);
+            } catch (err) {
+                assert.include(err.message, `ESLint configuration in ${configFilePath} is invalid`);
+                return;
+            }
+            assert.fail();
+        });
     });
 
     describe("resolve()", () => {
diff --git a/tests/lib/config/config-validator.js b/tests/lib/config/config-validator.js
index 5e42823b2..6fc24f0ad 100644
--- a/tests/lib/config/config-validator.js
+++ b/tests/lib/config/config-validator.js
@@ -370,31 +370,31 @@ describe("Validator", () => {
             it("should throw if override does not specify files", () => {
                 const fn = validator.validate.bind(null, { overrides: [{ rules: {} }] }, "tests", linter.rules, linter.environments);
 
-                assert.throws(fn, "tests:\n\tESLint configuration is invalid:\n\t- \"overrides[0]\" should have required property 'files'. Value: {\"rules\":{}}.\n");
+                assert.throws(fn, "ESLint configuration in tests is invalid:\n\t- \"overrides[0]\" should have required property 'files'. Value: {\"rules\":{}}.\n");
             });
 
             it("should throw if override has an empty files array", () => {
                 const fn = validator.validate.bind(null, { overrides: [{ files: [] }] }, "tests", linter.rules, linter.environments);
 
-                assert.throws(fn, "tests:\n\tESLint configuration is invalid:\n\t- Property \"overrides[0].files\" is the wrong type (expected string but got `[]`).\n\t- \"overrides[0].files\" should NOT have less than 1 items. Value: [].\n\t- \"overrides[0].files\" should match exactly one schema in oneOf. Value: [].\n");
+                assert.throws(fn, "ESLint configuration in tests is invalid:\n\t- Property \"overrides[0].files\" is the wrong type (expected string but got `[]`).\n\t- \"overrides[0].files\" should NOT have less than 1 items. Value: [].\n\t- \"overrides[0].files\" should match exactly one schema in oneOf. Value: [].\n");
             });
 
             it("should throw if override has nested overrides", () => {
                 const fn = validator.validate.bind(null, { overrides: [{ files: "*", overrides: [{ files: "*", rules: {} }] }] }, "tests", linter.rules, linter.environments);
 
-                assert.throws(fn, "tests:\n\tESLint configuration is invalid:\n\t- Unexpected top-level property \"overrides[0].overrides\".\n");
+                assert.throws(fn, "ESLint configuration in tests is invalid:\n\t- Unexpected top-level property \"overrides[0].overrides\".\n");
             });
 
             it("should throw if override extends", () => {
                 const fn = validator.validate.bind(null, { overrides: [{ files: "*", extends: "eslint-recommended" }] }, "tests", linter.rules, linter.environments);
 
-                assert.throws(fn, "tests:\n\tESLint configuration is invalid:\n\t- Unexpected top-level property \"overrides[0].extends\".\n");
+                assert.throws(fn, "ESLint configuration in tests is invalid:\n\t- Unexpected top-level property \"overrides[0].extends\".\n");
             });
 
             it("should throw if override tries to set root", () => {
                 const fn = validator.validate.bind(null, { overrides: [{ files: "*", root: "true" }] }, "tests", linter.rules, linter.environments);
 
-                assert.throws(fn, "tests:\n\tESLint configuration is invalid:\n\t- Unexpected top-level property \"overrides[0].root\".\n");
+                assert.throws(fn, "ESLint configuration in tests is invalid:\n\t- Unexpected top-level property \"overrides[0].root\".\n");
             });
         });
 
diff --git a/tests/lib/rules/no-constant-condition.js b/tests/lib/rules/no-constant-condition.js
index 244ab63ec..41f3d4bc3 100644
--- a/tests/lib/rules/no-constant-condition.js
+++ b/tests/lib/rules/no-constant-condition.js
@@ -16,7 +16,7 @@ const rule = require("../../../lib/rules/no-constant-condition"),
 // Tests
 //------------------------------------------------------------------------------
 
-const ruleTester = new RuleTester();
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
 
 ruleTester.run("no-constant-condition", rule, {
     valid: [
@@ -58,7 +58,17 @@ ruleTester.run("no-constant-condition", rule, {
         // { checkLoops: false }
         { code: "while(true);", options: [{ checkLoops: false }] },
         { code: "for(;true;);", options: [{ checkLoops: false }] },
-        { code: "do{}while(true)", options: [{ checkLoops: false }] }
+        { code: "do{}while(true)", options: [{ checkLoops: false }] },
+
+        { code: "function* foo(){while(true){yield 'foo';}}" },
+        { code: "function* foo(){for(;true;){yield 'foo';}}" },
+        { code: "function* foo(){do{yield 'foo';}while(true)}" },
+        { code: "function* foo(){while (true) { while(true) {yield;}}}" },
+        { code: "function* foo() {for (; yield; ) {}}" },
+        { code: "function* foo() {for (; ; yield) {}}" },
+        { code: "function* foo() {while (true) {function* foo() {yield;}yield;}}" },
+        { code: "function* foo() { for (let x = yield; x < 10; x++) {yield;}yield;}" },
+        { code: "function* foo() { for (let x = yield; ; x++) { yield; }}" }
     ],
     invalid: [
         { code: "for(;true;);", errors: [{ message: "Unexpected constant condition.", type: "ForStatement" }] },
@@ -78,7 +88,8 @@ ruleTester.run("no-constant-condition", rule, {
         { code: "while(~!0);", errors: [{ message: "Unexpected constant condition.", type: "WhileStatement" }] },
         { code: "while(x = 1);", errors: [{ message: "Unexpected constant condition.", type: "WhileStatement" }] },
         { code: "while(function(){});", errors: [{ message: "Unexpected constant condition.", type: "WhileStatement" }] },
-        { code: "while(() => {});", parserOptions: { ecmaVersion: 6 }, errors: [{ message: "Unexpected constant condition.", type: "WhileStatement" }] },
+        { code: "while(true);", errors: [{ message: "Unexpected constant condition.", type: "WhileStatement" }] },
+        { code: "while(() => {});", errors: [{ message: "Unexpected constant condition.", type: "WhileStatement" }] },
 
         // #5228 , typeof conditions
         { code: "if(typeof x){}", errors: [{ message: "Unexpected constant condition.", type: "IfStatement" }] },
@@ -103,6 +114,47 @@ ruleTester.run("no-constant-condition", rule, {
         { code: "if(abc==='str' || true){}", errors: [{ message: "Unexpected constant condition.", type: "IfStatement" }] },
         { code: "if(abc==='str' || true || def ==='str'){}", errors: [{ message: "Unexpected constant condition.", type: "IfStatement" }] },
         { code: "if(false || true){}", errors: [{ message: "Unexpected constant condition.", type: "IfStatement" }] },
-        { code: "if(typeof abc==='str' || true){}", errors: [{ message: "Unexpected constant condition.", type: "IfStatement" }] }
+        { code: "if(typeof abc==='str' || true){}", errors: [{ message: "Unexpected constant condition.", type: "IfStatement" }] },
+
+        {
+            code: "function* foo(){while(true){} yield 'foo';}",
+            errors: [{ message: "Unexpected constant condition.", type: "WhileStatement" }]
+        },
+        {
+            code: "function* foo(){while(true){if (true) {yield 'foo';}}}",
+            errors: [{ message: "Unexpected constant condition.", type: "IfStatement" }]
+        },
+        {
+            code: "function* foo(){while(true){yield 'foo';} while(true) {}}",
+            errors: [{ message: "Unexpected constant condition.", type: "WhileStatement" }]
+        },
+        {
+            code: "var a = function* foo(){while(true){} yield 'foo';}",
+            errors: [{ message: "Unexpected constant condition.", type: "WhileStatement" }]
+        },
+        {
+            code: "while (true) { function* foo() {yield;}}",
+            errors: [{ message: "Unexpected constant condition.", type: "WhileStatement" }]
+        },
+        {
+            code: "function* foo(){if (true) {yield 'foo';}}",
+            errors: [{ message: "Unexpected constant condition.", type: "IfStatement" }]
+        },
+        {
+            code: "function* foo() {for (let foo = yield; true;) {}}",
+            errors: [{ message: "Unexpected constant condition.", type: "ForStatement" }]
+        },
+        {
+            code: "function* foo() {for (foo = yield; true;) {}}",
+            errors: [{ message: "Unexpected constant condition.", type: "ForStatement" }]
+        },
+        {
+            code: "function foo() {while (true) {function* bar() {while (true) {yield;}}}}",
+            errors: [{ message: "Unexpected constant condition.", type: "WhileStatement" }]
+        },
+        {
+            code: "function* foo() { for (let foo = 1 + 2 + 3 + (yield); true; baz) {}}",
+            errors: [{ message: "Unexpected constant condition.", type: "ForStatement" }]
+        }
     ]
 });
diff --git a/tests/lib/rules/object-curly-newline.js b/tests/lib/rules/object-curly-newline.js
index 05376e0d4..ce8f87165 100644
--- a/tests/lib/rules/object-curly-newline.js
+++ b/tests/lib/rules/object-curly-newline.js
@@ -584,6 +584,31 @@ ruleTester.run("object-curly-newline", rule, {
                 { line: 2, column: 1, message: "Unexpected line break before this closing brace." }
             ]
         },
+        {
+            code: [
+                "var a = {",
+                " /* comment */ ",
+                "};"
+            ].join("\n"),
+            output: null,
+            options: [{ multiline: true }],
+            errors: [
+                { line: 1, column: 9, message: "Unexpected line break after this opening brace." },
+                { line: 3, column: 1, message: "Unexpected line break before this closing brace." }
+            ]
+        },
+        {
+            code: [
+                "var a = { // comment",
+                "};"
+            ].join("\n"),
+            output: null,
+            options: [{ multiline: true }],
+            errors: [
+                { line: 1, column: 9, message: "Unexpected line break after this opening brace." },
+                { line: 2, column: 1, message: "Unexpected line break before this closing brace." }
+            ]
+        },
         {
             code: [
                 "var b = {",
@@ -599,6 +624,22 @@ ruleTester.run("object-curly-newline", rule, {
                 { line: 3, column: 1, message: "Unexpected line break before this closing brace." }
             ]
         },
+        {
+            code: [
+                "var b = {",
+                "   a: 1 // comment",
+                "};"
+            ].join("\n"),
+            output: [
+                "var b = {a: 1 // comment",
+                "};"
+            ].join("\n"),
+            options: [{ multiline: true }],
+            errors: [
+                { line: 1, column: 9, message: "Unexpected line break after this opening brace." },
+                { line: 3, column: 1, message: "Unexpected line break before this closing brace." }
+            ]
+        },
         {
             code: [
                 "var c = {",
@@ -614,6 +655,22 @@ ruleTester.run("object-curly-newline", rule, {
                 { line: 3, column: 1, message: "Unexpected line break before this closing brace." }
             ]
         },
+        {
+            code: [
+                "var c = {",
+                "    a: 1, b: 2 // comment",
+                "};"
+            ].join("\n"),
+            output: [
+                "var c = {a: 1, b: 2 // comment",
+                "};"
+            ].join("\n"),
+            options: [{ multiline: true }],
+            errors: [
+                { line: 1, column: 9, message: "Unexpected line break after this opening brace." },
+                { line: 3, column: 1, message: "Unexpected line break before this closing brace." }
+            ]
+        },
         {
             code: [
                 "var d = {a: 1,",
@@ -631,6 +688,23 @@ ruleTester.run("object-curly-newline", rule, {
                 { line: 2, column: 9, message: "Expected a line break before this closing brace." }
             ]
         },
+        {
+            code: [
+                "var d = {a: 1, // comment",
+                "    b: 2};"
+            ].join("\n"),
+            output: [
+                "var d = {",
+                "a: 1, // comment",
+                "    b: 2",
+                "};"
+            ].join("\n"),
+            options: [{ multiline: true }],
+            errors: [
+                { line: 1, column: 9, message: "Expected a line break after this opening brace." },
+                { line: 2, column: 9, message: "Expected a line break before this closing brace." }
+            ]
+        },
         {
             code: [
                 "var e = {a: function foo() {",
@@ -650,6 +724,71 @@ ruleTester.run("object-curly-newline", rule, {
                 { line: 3, column: 2, message: "Expected a line break before this closing brace." }
             ]
         },
+        {
+            code: [
+                "var e = {a: function foo() { // comment",
+                "    dosomething();",
+                "}};"
+            ].join("\n"),
+            output: [
+                "var e = {",
+                "a: function foo() { // comment",
+                "    dosomething();",
+                "}",
+                "};"
+            ].join("\n"),
+            options: [{ multiline: true }],
+            errors: [
+                { line: 1, column: 9, message: "Expected a line break after this opening brace." },
+                { line: 3, column: 2, message: "Expected a line break before this closing brace." }
+            ]
+        },
+        {
+            code: [
+                "var e = {a: 1, /* comment */",
+                "    b: 2, // another comment",
+                "};"
+            ].join("\n"),
+            output: [
+                "var e = {",
+                "a: 1, /* comment */",
+                "    b: 2, // another comment",
+                "};"
+            ].join("\n"),
+            options: [{ multiline: true }],
+            errors: [
+                { line: 1, column: 9, message: "Expected a line break after this opening brace." }
+            ]
+        },
+        {
+            code: [
+                "var f = { /* comment */ a:",
+                "2",
+                "};"
+            ].join("\n"),
+            output: null,
+            options: [{ multiline: true }],
+            errors: [
+                { line: 1, column: 9, message: "Expected a line break after this opening brace." }
+            ]
+        },
+        {
+            code: [
+                "var f = {",
+                "/* comment */",
+                "a: 1};"
+            ].join("\n"),
+            output: [
+                "var f = {",
+                "/* comment */",
+                "a: 1",
+                "};"
+            ].join("\n"),
+            options: [{ multiline: true }],
+            errors: [
+                { line: 3, column: 5, message: "Expected a line break before this closing brace." }
+            ]
+        },
 
         // "minProperties" ----------------------------------------------------------
         {
diff --git a/tests/lib/rules/prefer-destructuring.js b/tests/lib/rules/prefer-destructuring.js
index bf33b8a6e..4ac2ba3e8 100644
--- a/tests/lib/rules/prefer-destructuring.js
+++ b/tests/lib/rules/prefer-destructuring.js
@@ -31,47 +31,75 @@ ruleTester.run("prefer-destructuring", rule, {
             // Ensure that this doesn't break variable declarating without assignment
             code: "var foo;"
         },
+        {
+
+            // Ensure that the default behavior does not require desturcturing when renaming
+            code: "var foo = object.bar;",
+            options: [{ VariableDeclarator: { object: true } }]
+        },
         {
 
             // Ensure that the default behavior does not require desturcturing when renaming
             code: "var foo = object.bar;",
             options: [{ object: true }]
         },
+        {
+            code: "var foo = object.bar;",
+            options: [{ VariableDeclarator: { object: true } }, { enforceForRenamedProperties: false }]
+        },
         {
             code: "var foo = object.bar;",
             options: [{ object: true }, { enforceForRenamedProperties: false }]
         },
         {
             code: "var foo = object['bar'];",
-            options: [{ object: true }, { enforceForRenamedProperties: false }]
+            options: [{ VariableDeclarator: { object: true } }, { enforceForRenamedProperties: false }]
         },
         {
             code: "var foo = object[bar];",
             options: [{ object: true }, { enforceForRenamedProperties: false }]
         },
+        {
+            code: "var { bar: foo } = object;",
+            options: [{ VariableDeclarator: { object: true } }, { enforceForRenamedProperties: true }]
+        },
         {
             code: "var { bar: foo } = object;",
             options: [{ object: true }, { enforceForRenamedProperties: true }]
         },
+        {
+            code: "var { [bar]: foo } = object;",
+            options: [{ VariableDeclarator: { object: true } }, { enforceForRenamedProperties: true }]
+        },
         {
             code: "var { [bar]: foo } = object;",
             options: [{ object: true }, { enforceForRenamedProperties: true }]
         },
+        {
+            code: "var foo = array[0];",
+            options: [{ VariableDeclarator: { array: false } }]
+        },
         {
             code: "var foo = array[0];",
             options: [{ array: false }]
         },
         {
             code: "var foo = object.foo;",
-            options: [{ object: false }]
+            options: [{ VariableDeclarator: { object: false } }]
         },
         {
             code: "var foo = object['foo'];",
-            options: [{ object: false }]
+            options: [{ VariableDeclarator: { object: false } }]
         },
         {
             code: "({ foo } = object);"
         },
+        {
+
+            // Fix #8654
+            code: "var foo = array[0];",
+            options: [{ VariableDeclarator: { array: false } }, { enforceForRenamedProperties: true }]
+        },
         {
 
             // Fix #8654
@@ -80,7 +108,39 @@ ruleTester.run("prefer-destructuring", rule, {
         },
         "[foo] = array;",
         "foo += array[0]",
-        "foo += bar.foo"
+        "foo += bar.foo",
+        {
+            code: "foo = object.foo;",
+            options: [{ AssignmentExpression: { object: false } }, { enforceForRenamedProperties: true }]
+        },
+        {
+            code: "foo = object.foo;",
+            options: [{ AssignmentExpression: { object: false } }, { enforceForRenamedProperties: false }]
+        },
+        {
+            code: "foo = array[0];",
+            options: [{ AssignmentExpression: { array: false } }, { enforceForRenamedProperties: true }]
+        },
+        {
+            code: "foo = array[0];",
+            options: [{ AssignmentExpression: { array: false } }, { enforceForRenamedProperties: false }]
+        },
+        {
+            code: "foo = array[0];",
+            options: [{ VariableDeclarator: { array: true }, AssignmentExpression: { array: false } }, { enforceForRenamedProperties: false }]
+        },
+        {
+            code: "var foo = array[0];",
+            options: [{ VariableDeclarator: { array: false }, AssignmentExpression: { array: true } }, { enforceForRenamedProperties: false }]
+        },
+        {
+            code: "foo = object.foo;",
+            options: [{ VariableDeclarator: { object: true }, AssignmentExpression: { object: false } }]
+        },
+        {
+            code: "var foo = object.foo;",
+            options: [{ VariableDeclarator: { object: false }, AssignmentExpression: { object: true } }]
+        }
     ],
 
     invalid: [
@@ -105,6 +165,14 @@ ruleTester.run("prefer-destructuring", rule, {
                 type: "VariableDeclarator"
             }]
         },
+        {
+            code: "var foobar = object.bar;",
+            options: [{ VariableDeclarator: { object: true } }, { enforceForRenamedProperties: true }],
+            errors: [{
+                message: "Use object destructuring.",
+                type: "VariableDeclarator"
+            }]
+        },
         {
             code: "var foobar = object.bar;",
             options: [{ object: true }, { enforceForRenamedProperties: true }],
@@ -113,6 +181,14 @@ ruleTester.run("prefer-destructuring", rule, {
                 type: "VariableDeclarator"
             }]
         },
+        {
+            code: "var foo = object[bar];",
+            options: [{ VariableDeclarator: { object: true } }, { enforceForRenamedProperties: true }],
+            errors: [{
+                message: "Use object destructuring.",
+                type: "VariableDeclarator"
+            }]
+        },
         {
             code: "var foo = object[bar];",
             options: [{ object: true }, { enforceForRenamedProperties: true }],
@@ -122,21 +198,90 @@ ruleTester.run("prefer-destructuring", rule, {
             }]
         },
         {
-            code: "var foo = array['foo'];",
+            code: "var foo = object['foo'];",
             errors: [{
                 message: "Use object destructuring.",
                 type: "VariableDeclarator"
             }]
         },
         {
-            code: "foo = array.foo;",
+            code: "foo = object.foo;",
             errors: [{
                 message: "Use object destructuring.",
                 type: "AssignmentExpression"
             }]
         },
         {
-            code: "foo = array['foo'];",
+            code: "foo = object['foo'];",
+            errors: [{
+                message: "Use object destructuring.",
+                type: "AssignmentExpression"
+            }]
+        },
+        {
+            code: "var foo = array[0];",
+            options: [{ VariableDeclarator: { array: true } }, { enforceForRenamedProperties: true }],
+            errors: [{
+                message: "Use array destructuring.",
+                type: "VariableDeclarator"
+            }]
+        },
+        {
+            code: "foo = array[0];",
+            options: [{ AssignmentExpression: { array: true } }],
+            errors: [{
+                message: "Use array destructuring.",
+                type: "AssignmentExpression"
+            }]
+        },
+        {
+            code: "var foo = array[0];",
+            options: [
+                {
+                    VariableDeclarator: { array: true },
+                    AssignmentExpression: { array: false }
+                },
+                { enforceForRenamedProperties: true }
+            ],
+            errors: [{
+                message: "Use array destructuring.",
+                type: "VariableDeclarator"
+            }]
+        },
+        {
+            code: "var foo = array[0];",
+            options: [
+                {
+                    VariableDeclarator: { array: true },
+                    AssignmentExpression: { array: false }
+                }
+            ],
+            errors: [{
+                message: "Use array destructuring.",
+                type: "VariableDeclarator"
+            }]
+        },
+        {
+            code: "foo = array[0];",
+            options: [
+                {
+                    VariableDeclarator: { array: false },
+                    AssignmentExpression: { array: true }
+                }
+            ],
+            errors: [{
+                message: "Use array destructuring.",
+                type: "AssignmentExpression"
+            }]
+        },
+        {
+            code: "foo = object.foo;",
+            options: [
+                {
+                    VariableDeclarator: { array: true, object: false },
+                    AssignmentExpression: { object: true }
+                }
+            ],
             errors: [{
                 message: "Use object destructuring.",
                 type: "AssignmentExpression"
diff --git a/tests/lib/testers/rule-tester.js b/tests/lib/testers/rule-tester.js
index 86bf40cde..84e6febae 100644
--- a/tests/lib/testers/rule-tester.js
+++ b/tests/lib/testers/rule-tester.js
@@ -648,7 +648,7 @@ describe("RuleTester", () => {
                 ],
                 invalid: []
             });
-        }, /ESLint configuration is invalid./);
+        }, /ESLint configuration in rule-tester is invalid./);
     });
 
     it("throw an error when an invalid config value is included", () => {