Repository: eslint/eslint
Pull request #12299 (2019-09-22 00:13:43)

Fix: no-undef-init autofix removes comments
<!--
    ESLint adheres to the [JS Foundation Code of Conduct](https://js.foundation/community/code-of-conduct).
-->

**What is the purpose of this pull request? (put an "X" next to item)**

[X] Bug fix

<!--
    If the item you've checked above has a template, please paste the template questions below and answer them. (If this pull request is addressing an issue, you can just paste a link to the issue here instead.)
-->

**Tell us about your environment**

* **ESLint Version:**  6.4.0
* **Node Version:** 10.16.0
* **npm Version:** 6.9.0

**What parser (default, Babel-ESLint, etc.) are you using?**

default

**Please show your full configuration:**

<details>
<summary>Configuration</summary>

<!-- Paste your configuration below: -->
```js
module.exports = {
  parserOptions: {
    ecmaVersion: 2015,
  }
};
```

</details>

**What did you do? Please include the actual source code causing the issue.**

[Demo link](https://eslint.org/demo#eyJ0ZXh0IjoiLyogZXNsaW50IG5vLXVuZGVmLWluaXQ6ZXJyb3IgKi9cblxubGV0IGEgLyogY29tbWVudCAqLyA9IHVuZGVmaW5lZDtcblxubGV0IGIgPSAvLyBjb21tZW50XG4gICAgdW5kZWZpbmVkOyIsIm9wdGlvbnMiOnsicGFyc2VyT3B0aW9ucyI6eyJlY21hVmVyc2lvbiI6Niwic291cmNlVHlwZSI6InNjcmlwdCIsImVjbWFGZWF0dXJlcyI6e319LCJydWxlcyI6eyJjb25zdHJ1Y3Rvci1zdXBlciI6MiwiZm9yLWRpcmVjdGlvbiI6MiwiZ2V0dGVyLXJldHVybiI6Miwibm8tYXN5bmMtcHJvbWlzZS1leGVjdXRvciI6Miwibm8tY2FzZS1kZWNsYXJhdGlvbnMiOjIsIm5vLWNsYXNzLWFzc2lnbiI6Miwibm8tY29tcGFyZS1uZWctemVybyI6Miwibm8tY29uZC1hc3NpZ24iOjIsIm5vLWNvbnN0LWFzc2lnbiI6Miwibm8tY29uc3RhbnQtY29uZGl0aW9uIjoyLCJuby1jb250cm9sLXJlZ2V4IjoyLCJuby1kZWJ1Z2dlciI6Miwibm8tZGVsZXRlLXZhciI6Miwibm8tZHVwZS1hcmdzIjoyLCJuby1kdXBlLWNsYXNzLW1lbWJlcnMiOjIsIm5vLWR1cGUta2V5cyI6Miwibm8tZHVwbGljYXRlLWNhc2UiOjIsIm5vLWVtcHR5IjoyLCJuby1lbXB0eS1jaGFyYWN0ZXItY2xhc3MiOjIsIm5vLWVtcHR5LXBhdHRlcm4iOjIsIm5vLWV4LWFzc2lnbiI6Miwibm8tZXh0cmEtYm9vbGVhbi1jYXN0IjoyLCJuby1leHRyYS1zZW1pIjoyLCJuby1mYWxsdGhyb3VnaCI6Miwibm8tZnVuYy1hc3NpZ24iOjIsIm5vLWdsb2JhbC1hc3NpZ24iOjIsIm5vLWlubmVyLWRlY2xhcmF0aW9ucyI6Miwibm8taW52YWxpZC1yZWdleHAiOjIsIm5vLWlycmVndWxhci13aGl0ZXNwYWNlIjoyLCJuby1taXNsZWFkaW5nLWNoYXJhY3Rlci1jbGFzcyI6Miwibm8tbWl4ZWQtc3BhY2VzLWFuZC10YWJzIjoyLCJuby1uZXctc3ltYm9sIjoyLCJuby1vYmotY2FsbHMiOjIsIm5vLW9jdGFsIjoyLCJuby1wcm90b3R5cGUtYnVpbHRpbnMiOjIsIm5vLXJlZGVjbGFyZSI6Miwibm8tcmVnZXgtc3BhY2VzIjoyLCJuby1zZWxmLWFzc2lnbiI6Miwibm8tc2hhZG93LXJlc3RyaWN0ZWQtbmFtZXMiOjIsIm5vLXNwYXJzZS1hcnJheXMiOjIsIm5vLXRoaXMtYmVmb3JlLXN1cGVyIjoyLCJuby11bmRlZiI6Miwibm8tdW5leHBlY3RlZC1tdWx0aWxpbmUiOjIsIm5vLXVucmVhY2hhYmxlIjoyLCJuby11bnNhZmUtZmluYWxseSI6Miwibm8tdW5zYWZlLW5lZ2F0aW9uIjoyLCJuby11bnVzZWQtbGFiZWxzIjoyLCJuby11c2VsZXNzLWNhdGNoIjoyLCJuby11c2VsZXNzLWVzY2FwZSI6Miwibm8td2l0aCI6MiwicmVxdWlyZS1hdG9taWMtdXBkYXRlcyI6MiwicmVxdWlyZS15aWVsZCI6MiwidXNlLWlzbmFuIjoyLCJ2YWxpZC10eXBlb2YiOjJ9LCJlbnYiOnt9fX0=)

```js
/* eslint no-undef-init:error */

let a /* comment */ = undefined;

let b = // comment
    undefined;
```

**What did you expect to happen?**

2 errors, but not to remove comments.

**What actually happened? Please include the actual, raw output from ESLint.**

```js
/* eslint no-undef-init:error */

let a;

let b;
```

<!--
    Please ensure your pull request is ready:

    - Read the pull request guide (https://eslint.org/docs/developer-guide/contributing/pull-requests)
    - Include tests for this change
    - Update documentation for this change (if appropriate)
-->

<!--
    The following is required for all pull requests:
-->

**What changes did you make? (Give an overview)**

Report errors but don't fix if that would remove any comments.

**Is there anything you'd like reviewers to focus on?**


kaicataldo: Hmmm, looks like we have a flaky test (not necessarily related to this change) in the test suite.
kaicataldo: Looks like https://github.com/eslint/eslint/pull/12290/checks?check_run_id=228384386 is failing in the same environment (though it looks like it timed out in the install rather than the test run phase).
mdjermanovic: Could be a problem with  `CI / Test (macOS-latest, 12.x)`, it also failed in #12290 but for a different reason.
kaicataldo: Yeah, seems likely it's an issue with GitHub Hooks. I don't think we need to hold up either PR for this, especially since it's passing in Azure Pipelines in macOS.

Patch:
diff --git a/Makefile.js b/Makefile.js
index a01d13bde..d11ca75a0 100644
--- a/Makefile.js
+++ b/Makefile.js
@@ -473,12 +473,12 @@ target.all = function() {
     target.test();
 };
 
-target.lint = function() {
+target.lint = function([fix = false] = []) {
     let errors = 0,
         lastReturn;
 
     echo("Validating JavaScript files");
-    lastReturn = exec(`${ESLINT} .`);
+    lastReturn = exec(`${ESLINT}${fix ? "--fix" : ""} .`);
     if (lastReturn.code !== 0) {
         errors++;
     }
diff --git a/README.md b/README.md
index ee077d1ea..c553cc810 100644
--- a/README.md
+++ b/README.md
@@ -120,7 +120,7 @@ Yes, ESLint natively supports parsing JSX syntax (this must be enabled in [confi
 
 ### What ECMAScript versions does ESLint support?
 
-ESLint has full support for ECMAScript 3, 5 (default), 2015, 2016, 2017, and 2018. You can set your desired ECMAScript syntax (and other settings, like global variables or your target environments) through [configuration](https://eslint.org/docs/user-guide/configuring).
+ESLint has full support for ECMAScript 3, 5 (default), 2015, 2016, 2017, 2018, and 2019. You can set your desired ECMAScript syntax (and other settings, like global variables or your target environments) through [configuration](https://eslint.org/docs/user-guide/configuring).
 
 ### What about experimental features?
 
@@ -262,9 +262,9 @@ The following companies, organizations, and individuals support ESLint's ongoing
 <!-- NOTE: This section is autogenerated. Do not manually edit.-->
 <!--sponsorsstart-->
 <h3>Gold Sponsors</h3>
-<p><a href="https://www.shopify.com"><img src="https://images.opencollective.com/shopify/eeb91aa/logo.png" alt="Shopify" height="96"></a> <a href="http://engineering.salesforce.com"><img src="https://images.opencollective.com/salesforce/d1b37c4/logo.png" alt="Salesforce" height="96"></a> <a href="https://badoo.com/team?utm_source=eslint"><img src="https://images.opencollective.com/badoo/2826a3b/logo.png" alt="Badoo" height="96"></a> <a href="https://www.airbnb.com/"><img src="https://images.opencollective.com/airbnb/d327d66/logo.png" alt="Airbnb" height="96"></a> <a href="https://opensource.facebook.com"><img src="https://images.opencollective.com/fbopensource/fbb8a5b/logo.png" alt="Facebook Open Source" height="96"></a></p><h3>Silver Sponsors</h3>
+<p><a href="https://www.shopify.com"><img src="https://images.opencollective.com/shopify/eeb91aa/logo.png" alt="Shopify" height="96"></a> <a href="http://engineering.salesforce.com"><img src="https://images.opencollective.com/salesforce/ca8f997/logo.png" alt="Salesforce" height="96"></a> <a href="https://badoo.com/team?utm_source=eslint"><img src="https://images.opencollective.com/badoo/2826a3b/logo.png" alt="Badoo" height="96"></a> <a href="https://www.airbnb.com/"><img src="https://images.opencollective.com/airbnb/d327d66/logo.png" alt="Airbnb" height="96"></a> <a href="https://opensource.facebook.com"><img src="https://images.opencollective.com/fbopensource/fbb8a5b/logo.png" alt="Facebook Open Source" height="96"></a></p><h3>Silver Sponsors</h3>
 <p><a href="https://www.ampproject.org/"><img src="https://images.opencollective.com/amp/c8a3b25/logo.png" alt="AMP Project" height="64"></a></p><h3>Bronze Sponsors</h3>
-<p><a href="https://www.vpsserver.com"><img src="https://images.opencollective.com/vpsservercom/logo.png" alt="VPS Server" height="32"></a> <a href="https://icons8.com"><img src="https://images.opencollective.com/icons8/0b37d14/logo.png" alt="Free Icons by Icons8" height="32"></a> <a href="https://uxplanet.org/top-ui-ux-design-agencies-user-experience-firms-8c54697e290"><img src="https://images.opencollective.com/ui-ux-design-agencies/cae5dfe/logo.png" alt="UI UX Design Agencies" height="32"></a> <a href="https://clay.global"><img src="https://images.opencollective.com/clayglobal/2468f34/logo.png" alt="clay" height="32"></a> <a href="https://discordapp.com"><img src="https://images.opencollective.com/discordapp/7e3d9a9/logo.png" alt="Discord" height="32"></a> <a href="https://themeisle.com"><img src="https://images.opencollective.com/themeisle/logo.png" alt="ThemeIsle" height="32"></a> <a href="https://tekhattan.com"><img src="https://images.opencollective.com/tekhattan/bc73c28/logo.png" alt="TekHattan" height="32"></a> <a href="https://www.marfeel.com/"><img src="https://images.opencollective.com/marfeel/4b88e30/logo.png" alt="Marfeel" height="32"></a> <a href="http://www.firesticktricks.com"><img src="https://images.opencollective.com/fire-stick-tricks/b8fbe2c/logo.png" alt="Fire Stick Tricks" height="32"></a> <a href="https://jsheroes.io/"><img src="https://images.opencollective.com/jsheroes1/9fedf0b/logo.png" alt="JSHeroes " height="32"></a></p>
+<p><a href="https://discordapp.com"><img src="https://images.opencollective.com/discordapp/7e3d9a9/logo.png" alt="Discord" height="32"></a> <a href="https://mixpanel.com"><img src="https://images.opencollective.com/mixpanel/c35a9d7/logo.png" alt="Mixpanel" height="32"></a> <a href="https://icons8.com"><img src="https://images.opencollective.com/icons8/0b37d14/logo.png" alt="Free Icons by Icons8" height="32"></a> <a href="https://uxplanet.org/top-ui-ux-design-agencies-user-experience-firms-8c54697e290"><img src="https://images.opencollective.com/ui-ux-design-agencies/cae5dfe/logo.png" alt="UI UX Design Agencies" height="32"></a> <a href="https://clay.global"><img src="https://images.opencollective.com/clayglobal/2468f34/logo.png" alt="clay" height="32"></a> <a href="https://www.vpsserver.com"><img src="https://images.opencollective.com/vpsservercom/logo.png" alt="VPS Server" height="32"></a> <a href="https://themeisle.com"><img src="https://images.opencollective.com/themeisle/logo.png" alt="ThemeIsle" height="32"></a> <a href="https://tekhattan.com"><img src="https://images.opencollective.com/tekhattan/bc73c28/logo.png" alt="TekHattan" height="32"></a> <a href="https://www.marfeel.com/"><img src="https://images.opencollective.com/marfeel/4b88e30/logo.png" alt="Marfeel" height="32"></a> <a href="http://www.firesticktricks.com"><img src="https://images.opencollective.com/fire-stick-tricks/b8fbe2c/logo.png" alt="Fire Stick Tricks" height="32"></a> <a href="https://jsheroes.io/"><img src="https://images.opencollective.com/jsheroes1/9fedf0b/logo.png" alt="JSHeroes " height="32"></a></p>
 <!--sponsorsend-->
 
 ## <a name="technology-sponsors"></a>Technology Sponsors
diff --git a/bin/eslint.js b/bin/eslint.js
index 061e94767..a9f51f1d7 100755
--- a/bin/eslint.js
+++ b/bin/eslint.js
@@ -16,9 +16,9 @@ require("v8-compile-cache");
 // Helpers
 //------------------------------------------------------------------------------
 
-const useStdIn = (process.argv.indexOf("--stdin") > -1),
-    init = (process.argv.indexOf("--init") > -1),
-    debug = (process.argv.indexOf("--debug") > -1);
+const useStdIn = process.argv.includes("--stdin"),
+    init = process.argv.includes("--init"),
+    debug = process.argv.includes("--debug");
 
 // must do this initialization *before* other requires in order to work
 if (debug) {
@@ -30,9 +30,9 @@ if (debug) {
 //------------------------------------------------------------------------------
 
 // now we can safely include the other modules that use debug
-const cli = require("../lib/cli"),
-    path = require("path"),
-    fs = require("fs");
+const path = require("path"),
+    fs = require("fs"),
+    cli = require("../lib/cli");
 
 //------------------------------------------------------------------------------
 // Execution
@@ -50,7 +50,6 @@ process.once("uncaughtException", err => {
         console.error("\nOops! Something went wrong! :(");
         console.error(`\nESLint: ${pkg.version}.\n\n${template(err.messageData || {})}`);
     } else {
-
         console.error(err.stack);
     }
 
@@ -60,13 +59,40 @@ process.once("uncaughtException", err => {
 if (useStdIn) {
 
     /*
-     * Note: `process.stdin.fd` is not used here due to https://github.com/nodejs/node/issues/7439.
-     * Accessing the `process.stdin` property seems to modify the behavior of file descriptor 0, resulting
-     * in an error when stdin is piped in asynchronously.
+     * Note: See
+     * - https://github.com/nodejs/node/blob/master/doc/api/process.md#processstdin
+     * - https://github.com/nodejs/node/blob/master/doc/api/process.md#a-note-on-process-io
+     * - https://lists.gnu.org/archive/html/bug-gnu-emacs/2016-01/msg00419.html
+     * - https://github.com/nodejs/node/issues/7439 (historical)
+     *
+     * On Windows using `fs.readFileSync(STDIN_FILE_DESCRIPTOR, "utf8")` seems
+     * to read 4096 bytes before blocking and never drains to read further data.
+     *
+     * The investigation on the Emacs thread indicates:
+     *
+     * > Emacs on MS-Windows uses pipes to communicate with subprocesses; a
+     * > pipe on Windows has a 4K buffer. So as soon as Emacs writes more than
+     * > 4096 bytes to the pipe, the pipe becomes full, and Emacs then waits for
+     * > the subprocess to read its end of the pipe, at which time Emacs will
+     * > write the rest of the stuff.
+     *
+     * Using the nodejs code example for reading from stdin.
      */
-    const STDIN_FILE_DESCRIPTOR = 0;
+    let contents = "",
+        chunk = "";
+
+    process.stdin.setEncoding("utf8");
+    process.stdin.on("readable", () => {
 
-    process.exitCode = cli.execute(process.argv, fs.readFileSync(STDIN_FILE_DESCRIPTOR, "utf8"));
+        // Use a loop to make sure we read all available data.
+        while ((chunk = process.stdin.read()) !== null) {
+            contents += chunk;
+        }
+    });
+
+    process.stdin.on("end", () => {
+        process.exitCode = cli.execute(process.argv, contents, "utf8");
+    });
 } else if (init) {
     const configInit = require("../lib/init/config-initializer");
 
diff --git a/docs/developer-guide/working-with-rules.md b/docs/developer-guide/working-with-rules.md
index 2a9f1b9d3..35f224b32 100644
--- a/docs/developer-guide/working-with-rules.md
+++ b/docs/developer-guide/working-with-rules.md
@@ -616,6 +616,6 @@ The thing that makes ESLint different from other linters is the ability to defin
 
 Runtime rules are written in the same format as all other rules. Create your rule as you would any other and then follow these steps:
 
-1. Place all of your runtime rules in the same directory (i.e., `eslint_rules`).
+1. Place all of your runtime rules in the same directory (e.g., `eslint_rules`).
 2. Create a [configuration file](../user-guide/configuring.md) and specify your rule ID error level under the `rules` key. Your rule will not run unless it has a value of `1` or `2` in the configuration file.
 3. Run the [command line interface](../user-guide/command-line-interface.md) using the `--rulesdir` option to specify the location of your runtime rules.
diff --git a/docs/user-guide/command-line-interface.md b/docs/user-guide/command-line-interface.md
index 532d8f947..8938bc91f 100644
--- a/docs/user-guide/command-line-interface.md
+++ b/docs/user-guide/command-line-interface.md
@@ -78,6 +78,7 @@ Caching:
 
 Miscellaneous:
   --init                         Run config initialization wizard - default: false
+  --env-info                     Output execution environment information - default: false
   --debug                        Output debugging information
   -h, --help                     Show help
   -v, --version                  Output the version number
@@ -446,6 +447,10 @@ This option will start config initialization wizard. It's designed to help new u
 
 The resulting configuration file will be created in the current directory.
 
+#### `--env-info`
+
+This option outputs information about the execution environment, including the version of Node, npm, and local and global installations of ESLint. The ESLint team may ask for this information to help solve bugs.
+
 #### `--debug`
 
 This option outputs debugging information to the console. This information is useful when you're seeing a problem and having a hard time pinpointing it. The ESLint team may ask for this debugging information to help solve bugs.
diff --git a/docs/user-guide/configuring.md b/docs/user-guide/configuring.md
index 8f5fd286d..172e79736 100644
--- a/docs/user-guide/configuring.md
+++ b/docs/user-guide/configuring.md
@@ -138,6 +138,8 @@ An environment defines global variables that are predefined. The available envir
 * `commonjs` - CommonJS global variables and CommonJS scoping (use this for browser-only code that uses Browserify/WebPack).
 * `shared-node-browser` - Globals common to both Node.js and Browser.
 * `es6` - enable all ECMAScript 6 features except for modules (this automatically sets the `ecmaVersion` parser option to 6).
+* `es2017` - adds all ECMAScript 2017 globals and automatically sets the `ecmaVersion` parser option to 8.
+* `es2020` - adds all ECMAScript 2020 globals and automatically sets the `ecmaVersion` parser option to 11.
 * `worker` - web workers global variables.
 * `amd` - defines `require()` and `define()` as global variables as per the [amd](https://github.com/amdjs/amdjs-api/wiki/AMD) spec.
 * `mocha` - adds all of the Mocha testing global variables.
diff --git a/lib/cli-engine/cli-engine.js b/lib/cli-engine/cli-engine.js
index 7f80887f0..3c67d33da 100644
--- a/lib/cli-engine/cli-engine.js
+++ b/lib/cli-engine/cli-engine.js
@@ -85,8 +85,8 @@ const validFixTypes = new Set(["problem", "suggestion", "layout"]);
  * @property {number} warningCount Number of warnings for the result.
  * @property {number} fixableErrorCount Number of fixable errors for the result.
  * @property {number} fixableWarningCount Number of fixable warnings for the result.
- * @property {string=} [source] The source code of the file that was linted.
- * @property {string=} [output] The source code of the file that was linted, with as many fixes applied as possible.
+ * @property {string} [source] The source code of the file that was linted.
+ * @property {string} [output] The source code of the file that was linted, with as many fixes applied as possible.
  */
 
 /**
@@ -329,7 +329,6 @@ function getRule(ruleId, configArrays) {
 /**
  * Collect used deprecated rules.
  * @param {ConfigArray[]} usedConfigArrays The config arrays which were used.
- * @param {Map<string, Object>} ruleMap The rule definitions which were used (built-ins).
  * @returns {IterableIterator<DeprecatedRuleInfo>} Used deprecated rules.
  */
 function *iterateRuleDeprecationWarnings(usedConfigArrays) {
@@ -530,7 +529,6 @@ class CLIEngine {
     /**
      * Creates a new instance of the core CLI engine.
      * @param {CLIEngineOptions} providedOptions The options for this instance.
-     * @constructor
      */
     constructor(providedOptions) {
         const options = Object.assign(
diff --git a/lib/cli-engine/ignored-paths.js b/lib/cli-engine/ignored-paths.js
index 137d156e9..12dfce066 100644
--- a/lib/cli-engine/ignored-paths.js
+++ b/lib/cli-engine/ignored-paths.js
@@ -78,7 +78,7 @@ function mergeDefaultOptions(options) {
     return Object.assign({}, DEFAULT_OPTIONS, options);
 }
 
-/* eslint-disable valid-jsdoc */
+/* eslint-disable jsdoc/check-param-names, jsdoc/require-param */
 /**
  * Normalize the path separators in a given string.
  * On Windows environment, this replaces `\` by `/`.
@@ -89,7 +89,7 @@ function mergeDefaultOptions(options) {
 const normalizePathSeps = path.sep === "/"
     ? (str => str)
     : ((seps, str) => str.replace(seps, "/")).bind(null, new RegExp(`\\${path.sep}`, "gu"));
-/* eslint-enable valid-jsdoc */
+/* eslint-enable jsdoc/check-param-names, jsdoc/require-param */
 
 /**
  * Converts a glob pattern to a new glob pattern relative to a different directory
@@ -298,7 +298,7 @@ class IgnoredPaths {
 
     /**
      * read ignore filepath
-     * @param {string} filePath, file to add to ig
+     * @param {string} filePath file to add to ig
      * @returns {Array} raw ignore rules
      */
     readIgnoreFile(filePath) {
diff --git a/lib/cli-engine/lint-result-cache.js b/lib/cli-engine/lint-result-cache.js
index 9408780fb..14e19d9e5 100644
--- a/lib/cli-engine/lint-result-cache.js
+++ b/lib/cli-engine/lint-result-cache.js
@@ -47,7 +47,6 @@ class LintResultCache {
 
     /**
      * Creates a new LintResultCache instance.
-     * @constructor
      * @param {string} cacheFileLocation The cache file location.
      *   configuration lookup by file path).
      */
diff --git a/lib/cli.js b/lib/cli.js
index c34545544..18a917cf0 100644
--- a/lib/cli.js
+++ b/lib/cli.js
@@ -20,7 +20,8 @@ const fs = require("fs"),
     mkdirp = require("mkdirp"),
     { CLIEngine } = require("./cli-engine"),
     options = require("./options"),
-    log = require("./shared/logging");
+    log = require("./shared/logging"),
+    RuntimeInfo = require("./shared/runtime-info");
 
 const debug = require("debug")("eslint:cli");
 
@@ -159,13 +160,18 @@ const cli = {
         }
 
         const files = currentOptions._;
-
         const useStdin = typeof text === "string";
 
-        if (currentOptions.version) { // version from package.json
-
-            log.info(`v${require("../package.json").version}`);
-
+        if (currentOptions.version) {
+            log.info(RuntimeInfo.version());
+        } else if (currentOptions.envInfo) {
+            try {
+                log.info(RuntimeInfo.environment());
+                return 0;
+            } catch (err) {
+                log.error(err.message);
+                return 2;
+            }
         } else if (currentOptions.printConfig) {
             if (files.length) {
                 log.error("The --print-config option must be used with exactly one file name.");
@@ -177,17 +183,13 @@ const cli = {
             }
 
             const engine = new CLIEngine(translateOptions(currentOptions));
-
             const fileConfig = engine.getConfigForFile(currentOptions.printConfig);
 
             log.info(JSON.stringify(fileConfig, null, "  "));
             return 0;
         } else if (currentOptions.help || (!files.length && !useStdin)) {
-
             log.info(options.generateHelp());
-
         } else {
-
             debug(`Running on ${useStdin ? "text" : "files"}`);
 
             if (currentOptions.fix && currentOptions.fixDryRun) {
@@ -227,9 +229,8 @@ const cli = {
 
                 return (report.errorCount || tooManyWarnings) ? 1 : 0;
             }
-            return 2;
-
 
+            return 2;
         }
 
         return 0;
diff --git a/lib/init/config-rule.js b/lib/init/config-rule.js
index fbe9b779a..e40feb714 100644
--- a/lib/init/config-rule.js
+++ b/lib/init/config-rule.js
@@ -99,7 +99,7 @@ function groupByProperty(objects) {
  * Configs may also have one or more additional elements to specify rule
  * configuration or options.
  *
- * @typedef {array|number} ruleConfig
+ * @typedef {Array|number} ruleConfig
  * @param {number}  0  The rule's severity (0, 1, 2).
  */
 
@@ -185,7 +185,7 @@ class RuleConfigSet {
 
         /**
          * Stored valid rule configurations for this instance
-         * @type {array}
+         * @type {Array}
          */
         this.ruleConfigs = configs || [];
     }
diff --git a/lib/init/npm-utils.js b/lib/init/npm-utils.js
index 7fe7c6e7d..3d4a896b6 100644
--- a/lib/init/npm-utils.js
+++ b/lib/init/npm-utils.js
@@ -163,7 +163,7 @@ function checkDevDeps(packages) {
 /**
  * Check whether package.json is found in current path.
  *
- * @param   {string=} startDir Starting directory
+ * @param   {string} [startDir] Starting directory
  * @returns {boolean} Whether a package.json is found in current path.
  */
 function checkPackageJson(startDir) {
diff --git a/lib/linter/code-path-analysis/debug-helpers.js b/lib/linter/code-path-analysis/debug-helpers.js
index 9af985ce8..2ca6dbc1e 100644
--- a/lib/linter/code-path-analysis/debug-helpers.js
+++ b/lib/linter/code-path-analysis/debug-helpers.js
@@ -21,7 +21,7 @@ const debug = require("debug")("eslint:code-path");
  * @returns {string} Id of the segment.
  */
 /* istanbul ignore next */
-function getId(segment) { // eslint-disable-line require-jsdoc
+function getId(segment) { // eslint-disable-line jsdoc/require-jsdoc
     return segment.id + (segment.reachable ? "" : "!");
 }
 
diff --git a/lib/linter/linter.js b/lib/linter/linter.js
index 7d1dc8c8c..534181da9 100644
--- a/lib/linter/linter.js
+++ b/lib/linter/linter.js
@@ -472,7 +472,6 @@ function normalizeFilename(filename) {
     return index === -1 ? filename : parts.slice(index).join(path.sep);
 }
 
-// eslint-disable-next-line valid-jsdoc
 /**
  * Normalizes the possible options for `linter.verify` and `linter.verifyAndFix` to a
  * consistent shape.
diff --git a/lib/options.js b/lib/options.js
index 440773a84..83bf9afc2 100644
--- a/lib/options.js
+++ b/lib/options.js
@@ -224,6 +224,12 @@ module.exports = optionator({
             default: "false",
             description: "Run config initialization wizard"
         },
+        {
+            option: "env-info",
+            type: "Boolean",
+            default: "false",
+            description: "Output execution environment information"
+        },
         {
             option: "debug",
             type: "Boolean",
diff --git a/lib/rules/indent-legacy.js b/lib/rules/indent-legacy.js
index fb84b732e..f1c024c36 100644
--- a/lib/rules/indent-legacy.js
+++ b/lib/rules/indent-legacy.js
@@ -303,7 +303,7 @@ module.exports = {
          * @param {int} needed Expected indentation character count
          * @param {int} gottenSpaces Indentation space count in the actual node/code
          * @param {int} gottenTabs Indentation tab count in the actual node/code
-         * @param {Object=} loc Error line and column location
+         * @param {Object} [loc] Error line and column location
          * @param {boolean} isLastNodeCheck Is the error for last node check
          * @returns {void}
          */
diff --git a/lib/rules/new-parens.js b/lib/rules/new-parens.js
index 4b9958fe6..405ec1b51 100644
--- a/lib/rules/new-parens.js
+++ b/lib/rules/new-parens.js
@@ -65,7 +65,11 @@ module.exports = {
 
                 const lastToken = sourceCode.getLastToken(node);
                 const hasLastParen = lastToken && astUtils.isClosingParenToken(lastToken);
-                const hasParens = hasLastParen && astUtils.isOpeningParenToken(sourceCode.getTokenBefore(lastToken));
+
+                // `hasParens` is true only if the new expression ends with its own parens, e.g., new new foo() does not end with its own parens
+                const hasParens = hasLastParen &&
+                    astUtils.isOpeningParenToken(sourceCode.getTokenBefore(lastToken)) &&
+                    node.callee.range[1] < node.range[1];
 
                 if (always) {
                     if (!hasParens) {
diff --git a/lib/rules/no-extra-parens.js b/lib/rules/no-extra-parens.js
index c5cf79047..c6809c355 100644
--- a/lib/rules/no-extra-parens.js
+++ b/lib/rules/no-extra-parens.js
@@ -205,7 +205,14 @@ module.exports = {
             const lastToken = sourceCode.getLastToken(newExpression);
             const penultimateToken = sourceCode.getTokenBefore(lastToken);
 
-            return newExpression.arguments.length > 0 || astUtils.isOpeningParenToken(penultimateToken) && astUtils.isClosingParenToken(lastToken);
+            return newExpression.arguments.length > 0 ||
+                (
+
+                    // The expression should end with its own parens, e.g., new new foo() is not a new expression with parens
+                    astUtils.isOpeningParenToken(penultimateToken) &&
+                    astUtils.isClosingParenToken(lastToken) &&
+                    newExpression.callee.range[1] < newExpression.range[1]
+                );
         }
 
         /**
@@ -338,7 +345,7 @@ module.exports = {
             function finishReport() {
                 context.report({
                     node,
-                    loc: leftParenToken.loc.start,
+                    loc: leftParenToken.loc,
                     messageId: "unexpected",
                     fix(fixer) {
                         const parenthesizedSource = sourceCode.text.slice(leftParenToken.range[1], rightParenToken.range[0]);
@@ -887,6 +894,12 @@ module.exports = {
                     report(node.object);
                 }
 
+                if (nodeObjHasExcessParens &&
+                  node.object.type === "NewExpression" &&
+                  isNewExpressionWithParens(node.object)) {
+                    report(node.object);
+                }
+
                 if (node.computed && hasExcessParens(node.property)) {
                     report(node.property);
                 }
diff --git a/lib/rules/no-undef-init.js b/lib/rules/no-undef-init.js
index 1390c880f..1fdccb867 100644
--- a/lib/rules/no-undef-init.js
+++ b/lib/rules/no-undef-init.js
@@ -37,7 +37,8 @@ module.exports = {
                     init = node.init && node.init.name,
                     scope = context.getScope(),
                     undefinedVar = astUtils.getVariableByName(scope, "undefined"),
-                    shadowed = undefinedVar && undefinedVar.defs.length > 0;
+                    shadowed = undefinedVar && undefinedVar.defs.length > 0,
+                    lastToken = sourceCode.getLastToken(node);
 
                 if (init === "undefined" && node.parent.kind !== "const" && !shadowed) {
                     context.report({
@@ -54,6 +55,11 @@ module.exports = {
                                 // Don't fix destructuring assignment to `undefined`.
                                 return null;
                             }
+
+                            if (sourceCode.commentsExistBetween(node.id, lastToken)) {
+                                return null;
+                            }
+
                             return fixer.removeRange([node.id.range[1], node.range[1]]);
                         }
                     });
diff --git a/lib/rules/no-useless-rename.js b/lib/rules/no-useless-rename.js
index c1860645e..67af97c8b 100644
--- a/lib/rules/no-useless-rename.js
+++ b/lib/rules/no-useless-rename.js
@@ -36,7 +36,8 @@ module.exports = {
     },
 
     create(context) {
-        const options = context.options[0] || {},
+        const sourceCode = context.getSourceCode(),
+            options = context.options[0] || {},
             ignoreDestructuring = options.ignoreDestructuring === true,
             ignoreImport = options.ignoreImport === true,
             ignoreExport = options.ignoreExport === true;
@@ -64,10 +65,18 @@ module.exports = {
                     type
                 },
                 fix(fixer) {
+                    if (sourceCode.commentsExistBetween(initial, result)) {
+                        return null;
+                    }
+
+                    const replacementText = result.type === "AssignmentPattern"
+                        ? sourceCode.getText(result)
+                        : name;
+
                     return fixer.replaceTextRange([
                         initial.range[0],
                         result.range[1]
-                    ], name);
+                    ], replacementText);
                 }
             });
         }
@@ -82,26 +91,21 @@ module.exports = {
                 return;
             }
 
-            const properties = node.properties;
-
-            for (let i = 0; i < properties.length; i++) {
-                if (properties[i].shorthand) {
-                    continue;
-                }
+            for (const property of node.properties) {
 
                 /**
-                 * If an ObjectPattern property is computed, we have no idea
-                 * if a rename is useless or not. If an ObjectPattern property
-                 * lacks a key, it is likely an ExperimentalRestProperty and
-                 * so there is no "renaming" occurring here.
+                 * Properties using shorthand syntax and rest elements can not be renamed.
+                 * If the property is computed, we have no idea if a rename is useless or not.
                  */
-                if (properties[i].computed || !properties[i].key) {
+                if (property.shorthand || property.type === "RestElement" || property.computed) {
                     continue;
                 }
 
-                if (properties[i].key.type === "Identifier" && properties[i].key.name === properties[i].value.name ||
-                        properties[i].key.type === "Literal" && properties[i].key.value === properties[i].value.name) {
-                    reportError(properties[i], properties[i].key, properties[i].value, "Destructuring assignment");
+                const key = (property.key.type === "Identifier" && property.key.name) || (property.key.type === "Literal" && property.key.value);
+                const renamedKey = property.value.type === "AssignmentPattern" ? property.value.left.name : property.value.name;
+
+                if (key === renamedKey) {
+                    reportError(property, property.key, property.value, "Destructuring assignment");
                 }
             }
         }
diff --git a/lib/rules/prefer-numeric-literals.js b/lib/rules/prefer-numeric-literals.js
index ca7358aa0..b4113a3b3 100644
--- a/lib/rules/prefer-numeric-literals.js
+++ b/lib/rules/prefer-numeric-literals.js
@@ -96,6 +96,10 @@ module.exports = {
                         fix(fixer) {
                             const newPrefix = prefixMap[node.arguments[1].value];
 
+                            if (sourceCode.getCommentsInside(node).length) {
+                                return null;
+                            }
+
                             if (+(newPrefix + node.arguments[0].value) !== parseInt(node.arguments[0].value, node.arguments[1].value)) {
 
                                 /*
diff --git a/lib/shared/logging.js b/lib/shared/logging.js
index 3eb898c34..6aa1f5a1e 100644
--- a/lib/shared/logging.js
+++ b/lib/shared/logging.js
@@ -12,6 +12,7 @@ module.exports = {
 
     /**
      * Cover for console.log
+     * @param {...any} args The elements to log.
      * @returns {void}
      */
     info(...args) {
@@ -20,6 +21,7 @@ module.exports = {
 
     /**
      * Cover for console.error
+     * @param {...any} args The elements to log.
      * @returns {void}
      */
     error(...args) {
diff --git a/lib/shared/runtime-info.js b/lib/shared/runtime-info.js
new file mode 100644
index 000000000..169bbc58f
--- /dev/null
+++ b/lib/shared/runtime-info.js
@@ -0,0 +1,163 @@
+/**
+ * @fileoverview Utility to get information about the execution environment.
+ * @author Kai Cataldo
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const path = require("path");
+const spawn = require("cross-spawn");
+const { isEmpty } = require("lodash");
+const log = require("../shared/logging");
+const packageJson = require("../../package.json");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Generates and returns execution environment information.
+ * @returns {string} A string that contains execution environment information.
+ */
+function environment() {
+    const cache = new Map();
+
+    /**
+     * Checks if a path is a child of a directory.
+     * @param {string} parentPath - The parent path to check.
+     * @param {string} childPath - The path to check.
+     * @returns {boolean} Whether or not the given path is a child of a directory.
+     */
+    function isChildOfDirectory(parentPath, childPath) {
+        return !path.relative(parentPath, childPath).startsWith("..");
+    }
+
+    /**
+     * Synchronously executes a shell command and formats the result.
+     * @param {string} cmd - The command to execute.
+     * @param {Array} args - The arguments to be executed with the command.
+     * @returns {string} The version returned by the command.
+     */
+    function execCommand(cmd, args) {
+        const key = [cmd, ...args].join(" ");
+
+        if (cache.has(key)) {
+            return cache.get(key);
+        }
+
+        const process = spawn.sync(cmd, args, { encoding: "utf8" });
+
+        if (process.error) {
+            throw process.error;
+        }
+
+        const result = process.stdout.trim();
+
+        cache.set(key, result);
+        return result;
+    }
+
+    /**
+     * Normalizes a version number.
+     * @param {string} versionStr - The string to normalize.
+     * @returns {string} The normalized version number.
+     */
+    function normalizeVersionStr(versionStr) {
+        return versionStr.startsWith("v") ? versionStr : `v${versionStr}`;
+    }
+
+    /**
+     * Gets bin version.
+     * @param {string} bin - The bin to check.
+     * @returns {string} The normalized version returned by the command.
+     */
+    function getBinVersion(bin) {
+        const binArgs = ["--version"];
+
+        try {
+            return normalizeVersionStr(execCommand(bin, binArgs));
+        } catch (e) {
+            log.error(`Error finding ${bin} version running the command \`${bin} ${binArgs.join(" ")}\``);
+            throw e;
+        }
+    }
+
+    /**
+     * Gets installed npm package version.
+     * @param {string} pkg - The package to check.
+     * @param {boolean} global - Whether to check globally or not.
+     * @returns {string} The normalized version returned by the command.
+     */
+    function getNpmPackageVersion(pkg, { global = false } = {}) {
+        const npmBinArgs = ["bin", "-g"];
+        const npmLsArgs = ["ls", "--depth=0", "--json", "eslint"];
+
+        if (global) {
+            npmLsArgs.push("-g");
+        }
+
+        try {
+            const parsedStdout = JSON.parse(execCommand("npm", npmLsArgs));
+
+            /*
+             * Checking globally returns an empty JSON object, while local checks
+             * include the name and version of the local project.
+             */
+            if (isEmpty(parsedStdout) || !(parsedStdout.dependencies && parsedStdout.dependencies.eslint)) {
+                return "Not found";
+            }
+
+            const [, processBinPath] = process.argv;
+            let npmBinPath;
+
+            try {
+                npmBinPath = execCommand("npm", npmBinArgs);
+            } catch (e) {
+                log.error(`Error finding npm binary path when running command \`npm ${npmBinArgs.join(" ")}\``);
+                throw e;
+            }
+
+            const isGlobal = isChildOfDirectory(npmBinPath, processBinPath);
+            let pkgVersion = parsedStdout.dependencies.eslint.version;
+
+            if ((global && isGlobal) || (!global && !isGlobal)) {
+                pkgVersion += " (Currently used)";
+            }
+
+            return normalizeVersionStr(pkgVersion);
+        } catch (e) {
+            log.error(`Error finding ${pkg} version running the command \`npm ${npmLsArgs.join(" ")}\``);
+            throw e;
+        }
+    }
+
+    return [
+        "Environment Info:",
+        "",
+        `Node version: ${getBinVersion("node")}`,
+        `npm version: ${getBinVersion("npm")}`,
+        `Local ESLint version: ${getNpmPackageVersion("eslint", { global: false })}`,
+        `Global ESLint version: ${getNpmPackageVersion("eslint", { global: true })}`
+    ].join("\n");
+}
+
+/**
+ * Returns version of currently executing ESLint.
+ * @returns {string} The version from the currently executing ESLint's package.json.
+ */
+function version() {
+    return `v${packageJson.version}`;
+}
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+module.exports = {
+    environment,
+    version
+};
diff --git a/lib/source-code/source-code.js b/lib/source-code/source-code.js
index 88a9c0a4e..42e7b0c2f 100644
--- a/lib/source-code/source-code.js
+++ b/lib/source-code/source-code.js
@@ -93,7 +93,6 @@ class SourceCode extends TokenStore {
      * @param {ScopeManager|null} textOrConfig.scopeManager - The scope of this source code.
      * @param {Object|null} textOrConfig.visitorKeys - The visitor keys to traverse AST.
      * @param {ASTNode} [astIfNoConfig] - The Program node of the AST representing the code. This AST should be created from the text that BOM was stripped.
-     * @constructor
      */
     constructor(textOrConfig, astIfNoConfig) {
         let text, ast, parserServices, scopeManager, visitorKeys;
@@ -206,9 +205,9 @@ class SourceCode extends TokenStore {
 
     /**
      * Gets the source code for the given node.
-     * @param {ASTNode=} node The AST node to get the text for.
-     * @param {int=} beforeCount The number of characters before the node to retrieve.
-     * @param {int=} afterCount The number of characters after the node to retrieve.
+     * @param {ASTNode} [node] The AST node to get the text for.
+     * @param {int} [beforeCount] The number of characters before the node to retrieve.
+     * @param {int} [afterCount] The number of characters after the node to retrieve.
      * @returns {string} The text representing the AST node.
      * @public
      */
diff --git a/package.json b/package.json
index 9ce1c59b0..e1fb731c1 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,7 @@
   "scripts": {
     "test": "node Makefile.js test",
     "lint": "node Makefile.js lint",
+    "fix": "node Makefile.js lint -- fix",
     "fuzz": "node Makefile.js fuzz",
     "generate-release": "node Makefile.js generateRelease",
     "generate-alpharelease": "node Makefile.js generatePrerelease -- alpha",
@@ -96,6 +97,7 @@
     "eslint-config-eslint": "file:packages/eslint-config-eslint",
     "eslint-plugin-eslint-plugin": "^2.0.1",
     "eslint-plugin-internal-rules": "file:tools/internal-rules",
+    "eslint-plugin-jsdoc": "^15.9.5",
     "eslint-plugin-node": "^9.0.0",
     "eslint-release": "^1.2.0",
     "eslump": "^2.0.0",
diff --git a/packages/eslint-config-eslint/default.yml b/packages/eslint-config-eslint/default.yml
index 526ce5600..1bbc1c576 100644
--- a/packages/eslint-config-eslint/default.yml
+++ b/packages/eslint-config-eslint/default.yml
@@ -1,6 +1,16 @@
 extends:
     - "eslint:recommended"
     - "plugin:node/recommended"
+plugins:
+    - "jsdoc"
+settings:
+    jsdoc:
+        tagNamePreference:
+            file: "fileoverview"
+            augments: "extends"
+            class: "constructor"
+        preferredTypes:
+            object: "Object"
 rules:
     array-bracket-spacing: "error"
     array-callback-return: "error"
@@ -31,6 +41,20 @@ rules:
     generator-star-spacing: "error"
     guard-for-in: "error"
     handle-callback-err: ["error", "err"]
+    jsdoc/check-alignment: "error"
+    jsdoc/check-param-names: "error"
+    jsdoc/check-syntax: "error"
+    jsdoc/check-tag-names: "error"
+    jsdoc/check-types: "error"
+    jsdoc/implements-on-classes: "error"
+    jsdoc/require-jsdoc: "error"
+    jsdoc/require-param: "error"
+    jsdoc/require-param-description: "error"
+    jsdoc/require-param-name: "error"
+    jsdoc/require-param-type: "error"
+    jsdoc/require-returns: ["error", { forceRequireReturn: true, forceReturnsWithAsync: true }]
+    jsdoc/require-returns-description: "error"
+    jsdoc/require-returns-type: "error"
     key-spacing: ["error", { beforeColon: false, afterColon: true }]
     keyword-spacing: "error"
     lines-around-comment: ["error", {
@@ -154,7 +178,6 @@ rules:
     quotes: ["error", "double", {avoidEscape: true}]
     quote-props: ["error", "as-needed"]
     radix: "error"
-    require-jsdoc: "error"
     require-unicode-regexp: "error"
     rest-spread-spacing: "error"
     semi: "error"
@@ -172,17 +195,6 @@ rules:
     template-curly-spacing: ["error", "never"]
     template-tag-spacing: "error"
     unicode-bom: "error"
-    valid-jsdoc: ["error", {
-        prefer: { "return": "returns"},
-        preferType: {
-            "String": "string",
-            "Number": "number",
-            "Boolean": "boolean",
-            "array": "Array",
-            "object": "Object",
-            "function": "Function"
-        }
-    }]
     wrap-iife: "error"
     yield-star-spacing: "error"
     yoda: ["error", "never"]
diff --git a/packages/eslint-config-eslint/package.json b/packages/eslint-config-eslint/package.json
index 514dbb7ca..48bfb65f5 100644
--- a/packages/eslint-config-eslint/package.json
+++ b/packages/eslint-config-eslint/package.json
@@ -20,6 +20,7 @@
   "homepage": "https://eslint.org",
   "bugs": "https://github.com/eslint/eslint/issues/",
   "peerDependencies": {
+    "eslint-plugin-jsdoc": "^15.9.5",
     "eslint-plugin-node": "^9.0.0"
   },
   "keywords": [
diff --git a/tools/code-sample-minimizer.js b/tools/code-sample-minimizer.js
index 63a687e56..2954bf08f 100644
--- a/tools/code-sample-minimizer.js
+++ b/tools/code-sample-minimizer.js
@@ -30,6 +30,7 @@ function isStatement(node) {
  * Given "bad" source text (e.g. an code sample that causes a rule to crash), tries to return a smaller
  * piece of source text which is also "bad", to make it easier for a human to figure out where the
  * problem is.
+ * @param {Object} options Options to process
  * @param {string} options.sourceText Initial piece of "bad" source text
  * @param {function(string): boolean} options.predicate A predicate that returns `true` for bad source text and `false` for good source text
  * @param {Parser} [options.parser] The parser used to parse the source text. Defaults to a modified
diff --git a/tools/internal-rules/no-invalid-meta.js b/tools/internal-rules/no-invalid-meta.js
index aeb7862fa..016643a8d 100644
--- a/tools/internal-rules/no-invalid-meta.js
+++ b/tools/internal-rules/no-invalid-meta.js
@@ -104,7 +104,6 @@ function hasMetaSchema(metaPropertyNode) {
  *
  * @param {RuleContext} context The ESLint rule context.
  * @param {ASTNode} exportsNode ObjectExpression node that the rule exports.
- * @param {boolean} ruleIsFixable whether the rule is fixable or not.
  * @returns {void}
  */
 function checkMetaValidity(context, exportsNode) {


Test patch:
diff --git a/lib/rule-tester/rule-tester.js b/lib/rule-tester/rule-tester.js
index 0d1319c11..83b91c45d 100644
--- a/lib/rule-tester/rule-tester.js
+++ b/lib/rule-tester/rule-tester.js
@@ -177,7 +177,6 @@ class RuleTester {
     /**
      * Creates a new instance of RuleTester.
      * @param {Object} [testerConfig] Optional, extra configuration for the tester
-     * @constructor
      */
     constructor(testerConfig) {
 
diff --git a/tests/bin/eslint.js b/tests/bin/eslint.js
index b6254fa5f..932dd9ec4 100644
--- a/tests/bin/eslint.js
+++ b/tests/bin/eslint.js
@@ -163,6 +163,15 @@ describe("bin/eslint.js", () => {
                 return assertExitCode(child, 0);
             });
         });
+
+        it("successfully handles more than 4k data via stdin", () => {
+            const child = runESLint(["--stdin", "--no-eslintrc"]);
+            const large = fs.createReadStream(`${__dirname}/../bench/large.js`, "utf8");
+
+            large.pipe(child.stdin);
+
+            return assertExitCode(child, 0);
+        });
     });
 
     describe("running on files", () => {
diff --git a/tests/lib/_utils.js b/tests/lib/_utils.js
index 3251a6023..2ac119fb7 100644
--- a/tests/lib/_utils.js
+++ b/tests/lib/_utils.js
@@ -25,7 +25,6 @@ function unIndent(strings, ...values) {
     return lines.map(line => line.slice(minLineIndent)).join("\n");
 }
 
-// eslint-disable-next-line valid-jsdoc
 /**
  * Add support of `recursive` option.
  * @param {import("fs")} fs The in-memory file system.
@@ -56,7 +55,6 @@ function supportMkdirRecursiveOption(fs, cwd) {
     };
 }
 
-// eslint-disable-next-line valid-jsdoc
 /**
  * Define in-memory file system.
  * @param {Object} options The options.
diff --git a/tests/lib/cli-engine/_utils.js b/tests/lib/cli-engine/_utils.js
index a3c56484c..bff1801f3 100644
--- a/tests/lib/cli-engine/_utils.js
+++ b/tests/lib/cli-engine/_utils.js
@@ -53,9 +53,6 @@
  */
 "use strict";
 
-// To use TypeScript type annotations for VSCode intellisense.
-/* eslint-disable valid-jsdoc */
-
 const path = require("path");
 const vm = require("vm");
 const Proxyquire = require("proxyquire/lib/proxyquire");
diff --git a/tests/lib/cli-engine/cascading-config-array-factory.js b/tests/lib/cli-engine/cascading-config-array-factory.js
index e0ca4f063..22c9b956d 100644
--- a/tests/lib/cli-engine/cascading-config-array-factory.js
+++ b/tests/lib/cli-engine/cascading-config-array-factory.js
@@ -85,6 +85,7 @@ describe("CascadingConfigArrayFactory", () => {
 
             /**
              * Returns the path inside of the fixture directory.
+             * @param {...string} args file path segments.
              * @returns {string} The path inside the fixture directory.
              * @private
              */
@@ -671,6 +672,7 @@ describe("CascadingConfigArrayFactory", () => {
 
                 /**
                  * Returns the path inside of the fixture directory.
+                 * @param {...string} args file path segments.
                  * @returns {string} The path inside the fixture directory.
                  * @private
                  */
@@ -766,6 +768,7 @@ describe("CascadingConfigArrayFactory", () => {
 
                 /**
                  * Returns the path inside of the fixture directory.
+                 * @param {...string} args file path segments.
                  * @returns {string} The path inside the fixture directory.
                  * @private
                  */
diff --git a/tests/lib/cli-engine/cli-engine.js b/tests/lib/cli-engine/cli-engine.js
index b3039e743..223cb6607 100644
--- a/tests/lib/cli-engine/cli-engine.js
+++ b/tests/lib/cli-engine/cli-engine.js
@@ -50,6 +50,7 @@ describe("CLIEngine", () => {
 
     /**
      * Returns the path inside of the fixture directory.
+     * @param {...string} args file path segments.
      * @returns {string} The path inside the fixture directory.
      * @private
      */
@@ -1543,7 +1544,11 @@ describe("CLIEngine", () => {
             engine = new CLIEngine({
                 cwd: originalDir,
                 configFile: ".eslintrc.js",
-                rules: { "indent-legacy": 1 }
+                rules: {
+                    "indent-legacy": 1,
+                    "require-jsdoc": 1,
+                    "valid-jsdoc": 1
+                }
             });
 
             const report = engine.executeOnFiles(["lib/cli*.js"]);
diff --git a/tests/lib/cli-engine/file-enumerator.js b/tests/lib/cli-engine/file-enumerator.js
index 13ff479db..d379ac520 100644
--- a/tests/lib/cli-engine/file-enumerator.js
+++ b/tests/lib/cli-engine/file-enumerator.js
@@ -173,6 +173,7 @@ describe("FileEnumerator", () => {
 
             /**
              * Returns the path inside of the fixture directory.
+             * @param {...string} args file path segments.
              * @returns {string} The path inside the fixture directory.
              * @private
              */
diff --git a/tests/lib/cli-engine/ignored-paths.js b/tests/lib/cli-engine/ignored-paths.js
index 487024427..7fd8e8408 100644
--- a/tests/lib/cli-engine/ignored-paths.js
+++ b/tests/lib/cli-engine/ignored-paths.js
@@ -92,6 +92,7 @@ function countDefaultPatterns(ignoredPaths) {
 
 /**
  * Returns the path inside of the fixture directory.
+ * @param {...string} args file path segments.
  * @returns {string} The path inside the fixture directory.
  * @private
  */
diff --git a/tests/lib/cli.js b/tests/lib/cli.js
index baae75b53..e0f10d5ae 100644
--- a/tests/lib/cli.js
+++ b/tests/lib/cli.js
@@ -29,14 +29,18 @@ const proxyquire = require("proxyquire").noCallThru().noPreserveCache();
 //------------------------------------------------------------------------------
 
 describe("cli", () => {
-
     let fixtureDir;
     const log = {
         info: sinon.spy(),
         error: sinon.spy()
     };
+    const RuntimeInfo = {
+        environment: sinon.stub(),
+        version: sinon.stub()
+    };
     const cli = proxyquire("../../lib/cli", {
-        "./shared/logging": log
+        "./shared/logging": log,
+        "./shared/runtime-info": RuntimeInfo
     });
 
     /**
@@ -67,6 +71,7 @@ describe("cli", () => {
 
     /**
      * Returns the path inside of the fixture directory.
+     * @param {...string} args file path segments.
      * @returns {string} The path inside the fixture directory.
      * @private
      */
@@ -324,15 +329,27 @@ describe("cli", () => {
     describe("when executing with version flag", () => {
         it("should print out current version", () => {
             assert.strictEqual(cli.execute("-v"), 0);
+            assert.strictEqual(log.info.callCount, 1);
+        });
+    });
 
+    describe("when executing with env-info flag", () => {
+        it("should print out environment information", () => {
+            assert.strictEqual(cli.execute("--env-info"), 0);
             assert.strictEqual(log.info.callCount, 1);
         });
+
+        it("should print error message and return error code", () => {
+            RuntimeInfo.environment.throws("There was an error!");
+
+            assert.strictEqual(cli.execute("--env-info"), 2);
+            assert.strictEqual(log.error.callCount, 1);
+        });
     });
 
     describe("when executing with help flag", () => {
         it("should print out help", () => {
             assert.strictEqual(cli.execute("-h"), 0);
-
             assert.strictEqual(log.info.callCount, 1);
         });
     });
@@ -349,7 +366,6 @@ describe("cli", () => {
     });
 
     describe("when given a file in excluded files list", () => {
-
         it("should not process the file", () => {
             const ignorePath = getFixturePath(".eslintignore");
             const filePath = getFixturePath("passing.js");
@@ -398,7 +414,6 @@ describe("cli", () => {
     });
 
     describe("when executing a file with a shebang", () => {
-
         it("should execute without error", () => {
             const filePath = getFixturePath("shebang.js");
             const exit = cli.execute(`--no-ignore ${filePath}`);
@@ -408,7 +423,6 @@ describe("cli", () => {
     });
 
     describe("when loading a custom rule", () => {
-
         it("should return an error when rule isn't found", () => {
             const rulesPath = getFixturePath("rules", "wrong");
             const configPath = getFixturePath("rules", "eslint.json");
@@ -551,7 +565,6 @@ describe("cli", () => {
     });
 
     describe("when supplied with report output file path", () => {
-
         afterEach(() => {
             sh.rm("-rf", "tests/output");
         });
@@ -594,7 +607,6 @@ describe("cli", () => {
     });
 
     describe("when supplied with a plugin", () => {
-
         it("should pass plugins to CLIEngine", () => {
             const examplePluginName = "eslint-plugin-example";
 
diff --git a/tests/lib/init/config-initializer.js b/tests/lib/init/config-initializer.js
index ed2388fd5..3393ae2be 100644
--- a/tests/lib/init/config-initializer.js
+++ b/tests/lib/init/config-initializer.js
@@ -57,6 +57,7 @@ describe("configInitializer", () => {
 
     /**
      * Returns the path inside of the fixture directory.
+     * @param {...string} args file path segments.
      * @returns {string} The path inside the fixture directory.
      * @private
      */
diff --git a/tests/lib/init/source-code-utils.js b/tests/lib/init/source-code-utils.js
index a5c9e90a1..8c821de78 100644
--- a/tests/lib/init/source-code-utils.js
+++ b/tests/lib/init/source-code-utils.js
@@ -31,6 +31,7 @@ describe("SourceCodeUtil", () => {
 
     /**
      * Returns the path inside of the fixture directory.
+     * @param {...string} args file path segments.
      * @returns {string} The path inside the fixture directory.
      * @private
      */
diff --git a/tests/lib/rules/new-parens.js b/tests/lib/rules/new-parens.js
index c13cc7150..3508ace8e 100644
--- a/tests/lib/rules/new-parens.js
+++ b/tests/lib/rules/new-parens.js
@@ -119,6 +119,12 @@ ruleTester.run("new-parens", rule, {
             options: ["always"],
             errors: [error]
         },
+        {
+            code: "var a = new new Foo()",
+            output: "var a = new new Foo()()",
+            options: ["always"],
+            errors: [error]
+        },
 
         // Never
         {
@@ -168,6 +174,12 @@ ruleTester.run("new-parens", rule, {
             output: "var a = ((new Foo)).bar;",
             options: ["never"],
             errors: [neverError]
+        },
+        {
+            code: "var a = new new Foo()",
+            output: "var a = new (new Foo)",
+            options: ["never"],
+            errors: [neverError]
         }
     ]
 });
diff --git a/tests/lib/rules/no-extra-parens.js b/tests/lib/rules/no-extra-parens.js
index 788823e04..e42b5eadc 100644
--- a/tests/lib/rules/no-extra-parens.js
+++ b/tests/lib/rules/no-extra-parens.js
@@ -95,6 +95,17 @@ ruleTester.run("no-extra-parens", rule, {
         "new A()()",
         "(new A)()",
         "(new (Foo || Bar))()",
+        "(new new foo())()",
+        "new (new new foo())(bar)",
+        "(new foo).bar",
+        "(new foo)[bar]",
+        "(new foo).bar.baz",
+        "(new foo.bar).baz",
+        "(new foo).bar()",
+        "(new foo.bar).baz()",
+        "new (new foo).bar",
+        "new (new foo.bar).baz",
+        "(new new foo()).baz",
         "(2 + 3) ** 4",
         "2 ** (2 + 3)",
         "new (import(source))",
@@ -647,6 +658,11 @@ ruleTester.run("no-extra-parens", rule, {
         invalid("(foo()).bar", "foo().bar", "CallExpression"),
         invalid("(foo.bar()).baz", "foo.bar().baz", "CallExpression"),
         invalid("(foo\n.bar())\n.baz", "foo\n.bar()\n.baz", "CallExpression"),
+        invalid("(new foo()).bar", "new foo().bar", "NewExpression"),
+        invalid("(new foo()).bar()", "new foo().bar()", "NewExpression"),
+        invalid("(new foo(bar)).baz", "new foo(bar).baz", "NewExpression"),
+        invalid("(new foo.bar()).baz", "new foo.bar().baz", "NewExpression"),
+        invalid("(new foo.bar()).baz()", "new foo.bar().baz()", "NewExpression"),
 
         invalid("new (A)", "new A", "Identifier"),
         invalid("(new A())()", "new A()()", "NewExpression"),
diff --git a/tests/lib/rules/no-undef-init.js b/tests/lib/rules/no-undef-init.js
index efbbbe4c4..509de0110 100644
--- a/tests/lib/rules/no-undef-init.js
+++ b/tests/lib/rules/no-undef-init.js
@@ -92,6 +92,62 @@ ruleTester.run("no-undef-init", rule, {
             output: "for(var i in [1,2,3]){let a; for(var j in [1,2,3]){}}",
             parserOptions: { ecmaVersion: 6 },
             errors: [{ message: "It's not necessary to initialize 'a' to undefined.", type: "VariableDeclarator" }]
+        },
+
+        // Should not autofix if it would remove comments
+        {
+            code: "let /* comment */a = undefined;",
+            output: "let /* comment */a;",
+            parserOptions: { ecmaVersion: 6 },
+            errors: [{ message: "It's not necessary to initialize 'a' to undefined.", type: "VariableDeclarator" }]
+        },
+        {
+            code: "let a/**/ = undefined;",
+            output: null,
+            parserOptions: { ecmaVersion: 6 },
+            errors: [{ message: "It's not necessary to initialize 'a' to undefined.", type: "VariableDeclarator" }]
+        },
+        {
+            code: "let a /**/ = undefined;",
+            output: null,
+            parserOptions: { ecmaVersion: 6 },
+            errors: [{ message: "It's not necessary to initialize 'a' to undefined.", type: "VariableDeclarator" }]
+        },
+        {
+            code: "let a//\n= undefined;",
+            output: null,
+            parserOptions: { ecmaVersion: 6 },
+            errors: [{ message: "It's not necessary to initialize 'a' to undefined.", type: "VariableDeclarator" }]
+        },
+        {
+            code: "let a = /**/undefined;",
+            output: null,
+            parserOptions: { ecmaVersion: 6 },
+            errors: [{ message: "It's not necessary to initialize 'a' to undefined.", type: "VariableDeclarator" }]
+        },
+        {
+            code: "let a = //\nundefined;",
+            output: null,
+            parserOptions: { ecmaVersion: 6 },
+            errors: [{ message: "It's not necessary to initialize 'a' to undefined.", type: "VariableDeclarator" }]
+        },
+        {
+            code: "let a = undefined/* comment */;",
+            output: "let a/* comment */;",
+            parserOptions: { ecmaVersion: 6 },
+            errors: [{ message: "It's not necessary to initialize 'a' to undefined.", type: "VariableDeclarator" }]
+        },
+        {
+            code: "let a = undefined/* comment */, b;",
+            output: "let a/* comment */, b;",
+            parserOptions: { ecmaVersion: 6 },
+            errors: [{ message: "It's not necessary to initialize 'a' to undefined.", type: "VariableDeclarator" }]
+        },
+        {
+            code: "let a = undefined//comment\n, b;",
+            output: "let a//comment\n, b;",
+            parserOptions: { ecmaVersion: 6 },
+            errors: [{ message: "It's not necessary to initialize 'a' to undefined.", type: "VariableDeclarator" }]
         }
     ]
 });
diff --git a/tests/lib/rules/no-useless-rename.js b/tests/lib/rules/no-useless-rename.js
index 1fcc843db..c9be46d69 100644
--- a/tests/lib/rules/no-useless-rename.js
+++ b/tests/lib/rules/no-useless-rename.js
@@ -182,6 +182,21 @@ ruleTester.run("no-useless-rename", rule, {
             output: "let {'foo': {bar}, baz} = obj;",
             errors: ["Destructuring assignment bar unnecessarily renamed.", "Destructuring assignment baz unnecessarily renamed."]
         },
+        {
+            code: "let {foo: foo = 1, 'bar': bar = 1, baz: baz} = obj;",
+            output: "let {foo = 1, bar = 1, baz} = obj;",
+            errors: ["Destructuring assignment foo unnecessarily renamed.", "Destructuring assignment bar unnecessarily renamed.", "Destructuring assignment baz unnecessarily renamed."]
+        },
+        {
+            code: "let {foo: {bar: bar = 1, 'baz': baz = 1}} = obj;",
+            output: "let {foo: {bar = 1, baz = 1}} = obj;",
+            errors: ["Destructuring assignment bar unnecessarily renamed.", "Destructuring assignment baz unnecessarily renamed."]
+        },
+        {
+            code: "let {foo: {bar: bar = {}} = {}} = obj;",
+            output: "let {foo: {bar = {}} = {}} = obj;",
+            errors: ["Destructuring assignment bar unnecessarily renamed."]
+        },
         {
             code: "function func({foo: foo}) {}",
             output: "function func({foo}) {}",
@@ -202,6 +217,21 @@ ruleTester.run("no-useless-rename", rule, {
             output: "function func({foo, bar}) {}",
             errors: ["Destructuring assignment foo unnecessarily renamed.", "Destructuring assignment bar unnecessarily renamed."]
         },
+        {
+            code: "function func({foo: foo = 1, 'bar': bar = 1, baz: baz}) {}",
+            output: "function func({foo = 1, bar = 1, baz}) {}",
+            errors: ["Destructuring assignment foo unnecessarily renamed.", "Destructuring assignment bar unnecessarily renamed.", "Destructuring assignment baz unnecessarily renamed."]
+        },
+        {
+            code: "function func({foo: {bar: bar = 1, 'baz': baz = 1}}) {}",
+            output: "function func({foo: {bar = 1, baz = 1}}) {}",
+            errors: ["Destructuring assignment bar unnecessarily renamed.", "Destructuring assignment baz unnecessarily renamed."]
+        },
+        {
+            code: "function func({foo: {bar: bar = {}} = {}}) {}",
+            output: "function func({foo: {bar = {}} = {}}) {}",
+            errors: ["Destructuring assignment bar unnecessarily renamed."]
+        },
         {
             code: "({foo: foo}) => {}",
             output: "({foo}) => {}",
@@ -222,6 +252,21 @@ ruleTester.run("no-useless-rename", rule, {
             output: "({foo, bar}) => {}",
             errors: ["Destructuring assignment foo unnecessarily renamed.", "Destructuring assignment bar unnecessarily renamed."]
         },
+        {
+            code: "({foo: foo = 1, 'bar': bar = 1, baz: baz}) => {}",
+            output: "({foo = 1, bar = 1, baz}) => {}",
+            errors: ["Destructuring assignment foo unnecessarily renamed.", "Destructuring assignment bar unnecessarily renamed.", "Destructuring assignment baz unnecessarily renamed."]
+        },
+        {
+            code: "({foo: {bar: bar = 1, 'baz': baz = 1}}) => {}",
+            output: "({foo: {bar = 1, baz = 1}}) => {}",
+            errors: ["Destructuring assignment bar unnecessarily renamed.", "Destructuring assignment baz unnecessarily renamed."]
+        },
+        {
+            code: "({foo: {bar: bar = {}} = {}}) => {}",
+            output: "({foo: {bar = {}} = {}}) => {}",
+            errors: ["Destructuring assignment bar unnecessarily renamed."]
+        },
         {
             code: "const {foo: foo, ...stuff} = myObject;",
             output: "const {foo, ...stuff} = myObject;",
@@ -299,6 +344,133 @@ ruleTester.run("no-useless-rename", rule, {
             code: "export {foo as foo, bar as bar} from 'foo';",
             output: "export {foo, bar} from 'foo';",
             errors: ["Export foo unnecessarily renamed.", "Export bar unnecessarily renamed."]
+        },
+
+        // Should not autofix if it would remove comments
+        {
+            code: "({/* comment */foo: foo} = {});",
+            output: "({/* comment */foo} = {});",
+            errors: ["Destructuring assignment foo unnecessarily renamed."]
+        },
+        {
+            code: "({foo, /* comment */bar: bar} = {});",
+            output: "({foo, /* comment */bar} = {});",
+            errors: ["Destructuring assignment bar unnecessarily renamed."]
+        },
+        {
+            code: "({foo/**/ : foo} = {});",
+            output: null,
+            errors: ["Destructuring assignment foo unnecessarily renamed."]
+        },
+        {
+            code: "({foo /**/: foo} = {});",
+            output: null,
+            errors: ["Destructuring assignment foo unnecessarily renamed."]
+        },
+        {
+            code: "({foo://\nfoo} = {});",
+            output: null,
+            errors: ["Destructuring assignment foo unnecessarily renamed."]
+        },
+        {
+            code: "({foo: /**/foo} = {});",
+            output: null,
+            errors: ["Destructuring assignment foo unnecessarily renamed."]
+        },
+        {
+            code: "({foo: foo/* comment */} = {});",
+            output: "({foo/* comment */} = {});",
+            errors: ["Destructuring assignment foo unnecessarily renamed."]
+        },
+        {
+            code: "({foo: foo//comment\n,bar} = {});",
+            output: "({foo//comment\n,bar} = {});",
+            errors: ["Destructuring assignment foo unnecessarily renamed."]
+        },
+        {
+            code: "import {/* comment */foo as foo} from 'foo';",
+            output: "import {/* comment */foo} from 'foo';",
+            errors: ["Import foo unnecessarily renamed."]
+        },
+        {
+            code: "import {foo,/* comment */bar as bar} from 'foo';",
+            output: "import {foo,/* comment */bar} from 'foo';",
+            errors: ["Import bar unnecessarily renamed."]
+        },
+        {
+            code: "import {foo/**/ as foo} from 'foo';",
+            output: null,
+            errors: ["Import foo unnecessarily renamed."]
+        },
+        {
+            code: "import {foo /**/as foo} from 'foo';",
+            output: null,
+            errors: ["Import foo unnecessarily renamed."]
+        },
+        {
+            code: "import {foo //\nas foo} from 'foo';",
+            output: null,
+            errors: ["Import foo unnecessarily renamed."]
+        },
+        {
+            code: "import {foo as/**/foo} from 'foo';",
+            output: null,
+            errors: ["Import foo unnecessarily renamed."]
+        },
+        {
+            code: "import {foo as foo/* comment */} from 'foo';",
+            output: "import {foo/* comment */} from 'foo';",
+            errors: ["Import foo unnecessarily renamed."]
+        },
+        {
+            code: "import {foo as foo/* comment */,bar} from 'foo';",
+            output: "import {foo/* comment */,bar} from 'foo';",
+            errors: ["Import foo unnecessarily renamed."]
+        },
+        {
+            code: "let foo; export {/* comment */foo as foo};",
+            output: "let foo; export {/* comment */foo};",
+            errors: ["Export foo unnecessarily renamed."]
+        },
+        {
+            code: "let foo, bar; export {foo,/* comment */bar as bar};",
+            output: "let foo, bar; export {foo,/* comment */bar};",
+            errors: ["Export bar unnecessarily renamed."]
+        },
+        {
+            code: "let foo; export {foo/**/as foo};",
+            output: null,
+            errors: ["Export foo unnecessarily renamed."]
+        },
+        {
+            code: "let foo; export {foo as/**/ foo};",
+            output: null,
+            errors: ["Export foo unnecessarily renamed."]
+        },
+        {
+            code: "let foo; export {foo as /**/foo};",
+            output: null,
+            errors: ["Export foo unnecessarily renamed."]
+        },
+        {
+            code: "let foo; export {foo as//comment\n foo};",
+            output: null,
+            errors: ["Export foo unnecessarily renamed."]
+        },
+        {
+            code: "let foo; export {foo as foo/* comment*/};",
+            output: "let foo; export {foo/* comment*/};",
+            errors: ["Export foo unnecessarily renamed."]
+        },
+        {
+            code: "let foo, bar; export {foo as foo/* comment*/,bar};",
+            output: "let foo, bar; export {foo/* comment*/,bar};",
+            errors: ["Export foo unnecessarily renamed."]
+        },
+        {
+            code: "let foo, bar; export {foo as foo//comment\n,bar};",
+            output: "let foo, bar; export {foo//comment\n,bar};",
+            errors: ["Export foo unnecessarily renamed."]
         }
     ]
 });
diff --git a/tests/lib/rules/prefer-numeric-literals.js b/tests/lib/rules/prefer-numeric-literals.js
index e84017490..800047051 100644
--- a/tests/lib/rules/prefer-numeric-literals.js
+++ b/tests/lib/rules/prefer-numeric-literals.js
@@ -90,6 +90,68 @@ ruleTester.run("prefer-numeric-literals", rule, {
             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()." }]
+        },
+
+        // Should not autofix if it would remove comments
+        {
+            code: "/* comment */Number.parseInt('11', 2);",
+            output: "/* comment */0b11;",
+            errors: 1
+        },
+        {
+            code: "Number/**/.parseInt('11', 2);",
+            output: null,
+            errors: 1
+        },
+        {
+            code: "Number//\n.parseInt('11', 2);",
+            output: null,
+            errors: 1
+        },
+        {
+            code: "Number./**/parseInt('11', 2);",
+            output: null,
+            errors: 1
+        },
+        {
+            code: "Number.parseInt(/**/'11', 2);",
+            output: null,
+            errors: 1
+        },
+        {
+            code: "Number.parseInt('11', /**/2);",
+            output: null,
+            errors: 1
+        },
+        {
+            code: "Number.parseInt('11', 2)/* comment */;",
+            output: "0b11/* comment */;",
+            errors: 1
+        },
+        {
+            code: "parseInt/**/('11', 2);",
+            output: null,
+            errors: 1
+        },
+        {
+            code: "parseInt(//\n'11', 2);",
+            output: null,
+            errors: 1
+        },
+        {
+            code: "parseInt('11'/**/, 2);",
+            output: null,
+            errors: 1
+        },
+        {
+            code: "parseInt('11', 2 /**/);",
+            output: null,
+            errors: 1
+        },
+        {
+            code: "parseInt('11', 2)//comment\n;",
+            output: "0b11//comment\n;",
+            errors: 1
         }
     ]
 });
diff --git a/tests/lib/shared/runtime-info.js b/tests/lib/shared/runtime-info.js
new file mode 100644
index 000000000..c82d416df
--- /dev/null
+++ b/tests/lib/shared/runtime-info.js
@@ -0,0 +1,216 @@
+/**
+ * @fileoverview Tests for RuntimeInfo util.
+ * @author Kai Cataldo
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const assert = require("chai").assert;
+const sinon = require("sinon");
+const spawn = require("cross-spawn");
+const { unIndent } = require("../_utils");
+const RuntimeInfo = require("../../../lib/shared/runtime-info");
+const log = require("../../../lib/shared/logging");
+const packageJson = require("../../../package.json");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Sets up spawn.sync() stub calls to return values and throw errors in the order in which they are given.
+ * @param {Function} stub - The stub to set up.
+ * @param {Array} returnVals - Values to be returned by subsequent stub calls.
+ * @returns {Function} The set up stub.
+ */
+function setupSpawnSyncStubReturnVals(stub, returnVals) {
+    let stubChain = stub;
+
+    for (const [i, val] of returnVals.entries()) {
+        const returnVal = val instanceof Error
+            ? { error: val }
+            : { stdout: val };
+
+        stubChain = stubChain.onCall(i).returns(returnVal);
+    }
+
+    return stubChain;
+}
+
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
+const LOCAL_ESLINT_BIN_PATH = "/Users/username/code/project/node_modules/eslint/bin/eslint.js";
+const GLOBAL_ESLINT_BIN_PATH = "/usr/local/bin/npm/node_modules/eslint/bin/eslint.js";
+const NPM_BIN_PATH = "/usr/local/bin/npm";
+
+describe("RuntimeInfo", () => {
+    describe("environment()", () => {
+        let spawnSyncStub;
+        let logErrorStub;
+        let originalProcessArgv;
+        let spawnSyncStubArgs;
+
+        beforeEach(() => {
+            spawnSyncStub = sinon.stub(spawn, "sync");
+            logErrorStub = sinon.stub(log, "error");
+            originalProcessArgv = process.argv;
+            process.argv[1] = LOCAL_ESLINT_BIN_PATH;
+            spawnSyncStubArgs = [
+                "v12.8.0",
+                "6.11.3",
+                unIndent`
+                    {
+                        "name": "project",
+                        "version": "1.0.0",
+                        "dependencies": {
+                            "eslint": {
+                                "version": "6.3.0"
+                            }
+                        }
+                    }
+                `,
+                NPM_BIN_PATH,
+                unIndent`
+                    {
+                        "dependencies": {
+                            "eslint": {
+                                "version": "5.16.0",
+                                "from": "eslint",
+                                "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.16.0.tgz"
+                            }
+                        }
+                    }
+                `
+            ];
+        });
+
+        afterEach(() => {
+            spawnSyncStub.restore();
+            logErrorStub.restore();
+            process.argv = originalProcessArgv;
+        });
+
+
+        it("should return a string containing environment information when running local installation", () => {
+            setupSpawnSyncStubReturnVals(spawnSyncStub, spawnSyncStubArgs);
+
+            assert.strictEqual(
+                RuntimeInfo.environment(),
+                unIndent`
+                    Environment Info:
+
+                    Node version: v12.8.0
+                    npm version: v6.11.3
+                    Local ESLint version: v6.3.0 (Currently used)
+                    Global ESLint version: v5.16.0
+                `
+            );
+        });
+
+        it("should return a string containing environment information when running global installation", () => {
+            setupSpawnSyncStubReturnVals(spawnSyncStub, spawnSyncStubArgs);
+            process.argv[1] = GLOBAL_ESLINT_BIN_PATH;
+
+            assert.strictEqual(
+                RuntimeInfo.environment(),
+                unIndent`
+                    Environment Info:
+
+                    Node version: v12.8.0
+                    npm version: v6.11.3
+                    Local ESLint version: v6.3.0
+                    Global ESLint version: v5.16.0 (Currently used)
+                `
+            );
+        });
+
+        it("should return a string containing environment information when not installed locally", () => {
+            spawnSyncStubArgs.splice(2, 2, unIndent`
+                {
+                    "name": "project",
+                    "version": "1.0.0"
+                }
+            `);
+            spawnSyncStubArgs.push(NPM_BIN_PATH);
+            setupSpawnSyncStubReturnVals(spawnSyncStub, spawnSyncStubArgs);
+            process.argv[1] = GLOBAL_ESLINT_BIN_PATH;
+
+            assert.strictEqual(
+                RuntimeInfo.environment(),
+                unIndent`
+                    Environment Info:
+
+                    Node version: v12.8.0
+                    npm version: v6.11.3
+                    Local ESLint version: Not found
+                    Global ESLint version: v5.16.0 (Currently used)
+                `
+            );
+        });
+
+        it("should return a string containing environment information when not installed globally", () => {
+            spawnSyncStubArgs[4] = "{}";
+            setupSpawnSyncStubReturnVals(spawnSyncStub, spawnSyncStubArgs);
+
+            assert.strictEqual(
+                RuntimeInfo.environment(),
+                unIndent`
+                    Environment Info:
+
+                    Node version: v12.8.0
+                    npm version: v6.11.3
+                    Local ESLint version: v6.3.0 (Currently used)
+                    Global ESLint version: Not found
+                `
+            );
+        });
+
+        it("log and throw an error when npm version can not be found", () => {
+            const expectedErr = new Error("npm can not be found");
+
+            spawnSyncStubArgs[1] = expectedErr;
+            setupSpawnSyncStubReturnVals(spawnSyncStub, spawnSyncStubArgs);
+
+            assert.throws(RuntimeInfo.environment, expectedErr);
+            assert.strictEqual(logErrorStub.args[0][0], "Error finding npm version running the command `npm --version`");
+        });
+
+        it("log and throw an error when npm binary path can not be found", () => {
+            const expectedErr = new Error("npm can not be found");
+
+            spawnSyncStubArgs[3] = expectedErr;
+            setupSpawnSyncStubReturnVals(spawnSyncStub, spawnSyncStubArgs);
+
+            assert.throws(RuntimeInfo.environment, expectedErr);
+            assert.strictEqual(logErrorStub.args[0][0], "Error finding npm binary path when running command `npm bin -g`");
+        });
+
+        it("log and throw an error when checking for local ESLint version when returned output of command is malformed", () => {
+            spawnSyncStubArgs[2] = "This is not JSON";
+            setupSpawnSyncStubReturnVals(spawnSyncStub, spawnSyncStubArgs);
+
+            assert.throws(RuntimeInfo.environment, "Unexpected token T in JSON at position 0");
+            assert.strictEqual(logErrorStub.args[0][0], "Error finding eslint version running the command `npm ls --depth=0 --json eslint`");
+        });
+
+        it("log and throw an error when checking for global ESLint version when returned output of command is malformed", () => {
+            spawnSyncStubArgs[4] = "This is not JSON";
+            setupSpawnSyncStubReturnVals(spawnSyncStub, spawnSyncStubArgs);
+
+            assert.throws(RuntimeInfo.environment, "Unexpected token T in JSON at position 0");
+            assert.strictEqual(logErrorStub.args[0][0], "Error finding eslint version running the command `npm ls --depth=0 --json eslint -g`");
+        });
+    });
+
+    describe("version()", () => {
+        it("should return the version of the package defined in package.json", () => {
+            assert.strictEqual(RuntimeInfo.version(), `v${packageJson.version}`);
+        });
+    });
+});
diff --git a/tests/tools/loose-parser.js b/tests/tools/loose-parser.js
index abb3aeb5b..98e97cc36 100644
--- a/tests/tools/loose-parser.js
+++ b/tests/tools/loose-parser.js
@@ -9,7 +9,6 @@
 const acorn = require("acorn");
 const espree = require("espree/lib/espree");
 
-// eslint-disable-next-line valid-jsdoc
 /**
  * Define the parser which ignores recoverable errors.
  * @returns {(parser:acorn.Parser) => acorn.Parser} The function that defines loose parser.