Repository: eslint/eslint (js)

Issue:
Handle rule configuration errors more consistently
I've noticed that our error-handling strategy is very inconsistent across different error scenarios. This inconsistency is a problem because it creates unexpected edge cases in integrations (causing bugs like https://github.com/eslint/eslint.github.io/issues/413).

| **Problem**                                                      | **Current error-handling behavior** |
|--------------------------------------------------------------|---------------------------------|
| Unrecognized rule in config file                             | Linting error                   |
| Invalid rule config in config file                           | Fatal error                     |
| Invalid rule config in `Linter#verify`                       | No error                        |
| Unrecognized rule in `/* eslint */` comment                  | Linting error                   |
| Unparseable rule config in `/* eslint */` comment            | *Linting error*                   |
| Parseable, but invalid rule config in `/* eslint */` comment | *Fatal error*                     |
| Unrecognized rule in `/* eslint-disable */` comment          | No error                        |

I've italicized the cases that I find particularly surprising (namely, that linting can continue if an `/* eslint */` comment contains a parsing error, but the process crashes if the rule config is parseable and doesn't match the schema). It seems like an unparseable rule config should always be a more severe issue than an invalid rule config, because all invalid rule configs are parseable.

This is also strange because `Linter#verify` doesn't verify rule schemas at all, so it ends up that only inline config schemas are verified, not the main config being used for the file.

I think we should make invalid rule configs in `/* eslint */` comments into a linting error, rather than a fatal error. That would create a useful invariant where `Linter#verify` should never throw an error when given a valid config, regardless of the source text being linted.

Separately, I think we might want to reconsider https://github.com/eslint/eslint/issues/8325 (validating configs in `Linter#verify`). There were two objections brought up before: performance and separation of concerns.

* I don't think performance would be a problem in practice, because we could cache config objects and avoid validating them again if we see a config which is known to be valid. This would prevent `CLIEngine` from slowing down.
* `Linter` already validates rule schemas anyway for inline comments, so I don't think validating the top-level config would introduce any new issues relating to separation of concerns that aren't already present. 

