Repository: eslint/eslint
Pull request #8796 (2017-06-24 21:55:31)

Update: support more options in prefer-destructuring
[X ] Changes an existing rule ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/rule-change-proposal.md))


**What rule do you want to change?**
prefer-destructuring.js
This pr is in reference to the issue #7886 

**Does this change cause the rule to produce more or fewer warnings?**
less warnings since you have more flexibility to control destructuring errors.

**How will the change be implemented? (New option, new default behavior, etc.)?**
The first parameter of destructuring has been updated to also take a VariableDeclarator value or an AssignmentExpression value. Each value takes an object that takes an object of optional keys array and/or object.

The old parameters of  {object: Boolean, array: Boolean} is still supported

**Please provide some example code that this change will affect:**

```json
{
  "rules": {
    "prefer-destructuring": ["error", {
      "VariableDeclaration": {
        "array": false,
        "object": true
      },
     "AssignmentExpression": {
        "array": true,
        "object": true
      }
    }, {
      "enforceForRenamedProperties": false
    }]
  }
}
```

**What does the rule currently do for this code?**
In the above example, prefer-destructuring will be enforced for variable declarations from objects but not from arrays. Prefer-destructuring will be enforced for assignment expressions for both arrays and objects

**What will the rule do after it's changed?**
It will let users have more flexibility and control over when prefer-destructuring should be enforced.



eslintbot: LGTM
eslintbot: LGTM
eslintbot: LGTM
eslintbot: LGTM
eslintbot: LGTM
eslintbot: LGTM
eslintbot: LGTM
eslintbot: LGTM
eslintbot: LGTM
VictorHom: @not-an-aardvark just want to bring this pr back up for review when you have some time
eslintbot: LGTM
eslintbot: LGTM

Patch:
diff --git a/bin/eslint.js b/bin/eslint.js
index 2b5d4e7fe..7c05ad3b6 100755
--- a/bin/eslint.js
+++ b/bin/eslint.js
@@ -44,11 +44,10 @@ process.once("uncaughtException", err => {
     if (typeof err.messageTemplate === "string" && err.messageTemplate.length > 0) {
         const template = lodash.template(fs.readFileSync(path.resolve(__dirname, `../messages/${err.messageTemplate}.txt`), "utf-8"));
 
-        console.log("\nOops! Something went wrong! :(");
-        console.log(`\n${template(err.messageData || {})}`);
+        console.error("\nOops! Something went wrong! :(");
+        console.error(`\n${template(err.messageData || {})}`);
     } else {
-        console.log(err.message);
-        console.log(err.stack);
+        console.error(err.stack);
     }
 
     process.exitCode = 1;
diff --git a/docs/developer-guide/shareable-configs.md b/docs/developer-guide/shareable-configs.md
index 23d6a3fef..58f36e904 100644
--- a/docs/developer-guide/shareable-configs.md
+++ b/docs/developer-guide/shareable-configs.md
@@ -33,7 +33,7 @@ Once your shareable config is ready, you can [publish to npm](https://docs.npmjs
 You should declare your dependency on eslint in `package.json` using the [peerDependencies](https://docs.npmjs.com/files/package.json#peerdependencies) field. The recommended way to declare a dependency for future proof compatibility is with the ">=" range syntax, using the lowest required eslint version. For example:
 
 ```
-peerDependencies: {
+"peerDependencies": {
     "eslint": ">= 3"
 }
 ```
diff --git a/docs/maintainer-guide/pullrequests.md b/docs/maintainer-guide/pullrequests.md
index 3b5618389..9130dd527 100644
--- a/docs/maintainer-guide/pullrequests.md
+++ b/docs/maintainer-guide/pullrequests.md
@@ -19,6 +19,7 @@ The bot will add a comment specifying the problems that it finds. You do not nee
 Once the bot checks have been satisfied, you check the following:
 
 1. Double-check that the commit message tag ("Fix:", "New:", etc.) is correct based on the issue (or, if no issue is referenced, based on the stated problem).
+1. If the pull request makes a change to core, ensure that an issue exists and the pull request references the issue in the commit message.
 1. Does the code follow our conventions (including header comments, JSDoc comments, etc.)? If not, please leave that feedback and reference the conventions document.
 1. For code changes:
     * Are there tests that verify the change? If not, please ask for them.
diff --git a/docs/rules/comma-dangle.md b/docs/rules/comma-dangle.md
index 9eb3dedd3..275fd0363 100644
--- a/docs/rules/comma-dangle.md
+++ b/docs/rules/comma-dangle.md
@@ -48,7 +48,7 @@ This rule has a string option or an object option:
         "objects": "never",
         "imports": "never",
         "exports": "never",
-        "functions": "ignore",
+        "functions": "ignore"
     }]
 }
 ```
diff --git a/docs/rules/no-restricted-globals.md b/docs/rules/no-restricted-globals.md
index 751f5d66d..c9df9d339 100644
--- a/docs/rules/no-restricted-globals.md
+++ b/docs/rules/no-restricted-globals.md
@@ -13,7 +13,35 @@ This rule allows you to specify global variable names that you don't want to use
 
 ## Options
 
-This rule takes a list of strings which are the global variable names.
+This rule takes a list of strings, where each string is a global to be restricted:
+
+```json
+{
+    "rules": {
+        "no-restricted-globals": ["error", "event", "fdescribe"]
+    }
+}
+```
+
+Alternatively, the rule also accepts objects, where the global name and an optional custom message are specified:
+
+```json
+{
+    "rules": {
+        "no-restricted-globals": [
+            "error",
+            {
+                "name": "event",
+                "message": "Use local parameter instead."
+            },
+            {
+                "name": "fdescribe",
+                "message": "Do not commit fdescribe. Use describe instead."
+            }
+        ]
+    }
+}
+```
 
 Examples of **incorrect** code for sample `"event", "fdescribe"` global variable names:
 
@@ -45,6 +73,17 @@ import event from "event-module";
 var event = 1;
 ```
 
+Examples of **incorrect** code for a sample `"event"` global variable name, along with a custom error message:
+
+```js
+/*global event*/
+/* eslint no-restricted-globals: [{ name: "error", message: "Use local parameter instead." }] */
+
+function onClick() {
+    console.log(event);    // Unexpected global variable 'event'. Use local parameter instead.
+}
+```
+
 ## Related Rules
 
 * [no-restricted-properties](no-restricted-properties.md)
diff --git a/docs/rules/no-underscore-dangle.md b/docs/rules/no-underscore-dangle.md
index d09c6d6ad..e51a020db 100644
--- a/docs/rules/no-underscore-dangle.md
+++ b/docs/rules/no-underscore-dangle.md
@@ -42,6 +42,7 @@ This rule has an object option:
 * `"allow"` allows specified identifiers to have dangling underscores
 * `"allowAfterThis": false` (default) disallows dangling underscores in members of the `this` object
 * `"allowAfterSuper": false` (default) disallows dangling underscores in members of the `super` object
+* `"enforceInMethodNames": false (default) allows dangling underscores in method names`
 
 ### allow
 
@@ -76,6 +77,30 @@ var a = super.foo_;
 super._bar();
 ```
 
+### enforceInMethodNames
+
+Examples of incorrect code for this rule with the `{ "enforceInMethodNames": true }` option:
+
+```js
+/*eslint no-underscore-dangle: ["error", { "enforceInMethodNames": true }]*/
+
+class Foo {
+  _bar() {}
+}
+
+class Foo {
+  bar_() {}
+}
+
+const o = {
+  _bar() {}
+};
+
+const o = {
+  bar_() = {}
+};
+```
+
 ## When Not To Use It
 
 If you want to allow dangling underscores in identifiers, then you can safely turn this rule off.
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/docs/rules/prefer-numeric-literals.md b/docs/rules/prefer-numeric-literals.md
index f4c70b6d3..58902e471 100644
--- a/docs/rules/prefer-numeric-literals.md
+++ b/docs/rules/prefer-numeric-literals.md
@@ -1,6 +1,6 @@
-# disallow `parseInt()` in favor of binary, octal, and hexadecimal literals (prefer-numeric-literals)
+# disallow `parseInt()` and `Number.parseInt()` in favor of binary, octal, and hexadecimal literals (prefer-numeric-literals)
 
-The `parseInt()` function can be used to turn binary, octal, and hexadecimal strings into integers. As binary, octal, and hexadecimal literals are supported in ES6, this rule encourages use of those numeric literals instead of `parseInt()`.
+The `parseInt()` and `Number.parseInt()` functions can be used to turn binary, octal, and hexadecimal strings into integers. As binary, octal, and hexadecimal literals are supported in ES6, this rule encourages use of those numeric literals instead of `parseInt()` or `Number.parseInt()`.
 
 ```js
 0b111110111 === 503;
@@ -9,7 +9,7 @@ The `parseInt()` function can be used to turn binary, octal, and hexadecimal str
 
 ## Rule Details
 
-This rule disallows `parseInt()` if it is called with two arguments: a string and a radix option of 2 (binary), 8 (octal), or 16 (hexadecimal).
+This rule disallows calls to `parseInt()` or `Number.parseInt()` if called with two arguments: a string; and a radix option of 2 (binary), 8 (octal), or 16 (hexadecimal).
 
 Examples of **incorrect** code for this rule:
 
@@ -19,6 +19,9 @@ Examples of **incorrect** code for this rule:
 parseInt("111110111", 2) === 503;
 parseInt("767", 8) === 503;
 parseInt("1F7", 16) === 255;
+Number.parseInt("111110111", 2) === 503;
+Number.parseInt("767", 8) === 503;
+Number.parseInt("1F7", 16) === 255;
 ```
 
 Examples of **correct** code for this rule:
@@ -29,6 +32,8 @@ Examples of **correct** code for this rule:
 
 parseInt(1);
 parseInt(1, 3);
+Number.parseInt(1);
+Number.parseInt(1, 3);
 
 0b111110111 === 503;
 0o767 === 503;
@@ -38,11 +43,13 @@ a[parseInt](1,2);
 
 parseInt(foo);
 parseInt(foo, 2);
+Number.parseInt(foo);
+Number.parseInt(foo, 2);
 ```
 
 ## When Not To Use It
 
-If you want to allow use of `parseInt()` for binary, octal, or hexadecimal integers. If you are not using ES6 (because binary and octal literals are not supported in ES5 and below).
+If you want to allow use of `parseInt()` or `Number.parseInt()` for binary, octal, or hexadecimal integers, or if you are not using ES6 (because binary and octal literals are not supported in ES5 and below), you may wish to disable this rule.
 
 ## Compatibility
 
diff --git a/lib/config/config-initializer.js b/lib/config/config-initializer.js
index e87cb79b6..d344fa0ac 100644
--- a/lib/config/config-initializer.js
+++ b/lib/config/config-initializer.js
@@ -12,10 +12,12 @@
 const util = require("util"),
     inquirer = require("inquirer"),
     ProgressBar = require("progress"),
+    semver = require("semver"),
     autoconfig = require("./autoconfig.js"),
     ConfigFile = require("./config-file"),
     ConfigOps = require("./config-ops"),
     getSourceCodeOfFiles = require("../util/source-code-util").getSourceCodeOfFiles,
+    ModuleResolver = require("../util/module-resolver"),
     npmUtil = require("../util/npm-util"),
     recConfig = require("../../conf/eslint-recommended"),
     log = require("../logging");
@@ -56,12 +58,35 @@ function writeFile(config, format) {
     }
 }
 
