Repository: eslint/eslint
Pull request #8762 (2017-06-18 19:40:25)

Update: Support generator yields in no constant condition
**What is the purpose of this pull request? (put an "X" next to item)**

[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?**
no-constant-conditions

**Does this change cause the rule to produce more or fewer warnings?**
produce less warning

**How will the change be implemented? (New option, new default behavior, etc.)?**
it's supporting yields in generator functions that might otherwise be recognized as violating no-constant-condition rule

**Please provide some example code that this change will affect:**
Supports this as good:

```js
function*() {
    while(true) {
        yield 'foo';
    } 
}
```
You can check the issue at #8566 

**What does the rule currently do for this code?**
Currently, it would just mark the loop as a violation of the rule (While (true) is a breaking the rule) 

**What will the rule do after it's changed?**
It would pass in the generator function example if you yield in what might otherwise be considered a constant-condition.

**Is there anything you'd like reviewers to focus on?**


eslintbot: LGTM
VictorHom: @platinumazure when you get some time, could I get your thoughts on how to approach the two failing tests I have?
eslintbot: LGTM
eslintbot: LGTM
eslintbot: LGTM
VictorHom: Thanks for going through this with me. it's very helpful for me.
@not-an-aardvark 
Do you know what I can do to pass this case in the invalid tests?
`function* foo() {for (let foo = yield; ;) {}}`
not-an-aardvark: Hmm, that case is difficult. I guess one solution would be to only "enter" a `ForStatement` when entering the `test` clause. One way to do this could be to change the `ForStatement` listener to a selector:

```diff
-ForStatement: checkLoop
+"ForStatement > .test": node => checkLoop(node.parent)
```
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-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-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-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();
         });
     });