Pull request:
Fix: avoid crashing on malformed configuration comments (fixes #9373)
<!--
    ESLint adheres to the [JS Foundation Code of Conduct](https://js.foundation/community/code-of-conduct).
-->

**What is the purpose of this pull request? (put an "X" next to item)**

[x] Bug fix (https://github.com/eslint/eslint/issues/9373)

<!--
    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)**

This updates `Linter` to treat malformed configuration comments as linting errors rather than fatal issues that crash the process. Previously, this could cause surprising behavior because it was the only known instance where a problem in the source code (given a valid configuration) could cause an error to be thrown.

For example, this also fixes https://github.com/eslint/eslint.github.io/issues/413.

**Is there anything you'd like reviewers to focus on?**

Nothing in particular

Patch:
diff --git a/docs/developer-guide/nodejs-api.md b/docs/developer-guide/nodejs-api.md
index b72425262..b774d3694 100644
--- a/docs/developer-guide/nodejs-api.md
+++ b/docs/developer-guide/nodejs-api.md
@@ -820,9 +820,9 @@ In addition to the properties above, invalid test cases can also have the follow
     * `message` (string/regexp): The message for the error
     * `type` (string): The type of the reported AST node
     * `line` (number): The 1-based line number of the reported location
-    * `column` (number): The 0-based column number of the reported location
+    * `column` (number): The 1-based column number of the reported location
     * `endLine` (number): The 1-based line number of the end of the reported location
-    * `endColumn` (number): The 0-based column number of the end of the reported location
+    * `endColumn` (number): The 1-based column number of the end of the reported location
 
     If a string is provided as an error instead of an object, the string is used to assert the `message` of the error.
 * `output` (string, optional): Asserts the output that will be produced when using this rule for a single pass of autofixing (e.g. with the `--fix` command line flag). If this is `null`, asserts that none of the reported problems suggest autofixes.
diff --git a/docs/developer-guide/working-with-rules.md b/docs/developer-guide/working-with-rules.md
index 8c9db815c..1d0ba82ee 100644
--- a/docs/developer-guide/working-with-rules.md
+++ b/docs/developer-guide/working-with-rules.md
@@ -2,10 +2,11 @@
 
 **Note:** This page covers the most recent rule format for ESLint >= 3.0.0. There is also a [deprecated rule format](./working-with-rules-deprecated.md).
 
-Each rule in ESLint has two files named with its identifier (for example, `no-extra-semi`).
+Each rule in ESLint has three files named with its identifier (for example, `no-extra-semi`).
 
 * in the `lib/rules` directory: a source file (for example, `no-extra-semi.js`)
 * in the `tests/lib/rules` directory: a test file (for example, `no-extra-semi.js`)
+* in the `docs/rules` directory: a Markdown documentation file (for example, `no-extra-semi.md`)
 
 **Important:** If you submit a **core** rule to the ESLint repository, you **must** follow some conventions explained below.
 
@@ -28,7 +29,8 @@ module.exports = {
         docs: {
             description: "disallow unnecessary semicolons",
             category: "Possible Errors",
-            recommended: true
+            recommended: true,
+            url: "https://eslint.org/docs/rules/no-extra-semi"
         },
         fixable: "code",
         schema: [] // no options
@@ -52,6 +54,7 @@ The source file for a rule exports an object with the following properties.
     * `description` (string) provides the short description of the rule in the [rules index](../rules/)
     * `category` (string) specifies the heading under which the rule is listed in the [rules index](../rules/)
     * `recommended` (boolean) is whether the `"extends": "eslint:recommended"` property in a [configuration file](../user-guide/configuring.md#extending-configuration-files) enables the rule
+    * `url` (string) specifies the URL at which the full documentation can be accessed
 
     In a custom rule or plugin, you can omit `docs` or include any properties that you need in it.
 
@@ -188,6 +191,7 @@ Instead of typing out messages in both the `context.report()` call and your test
 This allows you to avoid retyping error messages. It also prevents errors reported in different sections of your rule from having out-of-date messages.
 
 ```js
+{% raw %}
 // in your rule
 module.exports = {
     meta: {
@@ -236,6 +240,7 @@ ruleTester.run('my-rule', rule, {
     },
   ],
 })
+{% endraw %}
 ```
 
 ### Applying Fixes
diff --git a/docs/rules/keyword-spacing.md b/docs/rules/keyword-spacing.md
index b8d97a73e..becc9ef32 100644
--- a/docs/rules/keyword-spacing.md
+++ b/docs/rules/keyword-spacing.md
@@ -59,44 +59,44 @@ if (foo) {
     //...
 }
 
-// no conflict with `array-bracket-spacing`
+// Avoid conflict with `array-bracket-spacing`
 let a = [this];
 let b = [function() {}];
 
-// no conflict with `arrow-spacing`
+// Avoid conflict with `arrow-spacing`
 let a = ()=> this.foo;
 
-// no conflict with `block-spacing`
+// Avoid conflict with `block-spacing`
 {function foo() {}}
 
-// no conflict with `comma-spacing`
+// Avoid conflict with `comma-spacing`
 let a = [100,this.foo, this.bar];
 
-// not conflict with `computed-property-spacing`
+// Avoid conflict with `computed-property-spacing`
 obj[this.foo] = 0;
 
-// no conflict with `generator-star-spacing`
+// Avoid conflict with `generator-star-spacing`
 function *foo() {}
 
-// no conflict with `key-spacing`
+// Avoid conflict with `key-spacing`
 let obj = {
     foo:function() {}
 };
 
-// no conflict with `object-curly-spacing`
+// Avoid conflict with `object-curly-spacing`
 let obj = {foo: this};
 
-// no conflict with `semi-spacing`
+// Avoid conflict with `semi-spacing`
 let a = this;function foo() {}
 
-// no conflict with `space-in-parens`
+// Avoid conflict with `space-in-parens`
 (function () {})();
 
-// no conflict with `space-infix-ops`
+// Avoid conflict with `space-infix-ops`
 if ("foo"in {foo: 0}) {}
 if (10+this.foo<= this.bar) {}
 
-// no conflict with `jsx-curly-spacing`
+// Avoid conflict with `jsx-curly-spacing`
 let a = <A foo={this.foo} bar={function(){}} />
 ```
 
@@ -157,57 +157,57 @@ if (foo) {
     //...
 }
 
-// not conflict with `array-bracket-spacing`
+// Avoid conflict with `array-bracket-spacing`
 let a = [this];
 
-// not conflict with `arrow-spacing`
+// Avoid conflict with `arrow-spacing`
 let a = ()=> this.foo;
 
-// not conflict with `comma-spacing`
+// Avoid conflict with `comma-spacing`
 let a = [100, this.foo, this.bar];
 
-// not conflict with `computed-property-spacing`
+// Avoid conflict with `computed-property-spacing`
 obj[this.foo] = 0;
 
-// not conflict with `generator-star-spacing`
+// Avoid conflict with `generator-star-spacing`
 function* foo() {}
 
-// not conflict with `key-spacing`
+// Avoid conflict with `key-spacing`
 let obj = {
     foo:function() {}
 };
 
-// not conflict with `func-call-spacing`
+// Avoid conflict with `func-call-spacing`
 class A {
     constructor() {
         super();
     }
 }
 
-// not conflict with `object-curly-spacing`
+// Avoid conflict with `object-curly-spacing`
 let obj = {foo: this};
 
-// not conflict with `semi-spacing`
+// Avoid conflict with `semi-spacing`
 let a = this;function foo() {}
 
-// not conflict with `space-before-function-paren`
+// Avoid conflict with `space-before-function-paren`
 function() {}
 
-// no conflict with `space-infix-ops`
+// Avoid conflict with `space-infix-ops`
 if ("foo"in{foo: 0}) {}
 if (10+this.foo<= this.bar) {}
 
-// no conflict with `space-unary-ops`
+// Avoid conflict with `space-unary-ops`
 function* foo(a) {
     return yield+a;
 }
 
-// no conflict with `yield-star-spacing`
+// Avoid conflict with `yield-star-spacing`
 function* foo(a) {
     return yield* a;
 }
 
-// no conflict with `jsx-curly-spacing`
+// Avoid conflict with `jsx-curly-spacing`
 let a = <A foo={this.foo} bar={function(){}} />
 ```
 
diff --git a/docs/rules/object-property-newline.md b/docs/rules/object-property-newline.md
index 8cc73b05e..15d8ee0a0 100644
--- a/docs/rules/object-property-newline.md
+++ b/docs/rules/object-property-newline.md
@@ -74,7 +74,7 @@ Another benefit of this rule is specificity of diffs when a property is changed:
 
 ### Optional Exception
 
-The rule offers one object option, `allowMultiplePropertiesPerLine`. If you set it to `true`, object literals such as the first two above, with all property specifications on the same line, will be permitted, but one like
+The rule offers one object option, `allowAllPropertiesOnSameLine` (a deprecated synonym is `allowMultiplePropertiesPerLine`). If you set it to `true`, object literals such as the first two above, with all property specifications on the same line, will be permitted, but one like
 
 ```js
 const newObject = {
@@ -172,7 +172,7 @@ As illustrated above, the `--fix` option, applied to this rule, does not comply
 
 ## Examples
 
-Examples of **incorrect** code for this rule, with no object option or with `allowMultiplePropertiesPerLine` set to `false`:
+Examples of **incorrect** code for this rule, with no object option or with `allowAllPropertiesOnSameLine` set to `false`:
 
 ```js
 /*eslint object-property-newline: "error"*/
@@ -208,7 +208,7 @@ const obj5 = {
 ]: true};
 ```
 
-Examples of **correct** code for this rule, with no object option or with `allowMultiplePropertiesPerLine` set to `false`:
+Examples of **correct** code for this rule, with no object option or with `allowAllPropertiesOnSameLine` set to `false`:
 
 ```js
 /*eslint object-property-newline: "error"*/
@@ -238,10 +238,10 @@ const obj3 = {
 };
 ```
 
-Examples of additional **correct** code for this rule with the `{ "allowMultiplePropertiesPerLine": true }` option:
+Examples of additional **correct** code for this rule with the `{ "allowAllPropertiesOnSameLine": true }` option:
 
 ```js
-/*eslint object-property-newline: ["error", { "allowMultiplePropertiesPerLine": true }]*/
+/*eslint object-property-newline: ["error", { "allowAllPropertiesOnSameLine": true }]*/
 
 const obj = { foo: "foo", bar: "bar", baz: "baz" };
 
diff --git a/docs/user-guide/configuring.md b/docs/user-guide/configuring.md
index 8b1bcba4a..44b823aeb 100644
--- a/docs/user-guide/configuring.md
+++ b/docs/user-guide/configuring.md
@@ -26,7 +26,7 @@ For ES6 syntax, use `{ "parserOptions": { "ecmaVersion": 6 } }`; for new ES6 glo
 { "es6": true } }` (this setting enables ES6 syntax automatically).
 Parser options are set in your `.eslintrc.*` file by using the `parserOptions` property. The available options are:
 
-* `ecmaVersion` - set to 3, 5 (default), 6, 7, or 8 to specify the version of ECMAScript syntax you want to use. You can also set to 2015 (same as 6), 2016 (same as 7), or 2017 (same as 8) to use the year-based naming.
+* `ecmaVersion` - set to 3, 5 (default), 6, 7, 8, or 9 to specify the version of ECMAScript syntax you want to use. You can also set to 2015 (same as 6), 2016 (same as 7), 2017 (same as 8), or 2018 (same as 9) to use the year-based naming.
 * `sourceType` - set to `"script"` (default) or `"module"` if your code is in ECMAScript modules.
 * `ecmaFeatures` - an object indicating which additional language features you'd like to use:
     * `globalReturn` - allow `return` statements in the global scope
diff --git a/lib/config/config-validator.js b/lib/config/config-validator.js
index 3d98b5104..1a5b3ef13 100644
--- a/lib/config/config-validator.js
+++ b/lib/config/config-validator.js
@@ -98,7 +98,8 @@ function validateRuleSchema(rule, localOptions) {
  * @param {{create: Function}|null} rule The rule that the config is being validated for
  * @param {string} ruleId The rule's unique name.
  * @param {array|number} options The given options for the rule.
- * @param {string} source The name of the configuration source to report in any errors.
+ * @param {string|null} source The name of the configuration source to report in any errors. If null or undefined,
+ * no source is prepended to the message.
  * @returns {void}
  */
 function validateRuleOptions(rule, ruleId, options, source) {
@@ -112,7 +113,13 @@ function validateRuleOptions(rule, ruleId, options, source) {
             validateRuleSchema(rule, Array.isArray(options) ? options.slice(1) : []);
         }
     } catch (err) {
-        throw new Error(`${source}:\n\tConfiguration for rule "${ruleId}" is invalid:\n${err.message}`);
+        const enhancedMessage = `Configuration for rule "${ruleId}" is invalid:\n${err.message}`;
+
+        if (typeof source === "string") {
+            throw new Error(`${source}:\n\t${enhancedMessage}`);
+        } else {
+            throw new Error(enhancedMessage);
+        }
     }
 }
 
diff --git a/lib/linter.js b/lib/linter.js
index 2238c5be0..6cbb25b1c 100755
--- a/lib/linter.js
+++ b/lib/linter.js
@@ -340,7 +340,21 @@ function modifyConfigsFromComments(filename, ast, config, ruleMapper) {
                             Object.keys(parseResult.config).forEach(name => {
                                 const ruleValue = parseResult.config[name];
 
-                                validator.validateRuleOptions(ruleMapper(name), name, ruleValue, `${filename} line ${comment.loc.start.line}`);
+                                try {
+                                    validator.validateRuleOptions(ruleMapper(name), name, ruleValue);
+                                } catch (err) {
+                                    problems.push({
+                                        ruleId: name,
+                                        severity: 2,
+                                        source: null,
+                                        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
+                                    });
+                                }
                                 commentRules[name] = ruleValue;
                             });
                         } else {
diff --git a/lib/rules/indent.js b/lib/rules/indent.js
index 2159880f8..308306af2 100644
--- a/lib/rules/indent.js
+++ b/lib/rules/indent.js
@@ -1094,8 +1094,8 @@ module.exports = {
                     const questionMarkToken = sourceCode.getFirstTokenBetween(node.test, node.consequent, token => token.type === "Punctuator" && token.value === "?");
                     const colonToken = sourceCode.getFirstTokenBetween(node.consequent, node.alternate, token => token.type === "Punctuator" && token.value === ":");
 
-                    const firstConsequentToken = sourceCode.getTokenAfter(questionMarkToken, { includeComments: true });
-                    const lastConsequentToken = sourceCode.getTokenBefore(colonToken, { includeComments: true });
+                    const firstConsequentToken = sourceCode.getTokenAfter(questionMarkToken);
+                    const lastConsequentToken = sourceCode.getTokenBefore(colonToken);
                     const firstAlternateToken = sourceCode.getTokenAfter(colonToken);
 
                     offsets.setDesiredOffset(questionMarkToken, firstToken, 1);
diff --git a/lib/rules/object-property-newline.js b/lib/rules/object-property-newline.js
index 56ca26940..65baf0a95 100644
--- a/lib/rules/object-property-newline.js
+++ b/lib/rules/object-property-newline.js
@@ -22,7 +22,10 @@ module.exports = {
             {
                 type: "object",
                 properties: {
-                    allowMultiplePropertiesPerLine: {
+                    allowAllPropertiesOnSameLine: {
+                        type: "boolean"
+                    },
+                    allowMultiplePropertiesPerLine: { // Deprecated
                         type: "boolean"
                     }
                 },
@@ -34,7 +37,10 @@ module.exports = {
     },
 
     create(context) {
-        const allowSameLine = context.options[0] && Boolean(context.options[0].allowMultiplePropertiesPerLine);
+        const allowSameLine = context.options[0] && (
+            Boolean(context.options[0].allowAllPropertiesOnSameLine) ||
+            Boolean(context.options[0].allowMultiplePropertiesPerLine) // Deprecated
+        );
         const errorMessage = allowSameLine
             ? "Object properties must go on a new line if they aren't all on the same line."
             : "Object properties must go on a new line.";
diff --git a/lib/rules/prefer-destructuring.js b/lib/rules/prefer-destructuring.js
index e9d02da3a..112ea6461 100644
--- a/lib/rules/prefer-destructuring.js
+++ b/lib/rules/prefer-destructuring.js
@@ -165,8 +165,10 @@ module.exports = {
             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 && !rightNode.computed)
+                ) {
                     report(reportNode, "object");
                 }
             }
diff --git a/packages/eslint-config-eslint/default.yml b/packages/eslint-config-eslint/default.yml
index f901651e6..f8f20d9e2 100644
--- a/packages/eslint-config-eslint/default.yml
+++ b/packages/eslint-config-eslint/default.yml
@@ -129,7 +129,7 @@ rules:
     no-var: "error"
     object-curly-newline: ["error", { "consistent": true, "multiline": true }]
     object-curly-spacing: ["error", "always"]
-    object-property-newline: ["error", { "allowMultiplePropertiesPerLine": true }]
+    object-property-newline: ["error", { "allowAllPropertiesOnSameLine": true }]
     object-shorthand: "error"
     one-var-declaration-per-line: "error"
     operator-assignment: "error"


Test patch:
diff --git a/tests/lib/linter.js b/tests/lib/linter.js
index 481a0846f..fc4298d56 100644
--- a/tests/lib/linter.js
+++ b/tests/lib/linter.js
@@ -1615,14 +1615,42 @@ describe("Linter", () => {
     });
 
     describe("when evaluating code with invalid comments to enable rules", () => {
-        const code = "/*eslint no-alert:true*/ alert('test');";
-
-        it("should report a violation", () => {
-            const config = { rules: {} };
-
-            const fn = linter.verify.bind(linter, code, config, filename);
+        it("should report a violation when the config is not a valid rule configuration", () => {
+            assert.deepStrictEqual(
+                linter.verify("/*eslint no-alert:true*/ alert('test');", {}),
+                [
+                    {
+                        severity: 2,
+                        ruleId: "no-alert",
+                        message: "Configuration for rule \"no-alert\" is invalid:\n\tSeverity should be one of the following: 0 = off, 1 = warn, 2 = error (you passed 'true').\n",
+                        line: 1,
+                        column: 1,
+                        endLine: 1,
+                        endColumn: 25,
+                        source: null,
+                        nodeType: null
+                    }
+                ]
+            );
+        });
 
-            assert.throws(fn, "filename.js line 1:\n\tConfiguration for rule \"no-alert\" is invalid:\n\tSeverity should be one of the following: 0 = off, 1 = warn, 2 = error (you passed 'true').\n");
+        it("should report a violation when the config violates a rule's schema", () => {
+            assert.deepStrictEqual(
+                linter.verify("/* eslint no-alert: [error, {nonExistentPropertyName: true}]*/", {}),
+                [
+                    {
+                        severity: 2,
+                        ruleId: "no-alert",
+                        message: "Configuration for rule \"no-alert\" is invalid:\n\tValue [{\"nonExistentPropertyName\":true}] should NOT have more than 0 items.\n",
+                        line: 1,
+                        column: 1,
+                        endLine: 1,
+                        endColumn: 63,
+                        source: null,
+                        nodeType: null
+                    }
+                ]
+            );
         });
     });
 
diff --git a/tests/lib/rules/indent.js b/tests/lib/rules/indent.js
index 4ec4975bc..21f720d6b 100644
--- a/tests/lib/rules/indent.js
+++ b/tests/lib/rules/indent.js
@@ -4796,7 +4796,16 @@ ruleTester.run("indent", rule, {
                 }
             `,
             options: [4, { ignoreComments: true }]
-        }
+        },
+        unIndent`
+            const obj = {
+                foo () {
+                    return condition ? // comment
+                        1 :
+                        2
+                }
+            }
+        `
     ],
 
     invalid: [
@@ -9261,6 +9270,27 @@ ruleTester.run("indent", rule, {
             `,
             options: [4, { ignoreComments: false }],
             errors: expectedErrors([4, 4, 0, "Block"])
+        },
+        {
+            code: unIndent`
+                const obj = {
+                    foo () {
+                        return condition ? // comment
+                        1 :
+                            2
+                    }
+                }
+            `,
+            output: unIndent`
+                const obj = {
+                    foo () {
+                        return condition ? // comment
+                            1 :
+                            2
+                    }
+                }
+            `,
+            errors: expectedErrors([4, 12, 8, "Numeric"])
         }
     ]
 });
diff --git a/tests/lib/rules/object-property-newline.js b/tests/lib/rules/object-property-newline.js
index 2d0df640f..ed0e24db2 100644
--- a/tests/lib/rules/object-property-newline.js
+++ b/tests/lib/rules/object-property-newline.js
@@ -44,24 +44,27 @@ ruleTester.run("object-property-newline", rule, {
         { code: "foo({ k1: 'val1',\nk2: 'val2',\n...{} });", parserOptions: { ecmaVersion: 6, ecmaFeatures: { experimentalObjectRestSpread: true } } },
         { code: "foo({ ...{} });", parserOptions: { ecmaVersion: 6, ecmaFeatures: { experimentalObjectRestSpread: true } } },
 
-        // allowMultiplePropertiesPerLine: true
-        { code: "var obj = { k1: 'val1', k2: 'val2', k3: 'val3' };", options: [{ allowMultiplePropertiesPerLine: true }] },
-        { code: "var obj = {\nk1: 'val1', k2: 'val2', k3: 'val3'\n};", options: [{ allowMultiplePropertiesPerLine: true }] },
-        { code: "var obj = { k1: 'val1' };", options: [{ allowMultiplePropertiesPerLine: true }] },
-        { code: "var obj = {\nk1: 'val1'\n};", options: [{ allowMultiplePropertiesPerLine: true }] },
-        { code: "var obj = {};", options: [{ allowMultiplePropertiesPerLine: true }] },
-        { code: "var obj = { 'k1': 'val1', k2: 'val2', ...{} };", options: [{ allowMultiplePropertiesPerLine: true }], parserOptions: { ecmaVersion: 6, ecmaFeatures: { experimentalObjectRestSpread: true } } },
-        { code: "var obj = {\n'k1': 'val1', k2: 'val2', ...{}\n};", options: [{ allowMultiplePropertiesPerLine: true }], parserOptions: { ecmaVersion: 6, ecmaFeatures: { experimentalObjectRestSpread: true } } },
-        { code: "foo({ k1: 'val1', k2: 'val2' });", options: [{ allowMultiplePropertiesPerLine: true }] },
-        { code: "foo({\nk1: 'val1', k2: 'val2'\n});", options: [{ allowMultiplePropertiesPerLine: true }] },
-        { code: "foo({ a, b });", options: [{ allowMultiplePropertiesPerLine: true }], parserOptions: { ecmaVersion: 6 } },
-        { code: "foo({ bar() {}, baz });", options: [{ allowMultiplePropertiesPerLine: true }], parserOptions: { ecmaVersion: 6 } },
-        { code: "foo({ [bar]: 'baz', baz })", options: [{ allowMultiplePropertiesPerLine: true }], parserOptions: { ecmaVersion: 6 } },
-        { code: "foo({ 'k1': 'val1', k2: 'val2', ...{} });", options: [{ allowMultiplePropertiesPerLine: true }], parserOptions: { ecmaVersion: 6, ecmaFeatures: { experimentalObjectRestSpread: true } } },
-        { code: "foo({\n'k1': 'val1', k2: 'val2', ...{}\n});", options: [{ allowMultiplePropertiesPerLine: true }], parserOptions: { ecmaVersion: 6, ecmaFeatures: { experimentalObjectRestSpread: true } } },
-        { code: "var obj = {k1: ['foo', 'bar'], k2: 'val1', k3: 'val2'};", options: [{ allowMultiplePropertiesPerLine: true }] },
-        { code: "var obj = {\nk1: ['foo', 'bar'], k2: 'val1', k3: 'val2'\n};", options: [{ allowMultiplePropertiesPerLine: true }] },
-        { code: "var obj = {\nk1: 'val1', k2: {e1: 'foo', e2: 'bar'}, k3: 'val2'\n};", options: [{ allowMultiplePropertiesPerLine: true }] }
+        // allowAllPropertiesOnSameLine: true
+        { code: "var obj = { k1: 'val1', k2: 'val2', k3: 'val3' };", options: [{ allowAllPropertiesOnSameLine: true }] },
+        { code: "var obj = {\nk1: 'val1', k2: 'val2', k3: 'val3'\n};", options: [{ allowAllPropertiesOnSameLine: true }] },
+        { code: "var obj = { k1: 'val1' };", options: [{ allowAllPropertiesOnSameLine: true }] },
+        { code: "var obj = {\nk1: 'val1'\n};", options: [{ allowAllPropertiesOnSameLine: true }] },
+        { code: "var obj = {};", options: [{ allowAllPropertiesOnSameLine: true }] },
+        { code: "var obj = { 'k1': 'val1', k2: 'val2', ...{} };", options: [{ allowAllPropertiesOnSameLine: true }], parserOptions: { ecmaVersion: 6, ecmaFeatures: { experimentalObjectRestSpread: true } } },
+        { code: "var obj = {\n'k1': 'val1', k2: 'val2', ...{}\n};", options: [{ allowAllPropertiesOnSameLine: true }], parserOptions: { ecmaVersion: 6, ecmaFeatures: { experimentalObjectRestSpread: true } } },
+        { code: "foo({ k1: 'val1', k2: 'val2' });", options: [{ allowAllPropertiesOnSameLine: true }] },
+        { code: "foo({\nk1: 'val1', k2: 'val2'\n});", options: [{ allowAllPropertiesOnSameLine: true }] },
+        { code: "foo({ a, b });", options: [{ allowAllPropertiesOnSameLine: true }], parserOptions: { ecmaVersion: 6 } },
+        { code: "foo({ bar() {}, baz });", options: [{ allowAllPropertiesOnSameLine: true }], parserOptions: { ecmaVersion: 6 } },
+        { code: "foo({ [bar]: 'baz', baz })", options: [{ allowAllPropertiesOnSameLine: true }], parserOptions: { ecmaVersion: 6 } },
+        { code: "foo({ 'k1': 'val1', k2: 'val2', ...{} });", options: [{ allowAllPropertiesOnSameLine: true }], parserOptions: { ecmaVersion: 6, ecmaFeatures: { experimentalObjectRestSpread: true } } },
+        { code: "foo({\n'k1': 'val1', k2: 'val2', ...{}\n});", options: [{ allowAllPropertiesOnSameLine: true }], parserOptions: { ecmaVersion: 6, ecmaFeatures: { experimentalObjectRestSpread: true } } },
+        { code: "var obj = {k1: ['foo', 'bar'], k2: 'val1', k3: 'val2'};", options: [{ allowAllPropertiesOnSameLine: true }] },
+        { code: "var obj = {\nk1: ['foo', 'bar'], k2: 'val1', k3: 'val2'\n};", options: [{ allowAllPropertiesOnSameLine: true }] },
+        { code: "var obj = {\nk1: 'val1', k2: {e1: 'foo', e2: 'bar'}, k3: 'val2'\n};", options: [{ allowAllPropertiesOnSameLine: true }] },
+
+        // allowMultiplePropertiesPerLine: true (deprecated)
+        { code: "var obj = { k1: 'val1', k2: 'val2', k3: 'val3' };", options: [{ allowMultiplePropertiesPerLine: true }] }
     ],
 
     invalid: [
@@ -378,11 +381,11 @@ ruleTester.run("object-property-newline", rule, {
             ]
         },
 
-        // allowMultiplePropertiesPerLine: true
+        // allowAllPropertiesOnSameLine: true
         {
             code: "var obj = {\nk1: 'val1',\nk2: 'val2', k3: 'val3'\n};",
             output: "var obj = {\nk1: 'val1',\nk2: 'val2',\nk3: 'val3'\n};",
-            options: [{ allowMultiplePropertiesPerLine: true }],
+            options: [{ allowAllPropertiesOnSameLine: true }],
             errors: [
                 {
                     message: "Object properties must go on a new line if they aren't all on the same line.",
@@ -395,7 +398,7 @@ ruleTester.run("object-property-newline", rule, {
         {
             code: "var obj = {\nk1:\n'val1', k2: 'val2', k3:\n'val3'\n};",
             output: "var obj = {\nk1:\n'val1',\nk2: 'val2',\nk3:\n'val3'\n};",
-            options: [{ allowMultiplePropertiesPerLine: true }],
+            options: [{ allowAllPropertiesOnSameLine: true }],
             errors: [
                 {
                     message: "Object properties must go on a new line if they aren't all on the same line.",
@@ -414,7 +417,7 @@ ruleTester.run("object-property-newline", rule, {
         {
             code: "var obj = {k1: [\n'foo',\n'bar'\n], k2: 'val1'};",
             output: "var obj = {k1: [\n'foo',\n'bar'\n],\nk2: 'val1'};",
-            options: [{ allowMultiplePropertiesPerLine: true }],
+            options: [{ allowAllPropertiesOnSameLine: true }],
             errors: [
                 {
                     message: "Object properties must go on a new line if they aren't all on the same line.",
@@ -427,7 +430,7 @@ ruleTester.run("object-property-newline", rule, {
         {
             code: "var obj = {k1: [\n'foo', 'bar'\n], k2: 'val1'};",
             output: "var obj = {k1: [\n'foo', 'bar'\n],\nk2: 'val1'};",
-            options: [{ allowMultiplePropertiesPerLine: true }],
+            options: [{ allowAllPropertiesOnSameLine: true }],
             errors: [
                 {
                     message: "Object properties must go on a new line if they aren't all on the same line.",
@@ -440,7 +443,7 @@ ruleTester.run("object-property-newline", rule, {
         {
             code: "var obj = {\nk1: 'val1', k2: {\ne1: 'foo', e2: 'bar'\n}, k3: 'val2'\n};",
             output: "var obj = {\nk1: 'val1',\nk2: {\ne1: 'foo', e2: 'bar'\n},\nk3: 'val2'\n};",
-            options: [{ allowMultiplePropertiesPerLine: true }],
+            options: [{ allowAllPropertiesOnSameLine: true }],
             errors: [
                 {
                     message: "Object properties must go on a new line if they aren't all on the same line.",
@@ -459,7 +462,7 @@ ruleTester.run("object-property-newline", rule, {
         {
             code: "var obj = { k1: 'val1',\nk2: [\n'val2a', 'val2b', 'val2c'\n], k3: 'val3' };",
             output: "var obj = { k1: 'val1',\nk2: [\n'val2a', 'val2b', 'val2c'\n],\nk3: 'val3' };",
-            options: [{ allowMultiplePropertiesPerLine: true }],
+            options: [{ allowAllPropertiesOnSameLine: true }],
             errors: [
                 {
                     message: "Object properties must go on a new line if they aren't all on the same line.",
@@ -472,7 +475,7 @@ ruleTester.run("object-property-newline", rule, {
         {
             code: "var obj = { [\nk1]: 'val1', k2: 'val2' };",
             output: "var obj = { [\nk1]: 'val1',\nk2: 'val2' };",
-            options: [{ allowMultiplePropertiesPerLine: true }],
+            options: [{ allowAllPropertiesOnSameLine: true }],
             parserOptions: { ecmaVersion: 6 },
             errors: [
                 {
@@ -486,7 +489,7 @@ ruleTester.run("object-property-newline", rule, {
         {
             code: "var obj = {\nk1: 'val1',\nk2: 'val2', ...{}\n};",
             output: "var obj = {\nk1: 'val1',\nk2: 'val2',\n...{}\n};",
-            options: [{ allowMultiplePropertiesPerLine: true }],
+            options: [{ allowAllPropertiesOnSameLine: true }],
             parserOptions: { ecmaVersion: 6, ecmaFeatures: { experimentalObjectRestSpread: true } },
             errors: [
                 {
@@ -500,7 +503,7 @@ ruleTester.run("object-property-newline", rule, {
         {
             code: "var obj = {\n...{},\nk1: 'val1', k2: 'val2'\n};",
             output: "var obj = {\n...{},\nk1: 'val1',\nk2: 'val2'\n};",
-            options: [{ allowMultiplePropertiesPerLine: true }],
+            options: [{ allowAllPropertiesOnSameLine: true }],
             parserOptions: { ecmaVersion: 6, ecmaFeatures: { experimentalObjectRestSpread: true } },
             errors: [
                 {
@@ -514,7 +517,7 @@ ruleTester.run("object-property-newline", rule, {
         {
             code: "foo({ [\nk1]: 'val1', k2: 'val2' })",
             output: "foo({ [\nk1]: 'val1',\nk2: 'val2' })",
-            options: [{ allowMultiplePropertiesPerLine: true }],
+            options: [{ allowAllPropertiesOnSameLine: true }],
             parserOptions: { ecmaVersion: 6 },
             errors: [
                 {
@@ -528,7 +531,7 @@ ruleTester.run("object-property-newline", rule, {
         {
             code: "foo({\nk1: 'val1',\nk2: 'val2', ...{}\n})",
             output: "foo({\nk1: 'val1',\nk2: 'val2',\n...{}\n})",
-            options: [{ allowMultiplePropertiesPerLine: true }],
+            options: [{ allowAllPropertiesOnSameLine: true }],
             parserOptions: { ecmaVersion: 6, ecmaFeatures: { experimentalObjectRestSpread: true } },
             errors: [
                 {
@@ -542,7 +545,7 @@ ruleTester.run("object-property-newline", rule, {
         {
             code: "foo({\n...{},\nk1: 'val1', k2: 'val2'\n})",
             output: "foo({\n...{},\nk1: 'val1',\nk2: 'val2'\n})",
-            options: [{ allowMultiplePropertiesPerLine: true }],
+            options: [{ allowAllPropertiesOnSameLine: true }],
             parserOptions: { ecmaVersion: 6, ecmaFeatures: { experimentalObjectRestSpread: true } },
             errors: [
                 {
@@ -552,6 +555,21 @@ ruleTester.run("object-property-newline", rule, {
                     column: 13
                 }
             ]
+        },
+
+        // allowMultiplePropertiesPerLine: true (deprecated)
+        {
+            code: "var obj = {\nk1: 'val1',\nk2: 'val2', k3: 'val3'\n};",
+            output: "var obj = {\nk1: 'val1',\nk2: 'val2',\nk3: 'val3'\n};",
+            options: [{ allowMultiplePropertiesPerLine: true }],
+            errors: [
+                {
+                    message: "Object properties must go on a new line if they aren't all on the same line.",
+                    type: "ObjectExpression",
+                    line: 3,
+                    column: 13
+                }
+            ]
         }
     ]
 });
diff --git a/tests/lib/rules/prefer-destructuring.js b/tests/lib/rules/prefer-destructuring.js
index e60535e7e..a0e4aa79b 100644
--- a/tests/lib/rules/prefer-destructuring.js
+++ b/tests/lib/rules/prefer-destructuring.js
@@ -131,7 +131,9 @@ ruleTester.run("prefer-destructuring", rule, {
             code: "var foo = object.foo;",
             options: [{ VariableDeclarator: { object: false }, AssignmentExpression: { object: true } }]
         },
-        "class Foo extends Bar { static foo() {var foo = super.foo} }"
+        "class Foo extends Bar { static foo() {var foo = super.foo} }",
+        "foo = bar[foo];",
+        "var foo = bar[foo];"
     ],
 
     invalid: [