+/**
+ * Get the peer dependencies of the given module.
+ * This adds the gotten value to cache at the first time, then reuses it.
+ * In a process, this function is called twice, but `npmUtil.fetchPeerDependencies` needs to access network which is relatively slow.
+ * @param {string} moduleName The module name to get.
+ * @returns {Object} The peer dependencies of the given module.
+ * This object is the object of `peerDependencies` field of `package.json`.
+ */
+function getPeerDependencies(moduleName) {
+    let result = getPeerDependencies.cache.get(moduleName);
+
+    if (!result) {
+        log.info(`Checking peerDependencies of ${moduleName}`);
+
+        result = npmUtil.fetchPeerDependencies(moduleName);
+        getPeerDependencies.cache.set(moduleName, result);
+    }
+
+    return result;
+}
+getPeerDependencies.cache = new Map();
+
 /**
  * Synchronously install necessary plugins, configs, parsers, etc. based on the config
  * @param   {Object} config  config object
+ * @param   {boolean} [installESLint=true]  If `false` is given, it does not install eslint.
  * @returns {void}
  */
-function installModules(config) {
+function installModules(config, installESLint) {
     const modules = {};
 
     // Create a list of modules which should be installed based on config
@@ -73,11 +98,10 @@ function installModules(config) {
     if (config.extends && config.extends.indexOf("eslint:") === -1) {
         const moduleName = `eslint-config-${config.extends}`;
 
-        log.info(`Checking peerDependencies of ${moduleName}`);
         modules[moduleName] = "latest";
         Object.assign(
             modules,
-            npmUtil.fetchPeerDependencies(`${moduleName}@latest`)
+            getPeerDependencies(`${moduleName}@latest`)
         );
     }
 
@@ -86,15 +110,17 @@ function installModules(config) {
         return;
     }
 
-    // Add eslint to list in case user does not have it installed locally
-    modules.eslint = modules.eslint || "latest";
-
-    // Mark to show messages if it's new installation of eslint.
-    const installStatus = npmUtil.checkDevDeps(["eslint"]);
+    if (installESLint === false) {
+        delete modules.eslint;
+    } else {
+        const installStatus = npmUtil.checkDevDeps(["eslint"]);
 
-    if (installStatus.eslint === false) {
-        log.info("Local ESLint installation not found.");
-        config.installedESLint = true;
+        // Mark to show messages if it's new installation of eslint.
+        if (installStatus.eslint === false) {
+            log.info("Local ESLint installation not found.");
+            modules.eslint = modules.eslint || "latest";
+            config.installedESLint = true;
+        }
     }
 
     // Install packages
@@ -265,9 +291,10 @@ function processAnswers(answers) {
 /**
  * process user's style guide of choice and return an appropriate config object.
  * @param {string} guide name of the chosen style guide
+ * @param {boolean} [installESLint=true]  If `false` is given, it does not install eslint.
  * @returns {Object} config object
  */
-function getConfigForStyleGuide(guide) {
+function getConfigForStyleGuide(guide, installESLint) {
     const guides = {
         google: { extends: "google" },
         airbnb: { extends: "airbnb" },
@@ -279,11 +306,74 @@ function getConfigForStyleGuide(guide) {
         throw new Error("You referenced an unsupported guide.");
     }
 
-    installModules(guides[guide]);
+    installModules(guides[guide], installESLint);
 
     return guides[guide];
 }
 
+/**
+ * Get the version of the local ESLint.
+ * @returns {string|null} The version. If the local ESLint was not found, returns null.
+ */
+function getLocalESLintVersion() {
+    try {
+        const resolver = new ModuleResolver();
+        const eslintPath = resolver.resolve("eslint", process.cwd());
+        const eslint = require(eslintPath);
+
+        return eslint.linter.version || null;
+    } catch (_err) {
+        return null;
+    }
+}
+
+/**
+ * Get the shareable config name of the chosen style guide.
+ * @param {Object} answers The answers object.
+ * @returns {string} The shareable config name.
+ */
+function getStyleGuideName(answers) {
+    if (answers.styleguide === "airbnb" && !answers.airbnbReact) {
+        return "airbnb-base";
+    }
+    return answers.styleguide;
+}
+
+/**
+ * Check whether the local ESLint version conflicts with the required version of the chosen shareable config.
+ * @param {Object} answers The answers object.
+ * @returns {boolean} `true` if the local ESLint is found then it conflicts with the required version of the chosen shareable config.
+ */
+function hasESLintVersionConflict(answers) {
+
+    // Get the local ESLint version.
+    const localESLintVersion = getLocalESLintVersion();
+
+    if (!localESLintVersion) {
+        return false;
+    }
+
+    // Get the required range of ESLint version.
+    const configName = getStyleGuideName(answers);
+    const moduleName = `eslint-config-${configName}@latest`;
+    const requiredESLintVersionRange = getPeerDependencies(moduleName).eslint;
+
+    if (!requiredESLintVersionRange) {
+        return false;
+    }
+
+    answers.localESLintVersion = localESLintVersion;
+    answers.requiredESLintVersionRange = requiredESLintVersionRange;
+
+    // Check the version.
+    if (semver.satisfies(localESLintVersion, requiredESLintVersionRange)) {
+        answers.installESLint = false;
+        return false;
+    }
+
+    return true;
+}
+
 /* istanbul ignore next: no need to test inquirer*/
 /**
  * Ask use a few questions on command prompt
@@ -346,6 +436,21 @@ function promptUser() {
             when(answers) {
                 return ((answers.source === "guide" && answers.packageJsonExists) || answers.source === "auto");
             }
+        },
+        {
+            type: "confirm",
+            name: "installESLint",
+            message(answers) {
+                const verb = semver.ltr(answers.localESLintVersion, answers.requiredESLintVersionRange)
+                    ? "upgrade"
+                    : "downgrade";
+
+                return `The style guide "${answers.styleguide}" requires eslint@${answers.requiredESLintVersionRange}. You are currently using eslint@${answers.localESLintVersion}.\n  Do you want to ${verb}?`;
+            },
+            default: true,
+            when(answers) {
+                return answers.source === "guide" && answers.packageJsonExists && hasESLintVersionConflict(answers);
+            }
         }
     ]).then(earlyAnswers => {
 
@@ -355,11 +460,14 @@ function promptUser() {
                 log.info("A package.json is necessary to install plugins such as style guides. Run `npm init` to create a package.json file and try again.");
                 return void 0;
             }
+            if (earlyAnswers.installESLint === false && !semver.satisfies(earlyAnswers.localESLintVersion, earlyAnswers.requiredESLintVersionRange)) {
+                log.info(`Note: it might not work since ESLint's version is mismatched with the ${earlyAnswers.styleguide} config.`);
+            }
             if (earlyAnswers.styleguide === "airbnb" && !earlyAnswers.airbnbReact) {
                 earlyAnswers.styleguide = "airbnb-base";
             }
 
-            config = getConfigForStyleGuide(earlyAnswers.styleguide);
+            config = getConfigForStyleGuide(earlyAnswers.styleguide, earlyAnswers.installESLint);
             writeFile(config, earlyAnswers.format);
 
             return void 0;
@@ -479,6 +587,7 @@ function promptUser() {
 
 const init = {
     getConfigForStyleGuide,
+    hasESLintVersionConflict,
     processAnswers,
     /* istanbul ignore next */initializeConfig() {
         return promptUser();
diff --git a/lib/rules/indent.js b/lib/rules/indent.js
index ce21e6d54..ec3f79170 100644
--- a/lib/rules/indent.js
+++ b/lib/rules/indent.js
@@ -252,20 +252,10 @@ class OffsetStorage {
         return this._tree.findLe(token.range[0]).value;
     }
 
-    /**
-    * Sets the indent of one token to match the indent of another.
-    * @param {Token} baseToken The first token
-    * @param {Token} offsetToken The second token, whose indent should be matched to the first token
-    * @returns {void}
-    */
-    matchIndentOf(baseToken, offsetToken) {
-        return this.setDesiredOffsets(offsetToken.range, baseToken, 0);
-    }
-
     /**
      * Sets the offset column of token B to match the offset column of token A.
-     * **WARNING**: This is different from matchIndentOf because it matches a *column*, even if baseToken is not
-     * the first token on its line. In most cases, `matchIndentOf` should be used instead.
+     * **WARNING**: This matches a *column*, even if baseToken is not the first token on its line. In
+     * most cases, `setDesiredOffset` should be used instead.
      * @param {Token} baseToken The first token
      * @param {Token} offsetToken The second token, whose offset should be matched to the first token
      * @returns {void}
@@ -376,6 +366,7 @@ class OffsetStorage {
          * descriptor. The tree for the example above would have the following nodes:
          *
          * * key: 0, value: { offset: 0, from: null }
+         * * key: 15, value: { offset: 1, from: barToken }
          * * key: 30, value: { offset: 1, from: fooToken }
          * * key: 43, value: { offset: 2, from: barToken }
          * * key: 820, value: { offset: 1, from: bazToken }
@@ -625,8 +616,6 @@ module.exports = {
             MemberExpression: 1,
             ArrayExpression: 1,
             ObjectExpression: 1,
-            ArrayPattern: 1,
-            ObjectPattern: 1,
             flatTernaryExpressions: false
         };
 
@@ -634,7 +623,7 @@ module.exports = {
             if (context.options[0] === "tab") {
                 indentSize = 1;
                 indentType = "tab";
-            } else if (typeof context.options[0] === "number") {
+            } else {
                 indentSize = context.options[0];
                 indentType = "space";
             }
@@ -686,10 +675,10 @@ module.exports = {
 
         /**
          * Reports a given indent violation
-         * @param {Token} token Node violating the indent rule
+         * @param {Token} token Token violating the indent rule
          * @param {int} neededIndentLevel Expected indentation level
-         * @param {int} gottenSpaces Indentation space count in the actual node/code
-         * @param {int} gottenTabs Indentation tab count in the actual node/code
+         * @param {int} gottenSpaces Actual number of indentation spaces for the token
+         * @param {int} gottenTabs Actual number of indentation tabs for the token
          * @returns {void}
          */
         function report(token, neededIndentLevel) {
@@ -794,7 +783,7 @@ module.exports = {
                 startToken,
                 offset === "first" ? 1 : offset
             );
-            offsets.matchIndentOf(startToken, endToken);
+            offsets.setDesiredOffset(endToken, startToken, 0);
 
             // If the preference is "first" but there is no first element (e.g. sparse arrays w/ empty first slot), fall back to 1 level.
             if (offset === "first" && elements.length && !elements[0]) {
@@ -820,44 +809,6 @@ module.exports = {
             });
         }
 
-        /**
-         * Check indentation for blocks and class bodies
-         * @param {ASTNode} node The BlockStatement or ClassBody node to indent
-         * @returns {void}
-         */
-        function addBlockIndent(node) {
-
-            let blockIndentLevel;
-
-            if (node.parent && isOuterIIFE(node.parent)) {
-                blockIndentLevel = options.outerIIFEBody;
-            } else if (node.parent && (node.parent.type === "FunctionExpression" || node.parent.type === "ArrowFunctionExpression")) {
-                blockIndentLevel = options.FunctionExpression.body;
-            } else if (node.parent && node.parent.type === "FunctionDeclaration") {
-                blockIndentLevel = options.FunctionDeclaration.body;
-            } else {
-                blockIndentLevel = 1;
-            }
-
-            /*
-             * For blocks that aren't lone statements, ensure that the opening curly brace
-             * is aligned with the parent.
-             */
-            if (!astUtils.STATEMENT_LIST_PARENTS.has(node.parent.type)) {
-                offsets.matchIndentOf(sourceCode.getFirstToken(node.parent), sourceCode.getFirstToken(node));
-            }
-            addElementListIndent(node.body, sourceCode.getFirstToken(node), sourceCode.getLastToken(node), blockIndentLevel);
-        }
-
-        /**
-         * Check indent for array block content or object block content
-         * @param {ASTNode} node node to examine
-         * @returns {void}
-         */
-        function addArrayOrObjectIndent(node) {
-            addElementListIndent(node.elements || node.properties, sourceCode.getFirstToken(node), sourceCode.getLastToken(node), options[node.type]);
-        }
-
         /**
          * Check and decide whether to check for indentation for blockless nodes
          * Scenarios are for or while statements without braces around them
@@ -890,7 +841,7 @@ module.exports = {
                 const lastToken = sourceCode.getLastToken(node);
 
                 if (node.type !== "EmptyStatement" && astUtils.isSemicolonToken(lastToken)) {
-                    offsets.matchIndentOf(lastParentToken, lastToken);
+                    offsets.setDesiredOffset(lastToken, lastParentToken, 0);
                 }
             }
         }
@@ -912,25 +863,11 @@ module.exports = {
 
             parameterParens.add(openingParen);
             parameterParens.add(closingParen);
-            offsets.matchIndentOf(sourceCode.getTokenBefore(openingParen), openingParen);
+            offsets.setDesiredOffset(openingParen, sourceCode.getTokenBefore(openingParen), 0);
 
             addElementListIndent(node.arguments, openingParen, closingParen, options.CallExpression.arguments);
         }
 
-        /**
-        * Checks the indentation of ClassDeclarations and ClassExpressions
-        * @param {ASTNode} node A ClassDeclaration or ClassExpression node
-        * @returns {void}
-        */
-        function addClassIndent(node) {
-            if (node.superClass) {
-                const classToken = sourceCode.getFirstToken(node);
-                const extendsToken = sourceCode.getTokenBefore(node.superClass, astUtils.isNotOpeningParenToken);
-
-                offsets.setDesiredOffsets([extendsToken.range[0], node.body.range[0]], classToken, 1);
-            }
-        }
-
         /**
         * Checks the indentation of parenthesized values, given a list of tokens in a program
         * @param {Token[]} tokens A list of tokens
@@ -965,7 +902,7 @@ module.exports = {
                     });
                 }
 
-                offsets.matchIndentOf(leftParen, rightParen);
+                offsets.setDesiredOffset(rightParen, leftParen, 0);
             });
         }
 
@@ -985,7 +922,7 @@ module.exports = {
                     if (token === firstTokenOfLine) {
                         offsets.ignoreToken(token);
                     } else {
-                        offsets.matchIndentOf(firstTokenOfLine, token);
+                        offsets.setDesiredOffset(token, firstTokenOfLine, 0);
                     }
                 }
             });
@@ -1022,8 +959,13 @@ module.exports = {
         }
 
         return {
-            ArrayExpression: addArrayOrObjectIndent,
-            ArrayPattern: addArrayOrObjectIndent,
+            "ArrayExpression, ArrayPattern"(node) {
+                addElementListIndent(node.elements, sourceCode.getFirstToken(node), sourceCode.getLastToken(node), options.ArrayExpression);
+            },
+
+            "ObjectExpression, ObjectPattern"(node) {
+                addElementListIndent(node.properties, sourceCode.getFirstToken(node), sourceCode.getLastToken(node), options.ObjectExpression);
+            },
 
             ArrowFunctionExpression(node) {
                 const firstToken = sourceCode.getFirstToken(node);
@@ -1045,7 +987,7 @@ module.exports = {
                 } else {
                     arrowToken = sourceCode.getFirstToken(node, astUtils.isArrowToken);
                 }
-                offsets.matchIndentOf(sourceCode.getFirstToken(node), arrowToken);
+                offsets.setDesiredOffset(arrowToken, sourceCode.getFirstToken(node), 0);
             },
 
             AssignmentExpression(node) {
@@ -1073,15 +1015,39 @@ module.exports = {
                 offsets.setDesiredOffsets([tokenAfterOperator.range[1], node.range[1]], tokenAfterOperator, 1);
             },
 
-            BlockStatement: addBlockIndent,
+            "BlockStatement, ClassBody"(node) {
+
+                let blockIndentLevel;
+
+                if (node.parent && isOuterIIFE(node.parent)) {
+                    blockIndentLevel = options.outerIIFEBody;
+                } else if (node.parent && (node.parent.type === "FunctionExpression" || node.parent.type === "ArrowFunctionExpression")) {
+                    blockIndentLevel = options.FunctionExpression.body;
+                } else if (node.parent && node.parent.type === "FunctionDeclaration") {
+                    blockIndentLevel = options.FunctionDeclaration.body;
+                } else {
+                    blockIndentLevel = 1;
+                }
+
+                /*
+                 * For blocks that aren't lone statements, ensure that the opening curly brace
+                 * is aligned with the parent.
+                 */
+                if (!astUtils.STATEMENT_LIST_PARENTS.has(node.parent.type)) {
+                    offsets.setDesiredOffset(sourceCode.getFirstToken(node), sourceCode.getFirstToken(node.parent), 0);
+                }
+                addElementListIndent(node.body, sourceCode.getFirstToken(node), sourceCode.getLastToken(node), blockIndentLevel);
+            },
 
             CallExpression: addFunctionCallIndent,
 
-            ClassBody: addBlockIndent,
 
-            ClassDeclaration: addClassIndent,
+            "ClassDeclaration[superClass], ClassExpression[superClass]"(node) {
+                const classToken = sourceCode.getFirstToken(node);
+                const extendsToken = sourceCode.getTokenBefore(node.superClass, astUtils.isNotOpeningParenToken);
 
-            ClassExpression: addClassIndent,
+                offsets.setDesiredOffsets([extendsToken.range[0], node.body.range[0]], classToken, 1);
+            },
 
             ConditionalExpression(node) {
                 const firstToken = sourceCode.getFirstToken(node);
@@ -1118,7 +1084,7 @@ module.exports = {
                      * )
                      */
                     if (lastConsequentToken.loc.end.line === firstAlternateToken.loc.start.line) {
-                        offsets.matchIndentOf(firstConsequentToken, firstAlternateToken);
+                        offsets.setDesiredOffset(firstAlternateToken, firstConsequentToken, 0);
                     } else {
 
                         /**
@@ -1138,7 +1104,7 @@ module.exports = {
                 }
             },
 
-            DoWhileStatement: node => addBlocklessNodeIndent(node.body),
+            "DoWhileStatement, WhileStatement, ForInStatement, ForOfStatement": node => addBlocklessNodeIndent(node.body),
 
             ExportNamedDeclaration(node) {
                 if (node.declaration === null) {
@@ -1155,10 +1121,6 @@ module.exports = {
                 }
             },
 
-            ForInStatement: node => addBlocklessNodeIndent(node.body),
-
-            ForOfStatement: node => addBlocklessNodeIndent(node.body),
-
             ForStatement(node) {
                 const forOpeningParen = sourceCode.getFirstToken(node, 1);
 
@@ -1209,15 +1171,17 @@ module.exports = {
                 const firstNonObjectToken = sourceCode.getFirstTokenBetween(node.object, node.property, astUtils.isNotClosingParenToken);
                 const secondNonObjectToken = sourceCode.getTokenAfter(firstNonObjectToken);
 
-                const tokenBeforeObject = sourceCode.getTokenBefore(node.object, token => astUtils.isNotOpeningParenToken(token) || parameterParens.has(token));
-                const firstObjectToken = tokenBeforeObject ? sourceCode.getTokenAfter(tokenBeforeObject) : sourceCode.ast.tokens[0];
+                const objectParenCount = sourceCode.getTokensBetween(node.object, node.property, { filter: astUtils.isClosingParenToken }).length;
+                const firstObjectToken = objectParenCount
+                    ? sourceCode.getTokenBefore(node.object, { skip: objectParenCount - 1 })
+                    : sourceCode.getFirstToken(node.object);
                 const lastObjectToken = sourceCode.getTokenBefore(firstNonObjectToken);
                 const firstPropertyToken = node.computed ? firstNonObjectToken : secondNonObjectToken;
 
                 if (node.computed) {
 
                     // For computed MemberExpressions, match the closing bracket with the opening bracket.
-                    offsets.matchIndentOf(firstNonObjectToken, sourceCode.getLastToken(node));
+                    offsets.setDesiredOffset(sourceCode.getLastToken(node), firstNonObjectToken, 0);
                     offsets.setDesiredOffsets(node.property.range, firstNonObjectToken, 1);
                 }
 
@@ -1251,8 +1215,8 @@ module.exports = {
                     offsets.ignoreToken(secondNonObjectToken);
 
                     // To ignore the property indentation, ensure that the property tokens depend on the ignored tokens.
-                    offsets.matchIndentOf(offsetBase, firstNonObjectToken);
-                    offsets.matchIndentOf(firstNonObjectToken, secondNonObjectToken);
+                    offsets.setDesiredOffset(firstNonObjectToken, offsetBase, 0);
+                    offsets.setDesiredOffset(secondNonObjectToken, firstNonObjectToken, 0);
                 }
             },
 
@@ -1264,9 +1228,6 @@ module.exports = {
                 }
             },
 
-            ObjectExpression: addArrayOrObjectIndent,
-            ObjectPattern: addArrayOrObjectIndent,
-
             Property(node) {
                 if (!node.computed && !node.shorthand && !node.method && node.kind === "init") {
                     const colon = sourceCode.getFirstTokenBetween(node.key, node.value, astUtils.isColonToken);
@@ -1357,12 +1318,10 @@ module.exports = {
                     offsets.ignoreToken(equalOperator);
                     offsets.ignoreToken(tokenAfterOperator);
                     offsets.setDesiredOffsets([tokenAfterOperator.range[0], node.range[1]], equalOperator, 1);
-                    offsets.matchIndentOf(sourceCode.getLastToken(node.id), equalOperator);
+                    offsets.setDesiredOffset(equalOperator, sourceCode.getLastToken(node.id), 0);
                 }
             },
 
-            WhileStatement: node => addBlocklessNodeIndent(node.body),
-
             "*:exit": checkForUnknownNode,
 
             "JSXAttribute[value]"(node) {
@@ -1383,7 +1342,7 @@ module.exports = {
 
                 if (node.selfClosing) {
                     closingToken = sourceCode.getLastToken(node, { skip: 1 });
-                    offsets.matchIndentOf(closingToken, sourceCode.getLastToken(node));
+                    offsets.setDesiredOffset(sourceCode.getLastToken(node), closingToken, 0);
                 } else {
                     closingToken = sourceCode.getLastToken(node);
                 }
@@ -1395,7 +1354,7 @@ module.exports = {
                 const firstToken = sourceCode.getFirstToken(node);
 
                 offsets.setDesiredOffsets(node.name.range, firstToken, 1);
-                offsets.matchIndentOf(firstToken, sourceCode.getLastToken(node));
+                offsets.setDesiredOffset(sourceCode.getLastToken(node), firstToken, 0);
             },
 
             JSXExpressionContainer(node) {
@@ -1407,7 +1366,7 @@ module.exports = {
                     openingCurly,
                     1
                 );
-                offsets.matchIndentOf(openingCurly, closingCurly);
+                offsets.setDesiredOffset(closingCurly, openingCurly, 0);
             },
 
             "Program:exit"() {
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/no-restricted-globals.js b/lib/rules/no-restricted-globals.js
index 603a6b2d3..75428fc17 100644
--- a/lib/rules/no-restricted-globals.js
+++ b/lib/rules/no-restricted-globals.js
@@ -4,6 +4,13 @@
  */
 "use strict";
 
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const DEFAULT_MESSAGE_TEMPLATE = "Unexpected use of '{{name}}'.",
+    CUSTOM_MESSAGE_TEMPLATE = "Unexpected use of '{{name}}'. {{customMessage}}";
+
 //------------------------------------------------------------------------------
 // Rule Definition
 //------------------------------------------------------------------------------
@@ -19,20 +26,43 @@ module.exports = {
         schema: {
             type: "array",
             items: {
-                type: "string"
+                oneOf: [
+                    {
+                        type: "string"
+                    },
+                    {
+                        type: "object",
+                        properties: {
+                            name: { type: "string" },
+                            message: { type: "string" }
+                        },
+                        required: ["name"],
+                        additionalProperties: false
+                    }
+                ]
             },
-            uniqueItems: true
+            uniqueItems: true,
+            minItems: 0
         }
     },
 
     create(context) {
-        const restrictedGlobals = context.options;
 
-        // if no globals are restricted we don't need to check
-        if (restrictedGlobals.length === 0) {
+        // If no globals are restricted, we don't need to do anything
+        if (context.options.length === 0) {
             return {};
         }
 
+        const restrictedGlobalMessages = context.options.reduce((memo, option) => {
+            if (typeof option === "string") {
+                memo[option] = null;
+            } else {
+                memo[option.name] = option.message;
+            }
+
+            return memo;
+        }, {});
+
         /**
          * Report a variable to be used as a restricted global.
          * @param {Reference} reference the variable reference
@@ -40,9 +70,20 @@ module.exports = {
          * @private
          */
         function reportReference(reference) {
-            context.report({ node: reference.identifier, message: "Unexpected use of '{{name}}'.", data: {
-                name: reference.identifier.name
-            } });
+            const name = reference.identifier.name,
+                customMessage = restrictedGlobalMessages[name],
+                message = customMessage
+                    ? CUSTOM_MESSAGE_TEMPLATE
+                    : DEFAULT_MESSAGE_TEMPLATE;
+
+            context.report({
+                node: reference.identifier,
+                message,
+                data: {
+                    name,
+                    customMessage
+                }
+            });
         }
 
         /**
@@ -52,7 +93,7 @@ module.exports = {
          * @private
          */
         function isRestricted(name) {
-            return restrictedGlobals.indexOf(name) >= 0;
+            return restrictedGlobalMessages.hasOwnProperty(name);
         }
 
         return {
diff --git a/lib/rules/no-underscore-dangle.js b/lib/rules/no-underscore-dangle.js
index 6803cc68f..5964da41c 100644
--- a/lib/rules/no-underscore-dangle.js
+++ b/lib/rules/no-underscore-dangle.js
@@ -32,6 +32,9 @@ module.exports = {
                     },
                     allowAfterSuper: {
                         type: "boolean"
+                    },
+                    enforceInMethodNames: {
+                        type: "boolean"
                     }
                 },
                 additionalProperties: false
@@ -45,6 +48,7 @@ module.exports = {
         const ALLOWED_VARIABLES = options.allow ? options.allow : [];
         const allowAfterThis = typeof options.allowAfterThis !== "undefined" ? options.allowAfterThis : false;
         const allowAfterSuper = typeof options.allowAfterSuper !== "undefined" ? options.allowAfterSuper : false;
+        const enforceInMethodNames = typeof options.enforceInMethodNames !== "undefined" ? options.enforceInMethodNames : false;
 
         //-------------------------------------------------------------------------
         // Helpers
@@ -162,6 +166,27 @@ module.exports = {
             }
         }
 
+        /**
+         * Check if method declaration or method property has a underscore at the end
+         * @param {ASTNode} node node to evaluate
+         * @returns {void}
+         * @private
+         */
+        function checkForTrailingUnderscoreInMethod(node) {
+            const identifier = node.key.name;
+            const isMethod = node.type === "MethodDefinition" || node.type === "Property" && node.method;
+
+            if (typeof identifier !== "undefined" && enforceInMethodNames && isMethod && hasTrailingUnderscore(identifier)) {
+                context.report({
+                    node,
+                    message: "Unexpected dangling '_' in '{{identifier}}'.",
+                    data: {
+                        identifier
+                    }
+                });
+            }
+        }
+
         //--------------------------------------------------------------------------
         // Public API
         //--------------------------------------------------------------------------
@@ -169,7 +194,9 @@ module.exports = {
         return {
             FunctionDeclaration: checkForTrailingUnderscoreInFunctionDeclaration,
             VariableDeclarator: checkForTrailingUnderscoreInVariableExpression,
-            MemberExpression: checkForTrailingUnderscoreInMemberExpression
+            MemberExpression: checkForTrailingUnderscoreInMemberExpression,
+            MethodDefinition: checkForTrailingUnderscoreInMethod,
+            Property: checkForTrailingUnderscoreInMethod
         };
 
     }
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/lib/rules/prefer-numeric-literals.js b/lib/rules/prefer-numeric-literals.js
index ed84ce6a9..08deedd62 100644
--- a/lib/rules/prefer-numeric-literals.js
+++ b/lib/rules/prefer-numeric-literals.js
@@ -5,6 +5,33 @@
 
 "use strict";
 
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Checks to see if a CallExpression's callee node is `parseInt` or
+ * `Number.parseInt`.
+ * @param {ASTNode} calleeNode The callee node to evaluate.
+ * @returns {boolean} True if the callee is `parseInt` or `Number.parseInt`,
+ * false otherwise.
+ */
+function isParseInt(calleeNode) {
+    switch (calleeNode.type) {
+        case "Identifier":
+            return calleeNode.name === "parseInt";
+        case "MemberExpression":
+            return calleeNode.object.type === "Identifier" &&
+                calleeNode.object.name === "Number" &&
+                calleeNode.property.type === "Identifier" &&
+                calleeNode.property.name === "parseInt";
+
+        // no default
+    }
+
+    return false;
+}
+
 //------------------------------------------------------------------------------
 // Rule Definition
 //------------------------------------------------------------------------------
@@ -12,7 +39,7 @@
 module.exports = {
     meta: {
         docs: {
-            description: "disallow `parseInt()` in favor of binary, octal, and hexadecimal literals",
+            description: "disallow `parseInt()` and `Number.parseInt()` in favor of binary, octal, and hexadecimal literals",
             category: "ECMAScript 6",
             recommended: false
         },
@@ -23,6 +50,8 @@ module.exports = {
     },
 
     create(context) {
+        const sourceCode = context.getSourceCode();
+
         const radixMap = {
             2: "binary",
             8: "octal",
@@ -35,9 +64,9 @@ module.exports = {
             16: "0x"
         };
 
-        //--------------------------------------------------------------------------
+        //----------------------------------------------------------------------
         // Public
-        //--------------------------------------------------------------------------
+        //----------------------------------------------------------------------
 
         return {
 
@@ -51,16 +80,16 @@ module.exports = {
                 // only error if the radix is 2, 8, or 16
                 const radixName = radixMap[node.arguments[1].value];
 
-                if (node.callee.type === "Identifier" &&
-                    node.callee.name === "parseInt" &&
+                if (isParseInt(node.callee) &&
                     radixName &&
                     node.arguments[0].type === "Literal"
                 ) {
                     context.report({
                         node,
-                        message: "Use {{radixName}} literals instead of parseInt().",
+                        message: "Use {{radixName}} literals instead of {{functionName}}().",
                         data: {
-                            radixName
+                            radixName,
+                            functionName: sourceCode.getText(node.callee)
                         },
                         fix(fixer) {
                             const newPrefix = prefixMap[node.arguments[1].value];
diff --git a/lib/util/npm-util.js b/lib/util/npm-util.js
index 057dbfea4..4f488c012 100644
--- a/lib/util/npm-util.js
+++ b/lib/util/npm-util.js
@@ -10,7 +10,7 @@
 //------------------------------------------------------------------------------
 
 const fs = require("fs"),
-    childProcess = require("child_process"),
+    spawn = require("cross-spawn"),
     path = require("path"),
     log = require("../logging");
 
@@ -50,22 +50,23 @@ function findPackageJson(startDir) {
  * @returns {void}
  */
 function installSyncSaveDev(packages) {
-    if (Array.isArray(packages)) {
-        packages = packages.join(" ");
+    if (!Array.isArray(packages)) {
+        packages = [packages];
     }
-    childProcess.execSync(`npm i --save-dev ${packages}`, { stdio: "inherit", encoding: "utf8" });
+    spawn.sync("npm", ["i", "--save-dev"].concat(packages), { stdio: "inherit" });
 }
 
 /**
  * Fetch `peerDependencies` of the given package by `npm show` command.
  * @param {string} packageName The package name to fetch peerDependencies.
- * @returns {string[]} Gotten peerDependencies.
+ * @returns {Object} Gotten peerDependencies.
  */
 function fetchPeerDependencies(packageName) {
-    const fetchedText = childProcess.execSync(
-        `npm show --json ${packageName} peerDependencies`,
+    const fetchedText = spawn.sync(
+        "npm",
+        ["show", "--json", packageName, "peerDependencies"],
         { encoding: "utf8" }
-    ).trim();
+    ).stdout.trim();
 
     return JSON.parse(fetchedText || "{}");
 }
diff --git a/package.json b/package.json
index 53fb78c32..ca6e3cd91 100644
--- a/package.json
+++ b/package.json
@@ -39,6 +39,7 @@
     "babel-code-frame": "^6.22.0",
     "chalk": "^1.1.3",
     "concat-stream": "^1.6.0",
+    "cross-spawn": "^5.1.0",
     "debug": "^2.6.8",
     "doctrine": "^2.0.0",
     "eslint-scope": "^3.7.1",
@@ -66,6 +67,7 @@
     "pluralize": "^4.0.0",
     "progress": "^2.0.0",
     "require-uncached": "^1.0.3",
+    "semver": "^5.3.0",
     "strip-json-comments": "~2.0.1",
     "table": "^4.0.1",
     "text-table": "~0.2.0"
@@ -103,7 +105,6 @@
     "npm-license": "^0.3.3",
     "phantomjs-prebuilt": "^2.1.14",
     "proxyquire": "^1.8.0",
-    "semver": "^5.3.0",
     "shelljs": "^0.7.7",
     "shelljs-nodecli": "~0.1.1",
     "sinon": "^2.3.2",
diff --git a/packages/eslint-config-eslint/README.md b/packages/eslint-config-eslint/README.md
index 636e09360..f16805cd3 100644
--- a/packages/eslint-config-eslint/README.md
+++ b/packages/eslint-config-eslint/README.md
@@ -31,5 +31,5 @@ In your `.eslintrc` file, add:
 
 Join our [Mailing List](https://groups.google.com/group/eslint) or [Chatroom](https://gitter.im/eslint/eslint)
 
-[npm-image]: https://img.shields.io/npm/v/eslint.svg?style=flat-square
+[npm-image]: https://img.shields.io/npm/v/eslint-config-eslint.svg?style=flat-square
 [npm-url]: https://www.npmjs.com/package/eslint-config-eslint


Test patch:
diff --git a/tests/bin/eslint.js b/tests/bin/eslint.js
index 7c5cd6552..0774500f0 100644
--- a/tests/bin/eslint.js
+++ b/tests/bin/eslint.js
@@ -34,13 +34,16 @@ function assertExitCode(exitingProcess, expectedExitCode) {
 /**
 * Returns a Promise for the stdout of a process.
 * @param {ChildProcess} runningProcess The child process
-* @returns {Promise<string>} A Promise that fulfills with all of the stdout output produced by the process when it exits.
+* @returns {Promise<{stdout: string, stderr: string}>} A Promise that fulfills with all of the
+* stdout and stderr output produced by the process when it exits.
 */
-function getStdout(runningProcess) {
+function getOutput(runningProcess) {
     let stdout = "";
+    let stderr = "";
 
     runningProcess.stdout.on("data", data => (stdout += data));
-    return awaitExit(runningProcess).then(() => stdout);
+    runningProcess.stderr.on("data", data => (stderr += data));
+    return awaitExit(runningProcess).then(() => ({ stdout, stderr }));
 }
 
 describe("bin/eslint.js", () => {
@@ -88,8 +91,8 @@ describe("bin/eslint.js", () => {
             const child = runESLint(["--stdin"], { cwd: "/" }); // Assumes the root directory has no .eslintrc file
 
             const exitCodePromise = assertExitCode(child, 1);
-            const stdoutPromise = getStdout(child).then(stdout => {
-                assert.match(stdout, /ESLint couldn't find a configuration file/);
+            const stdoutPromise = getOutput(child).then(output => {
+                assert.match(output.stderr, /ESLint couldn't find a configuration file/);
             });
 
             child.stdin.write("var foo = bar\n");
@@ -131,7 +134,7 @@ describe("bin/eslint.js", () => {
         it("has exit code 0, fixes errors in a file, and does not report or fix warnings if --quiet and --fix are used", () => {
             const child = runESLint(["--fix", "--quiet", "--no-eslintrc", "--no-ignore", tempFilePath]);
             const exitCodeAssertion = assertExitCode(child, 0);
-            const stdoutAssertion = getStdout(child).then(stdout => assert.strictEqual(stdout, ""));
+            const stdoutAssertion = getOutput(child).then(output => assert.strictEqual(output.stdout, ""));
             const outputFileAssertion = awaitExit(child).then(() => {
                 assert.strictEqual(fs.readFileSync(tempFilePath).toString(), expectedFixedTextQuiet);
             });
@@ -259,6 +262,24 @@ describe("bin/eslint.js", () => {
         });
     });
 
+    describe("handling crashes", () => {
+        it("prints the error message exactly once to stderr in the event of a crash", () => {
+            const child = runESLint(["--rule=no-restricted-syntax:[error, 'Invalid Selector [[[']", "Makefile.js"]);
+            const exitCodeAssertion = assertExitCode(child, 1);
+            const outputAssertion = getOutput(child).then(output => {
+                const expectedSubstring = "Syntax error in selector";
+
+                assert.strictEqual(output.stdout, "");
+                assert.include(output.stderr, expectedSubstring);
+
+                // The message should appear exactly once in stderr
+                assert.strictEqual(output.stderr.indexOf(expectedSubstring), output.stderr.lastIndexOf(expectedSubstring));
+            });
+
+            return Promise.all([exitCodeAssertion, outputAssertion]);
+        });
+    });
+
     afterEach(() => {
 
         // Clean up all the processes after every test.
diff --git a/tests/lib/config/config-initializer.js b/tests/lib/config/config-initializer.js
index 1a7d0a906..9cb44ae1e 100644
--- a/tests/lib/config/config-initializer.js
+++ b/tests/lib/config/config-initializer.js
@@ -33,14 +33,30 @@ describe("configInitializer", () => {
         npmCheckStub,
         npmInstallStub,
         npmFetchPeerDependenciesStub,
-        init;
+        init,
+        localESLintVersion = null;
 
     const log = {
         info: sinon.spy(),
         error: sinon.spy()
     };
     const requireStubs = {
-        "../logging": log
+        "../logging": log,
+        "../util/module-resolver": class ModuleResolver {
+
+            /**
+             * @returns {string} The path to local eslint to test.
+             */
+            resolve() { // eslint-disable-line class-methods-use-this
+                if (localESLintVersion) {
+                    return `local-eslint-${localESLintVersion}`;
+                }
+                throw new Error("Cannot find module");
+            }
+        },
+        "local-eslint-3.18.0": { linter: { version: "3.18.0" }, "@noCallThru": true },
+        "local-eslint-3.19.0": { linter: { version: "3.19.0" }, "@noCallThru": true },
+        "local-eslint-4.0.0": { linter: { version: "4.0.0" }, "@noCallThru": true }
     };
 
     /**
@@ -245,6 +261,56 @@ describe("configInitializer", () => {
                     ]
                 );
             });
+
+            describe("hasESLintVersionConflict (Note: peerDependencies always `eslint: \"^3.19.0\"` by stubs)", () => {
+                describe("if local ESLint is not found,", () => {
+                    before(() => {
+                        localESLintVersion = null;
+                    });
+
+                    it("should return false.", () => {
+                        const result = init.hasESLintVersionConflict({ styleguide: "airbnb" });
+
+                        assert.equal(result, false);
+                    });
+                });
+
+                describe("if local ESLint is 3.19.0,", () => {
+                    before(() => {
+                        localESLintVersion = "3.19.0";
+                    });
+
+                    it("should return false.", () => {
+                        const result = init.hasESLintVersionConflict({ styleguide: "airbnb" });
+
+                        assert.equal(result, false);
+                    });
+                });
+
+                describe("if local ESLint is 4.0.0,", () => {
+                    before(() => {
+                        localESLintVersion = "4.0.0";
+                    });
+
+                    it("should return true.", () => {
+                        const result = init.hasESLintVersionConflict({ styleguide: "airbnb" });
+
+                        assert.equal(result, true);
+                    });
+                });
+
+                describe("if local ESLint is 3.18.0,", () => {
+                    before(() => {
+                        localESLintVersion = "3.18.0";
+                    });
+
+                    it("should return true.", () => {
+                        const result = init.hasESLintVersionConflict({ styleguide: "airbnb" });
+
+                        assert.equal(result, true);
+                    });
+                });
+            });
         });
 
         describe("auto", () => {
diff --git a/tests/lib/rules/array-bracket-newline.js b/tests/lib/rules/array-bracket-newline.js
index fd4afcc46..a29db6164 100644
--- a/tests/lib/rules/array-bracket-newline.js
+++ b/tests/lib/rules/array-bracket-newline.js
@@ -505,7 +505,7 @@ ruleTester.run("array-bracket-newline", rule, {
         {
             code: "var foo = [\n// any comment\n];",
             options: [{ multiline: true }],
-            output: "var foo = [\n// any comment\n];",
+            output: null,
             errors: [
                 {
                     message: ERR_NO_BREAK_AFTER,
diff --git a/tests/lib/rules/array-element-newline.js b/tests/lib/rules/array-element-newline.js
index 1bfbfccd2..e6ca6236c 100644
--- a/tests/lib/rules/array-element-newline.js
+++ b/tests/lib/rules/array-element-newline.js
@@ -368,7 +368,7 @@ ruleTester.run("array-element-newline", rule, {
         {
             code: "var foo = [\n1 // any comment\n, 2\n];",
             options: ["never"],
-            output: "var foo = [\n1 // any comment\n, 2\n];",
+            output: null,
             errors: [
                 {
                     message: ERR_NO_BREAK_HERE,
@@ -380,7 +380,7 @@ ruleTester.run("array-element-newline", rule, {
         {
             code: "var foo = [\n1, // any comment\n2\n];",
             options: ["never"],
-            output: "var foo = [\n1, // any comment\n2\n];",
+            output: null,
             errors: [
                 {
                     message: ERR_NO_BREAK_HERE,
@@ -437,7 +437,7 @@ ruleTester.run("array-element-newline", rule, {
         {
             code: "var foo = [\nfunction foo() {\ndosomething();\n}, /* any comment */\nfunction bar() {\ndosomething();\n}\n];",
             options: ["never"],
-            output: "var foo = [\nfunction foo() {\ndosomething();\n}, /* any comment */\nfunction bar() {\ndosomething();\n}\n];",
+            output: null,
             errors: [
                 {
                     message: ERR_NO_BREAK_HERE,
diff --git a/tests/lib/rules/indent.js b/tests/lib/rules/indent.js
index 6f816f03d..fb621cb49 100644
--- a/tests/lib/rules/indent.js
+++ b/tests/lib/rules/indent.js
@@ -1822,6 +1822,72 @@ ruleTester.run("indent", rule, {
             `,
             options: [2, { MemberExpression: 2 }]
         },
+        {
+            code: unIndent`
+                (
+                    foo
+                        .bar
+                )
+            `
+        },
+        {
+            code: unIndent`
+                (
+                    (
+                        foo
+                            .bar
+                    )
+                )
+            `
+        },
+        {
+            code: unIndent`
+                (
+                    foo
+                )
+                    .bar
+            `
+        },
+        {
+            code: unIndent`
+                (
+                    (
+                        foo
+                    )
+                        .bar
+                )
+            `
+        },
+        {
+            code: unIndent`
+                (
+                    (
+                        foo
+                    )
+                        [
+                            (
+                                bar
+                            )
+                        ]
+                )
+            `
+        },
+        {
+            code: unIndent`
+                (
+                    foo[bar]
+                )
+                    .baz
+            `
+        },
+        {
+            code: unIndent`
+                (
+                    (foo.bar)
+                )
+                    .baz
+            `
+        },
         {
             code: unIndent`
                 MemberExpression
@@ -4469,15 +4535,17 @@ ruleTester.run("indent", rule, {
             // Multiline ternary
             // (multiline JSX, colon on its own line)
             code: unIndent`
-                {!foo ?
-                    <Foo
-                        onClick={this.onClick}
-                    />
-                    :
-                    <Bar
-                        onClick={this.onClick}
-                    />
-                }
+                <div>
+                    {!foo ?
+                        <Foo
+                            onClick={this.onClick}
+                        />
+                        :
+                        <Bar
+                            onClick={this.onClick}
+                        />
+                    }
+                </div>
             `
         },
         {
@@ -5216,6 +5284,21 @@ ruleTester.run("indent", rule, {
                 [3, 8, 10, "Punctuator"]
             )
         },
+        {
+            code: unIndent`
+                (
+                    foo
+                    .bar
+                )
+            `,
+            output: unIndent`
+                (
+                    foo
+                        .bar
+                )
+            `,
+            errors: expectedErrors([3, 8, 4, "Punctuator"])
+        },
         {
             code: unIndent`
                 var foo = function(){
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/no-restricted-globals.js b/tests/lib/rules/no-restricted-globals.js
index df9d6e4ff..6ce7b9cd5 100644
--- a/tests/lib/rules/no-restricted-globals.js
+++ b/tests/lib/rules/no-restricted-globals.js
@@ -20,46 +20,158 @@ const ruleTester = new RuleTester();
 
 ruleTester.run("no-restricted-globals", rule, {
     valid: [
-        { code: "foo" },
-        { code: "foo", options: ["bar"] },
-        { code: "var foo = 1;", options: ["foo"] },
-        { code: "event", env: { browser: true }, options: ["bar"] },
-        { code: "import foo from 'bar';", options: ["foo"], parserOptions: { ecmaVersion: 6, sourceType: "module" } },
-        { code: "function foo() {}", options: ["foo"] },
-        { code: "function fn() { var foo; }", options: ["foo"] },
-        { code: "foo.bar", options: ["bar"] }
+        {
+            code: "foo"
+        },
+        {
+            code: "foo",
+            options: ["bar"]
+        },
+        {
+            code: "var foo = 1;",
+            options: ["foo"]
+        },
+        {
+            code: "event",
+            env: { browser: true },
+            options: ["bar"]
+        },
+        {
+            code: "import foo from 'bar';",
+            options: ["foo"],
+            parserOptions: { ecmaVersion: 6, sourceType: "module" }
+        },
+        {
+            code: "function foo() {}",
+            options: ["foo"]
+        },
+        {
+            code: "function fn() { var foo; }",
+            options: ["foo"]
+        },
+        {
+            code: "foo.bar",
+            options: ["bar"]
+        },
+        {
+            code: "foo",
+            options: [{ name: "bar", message: "Use baz instead." }]
+        }
     ],
     invalid: [
         {
-            code: "foo", options: ["foo"],
+            code: "foo",
+            options: ["foo"],
+            errors: [{ message: "Unexpected use of 'foo'.", type: "Identifier" }]
+        },
+        {
+            code: "function fn() { foo; }",
+            options: ["foo"],
+            errors: [{ message: "Unexpected use of 'foo'.", type: "Identifier" }]
+        },
+        {
+            code: "function fn() { foo; }",
+            options: ["foo"],
+            globals: { foo: false },
+            errors: [{ message: "Unexpected use of 'foo'.", type: "Identifier" }]
+        },
+        {
+            code: "event",
+            options: ["foo", "event"],
+            env: { browser: true },
+            errors: [{ message: "Unexpected use of 'event'.", type: "Identifier" }]
+        },
+        {
+            code: "foo",
+            options: ["foo"],
+            globals: { foo: false },
+            errors: [{ message: "Unexpected use of 'foo'.", type: "Identifier" }]
+        },
+        {
+            code: "foo()",
+            options: ["foo"],
             errors: [{ message: "Unexpected use of 'foo'.", type: "Identifier" }]
         },
         {
-            code: "function fn() { foo; }", options: ["foo"],
+            code: "foo.bar()",
+            options: ["foo"],
             errors: [{ message: "Unexpected use of 'foo'.", type: "Identifier" }]
         },
         {
-            code: "function fn() { foo; }", options: ["foo"],
+            code: "foo",
+            options: [{ name: "foo" }],
+            errors: [{ message: "Unexpected use of 'foo'.", type: "Identifier" }]
+        },
+        {
+            code: "function fn() { foo; }",
+            options: [{ name: "foo" }],
+            errors: [{ message: "Unexpected use of 'foo'.", type: "Identifier" }]
+        },
+        {
+            code: "function fn() { foo; }",
+            options: [{ name: "foo" }],
             globals: { foo: false },
             errors: [{ message: "Unexpected use of 'foo'.", type: "Identifier" }]
         },
         {
-            code: "event", options: ["foo", "event"],
+            code: "event",
+            options: ["foo", { name: "event" }],
             env: { browser: true },
             errors: [{ message: "Unexpected use of 'event'.", type: "Identifier" }]
         },
         {
-            code: "foo", options: ["foo"],
+            code: "foo",
+            options: [{ name: "foo" }],
             globals: { foo: false },
             errors: [{ message: "Unexpected use of 'foo'.", type: "Identifier" }]
         },
         {
-            code: "foo()", options: ["foo"],
+            code: "foo()",
+            options: [{ name: "foo" }],
             errors: [{ message: "Unexpected use of 'foo'.", type: "Identifier" }]
         },
         {
-            code: "foo.bar()", options: ["foo"],
+            code: "foo.bar()",
+            options: [{ name: "foo" }],
             errors: [{ message: "Unexpected use of 'foo'.", type: "Identifier" }]
+        },
+        {
+            code: "foo",
+            options: [{ name: "foo", message: "Use bar instead." }],
+            errors: [{ message: "Unexpected use of 'foo'. Use bar instead.", type: "Identifier" }]
+        },
+        {
+            code: "function fn() { foo; }",
+            options: [{ name: "foo", message: "Use bar instead." }],
+            errors: [{ message: "Unexpected use of 'foo'. Use bar instead.", type: "Identifier" }]
+        },
+        {
+            code: "function fn() { foo; }",
+            options: [{ name: "foo", message: "Use bar instead." }],
+            globals: { foo: false },
+            errors: [{ message: "Unexpected use of 'foo'. Use bar instead.", type: "Identifier" }]
+        },
+        {
+            code: "event",
+            options: ["foo", { name: "event", message: "Use local event parameter." }],
+            env: { browser: true },
+            errors: [{ message: "Unexpected use of 'event'. Use local event parameter.", type: "Identifier" }]
+        },
+        {
+            code: "foo",
+            options: [{ name: "foo", message: "Use bar instead." }],
+            globals: { foo: false },
+            errors: [{ message: "Unexpected use of 'foo'. Use bar instead.", type: "Identifier" }]
+        },
+        {
+            code: "foo()",
+            options: [{ name: "foo", message: "Use bar instead." }],
+            errors: [{ message: "Unexpected use of 'foo'. Use bar instead.", type: "Identifier" }]
+        },
+        {
+            code: "foo.bar()",
+            options: [{ name: "foo", message: "Use bar instead." }],
+            errors: [{ message: "Unexpected use of 'foo'. Use bar instead.", type: "Identifier" }]
         }
     ]
 });
diff --git a/tests/lib/rules/no-underscore-dangle.js b/tests/lib/rules/no-underscore-dangle.js
index 0f7a8ad43..ba23575f3 100644
--- a/tests/lib/rules/no-underscore-dangle.js
+++ b/tests/lib/rules/no-underscore-dangle.js
@@ -32,7 +32,13 @@ ruleTester.run("no-underscore-dangle", rule, {
         { code: "foo._bar;", options: [{ allow: ["_bar"] }] },
         { code: "function _foo() {}", options: [{ allow: ["_foo"] }] },
         { code: "this._bar;", options: [{ allowAfterThis: true }] },
-        { code: "class foo { constructor() { super._bar; } }", parserOptions: { ecmaVersion: 6 }, options: [{ allowAfterSuper: true }] }
+        { code: "class foo { constructor() { super._bar; } }", parserOptions: { ecmaVersion: 6 }, options: [{ allowAfterSuper: true }] },
+        { code: "class foo { _onClick() { } }", parserOptions: { ecmaVersion: 6 } },
+        { code: "class foo { onClick_() { } }", parserOptions: { ecmaVersion: 6 } },
+        { code: "const o = { _onClick() { } }", parserOptions: { ecmaVersion: 6 } },
+        { code: "const o = { onClick_() { } }", parserOptions: { ecmaVersion: 6 } },
+        { code: "const o = { _foo: 'bar' }", parserOptions: { ecmaVersion: 6 } },
+        { code: "const o = { foo_: 'bar' }", parserOptions: { ecmaVersion: 6 } }
     ],
     invalid: [
         { code: "var _foo = 1", errors: [{ message: "Unexpected dangling '_' in '_foo'.", type: "VariableDeclarator" }] },
@@ -43,6 +49,10 @@ ruleTester.run("no-underscore-dangle", rule, {
         { code: "foo._bar;", errors: [{ message: "Unexpected dangling '_' in '_bar'.", type: "MemberExpression" }] },
         { code: "this._prop;", errors: [{ message: "Unexpected dangling '_' in '_prop'.", type: "MemberExpression" }] },
         { code: "class foo { constructor() { super._prop; } }", parserOptions: { ecmaVersion: 6 }, errors: [{ message: "Unexpected dangling '_' in '_prop'.", type: "MemberExpression" }] },
-        { code: "class foo { constructor() { this._prop; } }", options: [{ allowAfterSuper: true }], parserOptions: { ecmaVersion: 6 }, errors: [{ message: "Unexpected dangling '_' in '_prop'.", type: "MemberExpression" }] }
+        { code: "class foo { constructor() { this._prop; } }", options: [{ allowAfterSuper: true }], parserOptions: { ecmaVersion: 6 }, errors: [{ message: "Unexpected dangling '_' in '_prop'.", type: "MemberExpression" }] },
+        { code: "class foo { _onClick() { } }", options: [{ enforceInMethodNames: true }], parserOptions: { ecmaVersion: 6 }, errors: [{ message: "Unexpected dangling '_' in '_onClick'.", type: "MethodDefinition" }] },
+        { code: "class foo { onClick_() { } }", options: [{ enforceInMethodNames: true }], parserOptions: { ecmaVersion: 6 }, errors: [{ message: "Unexpected dangling '_' in 'onClick_'.", type: "MethodDefinition" }] },
+        { code: "const o = { _onClick() { } }", options: [{ enforceInMethodNames: true }], parserOptions: { ecmaVersion: 6 }, errors: [{ message: "Unexpected dangling '_' in '_onClick'.", type: "Property" }] },
+        { code: "const o = { onClick_() { } }", options: [{ enforceInMethodNames: true }], parserOptions: { ecmaVersion: 6 }, errors: [{ message: "Unexpected dangling '_' in 'onClick_'.", type: "Property" }] }
     ]
 });
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/rules/prefer-numeric-literals.js b/tests/lib/rules/prefer-numeric-literals.js
index ecaf9de47..73fecfb30 100644
--- a/tests/lib/rules/prefer-numeric-literals.js
+++ b/tests/lib/rules/prefer-numeric-literals.js
@@ -22,12 +22,16 @@ ruleTester.run("prefer-numeric-literals", rule, {
     valid: [
         "parseInt(1);",
         "parseInt(1, 3);",
+        "Number.parseInt(1);",
+        "Number.parseInt(1, 3);",
         "0b111110111 === 503;",
         "0o767 === 503;",
         "0x1F7 === 503;",
         "a[parseInt](1,2);",
         "parseInt(foo);",
-        "parseInt(foo, 2);"
+        "parseInt(foo, 2);",
+        "Number.parseInt(foo);",
+        "Number.parseInt(foo, 2);"
     ],
     invalid: [
         {
@@ -42,6 +46,18 @@ ruleTester.run("prefer-numeric-literals", rule, {
             code: "parseInt(\"1F7\", 16) === 255;",
             output: "0x1F7 === 255;",
             errors: [{ message: "Use hexadecimal literals instead of parseInt()." }]
+        }, {
+            code: "Number.parseInt(\"111110111\", 2) === 503;",
+            output: "0b111110111 === 503;",
+            errors: [{ message: "Use binary literals instead of Number.parseInt()." }]
+        }, {
+            code: "Number.parseInt(\"767\", 8) === 503;",
+            output: "0o767 === 503;",
+            errors: [{ message: "Use octal literals instead of Number.parseInt()." }]
+        }, {
+            code: "Number.parseInt(\"1F7\", 16) === 255;",
+            output: "0x1F7 === 255;",
+            errors: [{ message: "Use hexadecimal literals instead of Number.parseInt()." }]
         }, {
             code: "parseInt('7999', 8);",
             output: null, // not fixed, unexpected 9 in parseInt string
@@ -58,6 +74,22 @@ ruleTester.run("prefer-numeric-literals", rule, {
             code: "parseInt('1️⃣3️⃣3️⃣7️⃣', 16);",
             output: null, // not fixed, javascript doesn't support emoji literals
             errors: [{ message: "Use hexadecimal literals instead of parseInt()." }]
+        }, {
+            code: "Number.parseInt('7999', 8);",
+            output: null, // not fixed, unexpected 9 in parseInt string
+            errors: [{ message: "Use octal literals instead of Number.parseInt()." }]
+        }, {
+            code: "Number.parseInt('1234', 2);",
+            output: null, // not fixed, invalid binary string
+            errors: [{ message: "Use binary literals instead of Number.parseInt()." }]
+        }, {
+            code: "Number.parseInt('1234.5', 8);",
+            output: null, // not fixed, this isn't an integer
+            errors: [{ message: "Use octal literals instead of Number.parseInt()." }]
+        }, {
+            code: "Number.parseInt('1️⃣3️⃣3️⃣7️⃣', 16);",
+            output: null, // not fixed, javascript doesn't support emoji literals
+            errors: [{ message: "Use hexadecimal literals instead of Number.parseInt()." }]
         }
     ]
 });
diff --git a/tests/lib/util/npm-util.js b/tests/lib/util/npm-util.js
index c8c7d8f2d..466d4b839 100644
--- a/tests/lib/util/npm-util.js
+++ b/tests/lib/util/npm-util.js
@@ -9,7 +9,7 @@
 //------------------------------------------------------------------------------
 
 const assert = require("chai").assert,
-    childProcess = require("child_process"),
+    spawn = require("cross-spawn"),
     sinon = require("sinon"),
     npmUtil = require("../../../lib/util/npm-util"),
     log = require("../../../lib/logging"),
@@ -170,31 +170,34 @@ describe("npmUtil", () => {
 
     describe("installSyncSaveDev()", () => {
         it("should invoke npm to install a single desired package", () => {
-            const stub = sandbox.stub(childProcess, "execSync");
+            const stub = sandbox.stub(spawn, "sync");
 
             npmUtil.installSyncSaveDev("desired-package");
             assert(stub.calledOnce);
-            assert.equal(stub.firstCall.args[0], "npm i --save-dev desired-package");
+            assert.equal(stub.firstCall.args[0], "npm");
+            assert.deepEqual(stub.firstCall.args[1], ["i", "--save-dev", "desired-package"]);
             stub.restore();
         });
 
         it("should accept an array of packages to install", () => {
-            const stub = sandbox.stub(childProcess, "execSync");
+            const stub = sandbox.stub(spawn, "sync");
 
             npmUtil.installSyncSaveDev(["first-package", "second-package"]);
             assert(stub.calledOnce);
-            assert.equal(stub.firstCall.args[0], "npm i --save-dev first-package second-package");
+            assert.equal(stub.firstCall.args[0], "npm");
+            assert.deepEqual(stub.firstCall.args[1], ["i", "--save-dev", "first-package", "second-package"]);
             stub.restore();
         });
     });
 
     describe("fetchPeerDependencies()", () => {
         it("should execute 'npm show --json <packageName> peerDependencies' command", () => {
-            const stub = sandbox.stub(childProcess, "execSync").returns("");
+            const stub = sandbox.stub(spawn, "sync").returns({ stdout: "" });
 
             npmUtil.fetchPeerDependencies("desired-package");
             assert(stub.calledOnce);
-            assert.equal(stub.firstCall.args[0], "npm show --json desired-package peerDependencies");
+            assert.equal(stub.firstCall.args[0], "npm");
+            assert.deepEqual(stub.firstCall.args[1], ["show", "--json", "desired-package", "peerDependencies"]);
             stub.restore();
         });
     });