Repository: eslint/eslint (js)

Issue:
When a crash occurs, the error message is printed twice
<!--
    ESLint adheres to the [JS Foundation Code of Conduct](https://js.foundation/community/code-of-conduct).

    This template is for bug reports. If you are reporting a bug, please continue on. If you are here for another reason, please see below:

    1. To propose a new rule: http://eslint.org/docs/developer-guide/contributing/new-rules
    2. To request a change: http://eslint.org/docs/developer-guide/contributing/changes
    3. If you have any questions, please stop by our chatroom: https://gitter.im/eslint/eslint

    Note that leaving sections blank will make it difficult for us to troubleshoot and we may have to close the issue.
-->

**Tell us about your environment**

* **ESLint Version:** 4.2.0
* **Node Version:** 8.1.4
* **npm Version:** 5.0.3

**What parser (default, Babel-ESLint, etc.) are you using?**

default

**Please show your full configuration:**

<details>
<summary>Configuration</summary>

<!-- Paste your configuration below: -->
```yml
rules:
  no-restricted-syntax: [error, 'Invalid selector [[[[']
```

</details>

**What did you do? Please include the actual source code causing the issue.**

<!-- Paste the source code below: -->
```js
a;
```

**What did you expect to happen?**

The configuration isn't valid (it includes an invalid selector), so I expected ESLint to output an error message.

**What actually happened? Please include the actual, raw output from ESLint.**

ESLint outputted the same error message twice, once with the stack trace and once without:

<details>
<summary>Output</summary>

```
Syntax error in selector "Invalid selector [[[[" at position 18: Expected " ", "." or [^ [\],():#!=><~+.] but "[" found.
Error: Syntax error in selector "Invalid selector [[[[" at position 18: Expected " ", "." or [^ [\],():#!=><~+.] but "[" found.
    at tryParseSelector (/path/to/node_modules/eslint/lib/util/node-event-generator.js:163:19)
    at lodash.memoize.rawSelector (/path/to/node_modules/eslint/lib/util/node-event-generator.js:175:28)
    at memoized (/path/to/node_modules/lodash/lodash.js:10566:27)
    at NodeEventGenerator.eventNames.forEach.rawSelector (/path/to/node_modules/eslint/lib/util/node-event-generator.js:235:30)
    at Array.forEach (native)
    at new NodeEventGenerator (/path/to/node_modules/eslint/lib/util/node-event-generator.js:234:20)
    at Linter.verify (/path/to/node_modules/eslint/lib/linter.js:909:34)
    at processText (/path/to/node_modules/eslint/lib/cli-engine.js:203:31)
    at processFile (/path/to/node_modules/eslint/lib/cli-engine.js:245:18)
    at executeOnFile (/path/to/node_modules/eslint/lib/cli-engine.js:585:25)

```

</details>

This isn't specific to the `no-restricted-syntax` rule -- it seems to occur whenever any error is thrown from anywhere.

Pull request description:
Fix: duplicated error message if a crash occurs (fixes #8964)
<!--
    ESLint adheres to the [JS Foundation Code of Conduct](https://js.foundation/community/code-of-conduct).
-->

**What is the purpose of this pull request? (put an "X" next to item)**

[x] Bug fix (https://github.com/eslint/eslint/issues/8964)

<!--
    If the item you've checked above has a template, please paste the template questions below and answer them. (If this pull request is addressing an issue, you can just paste a link to the issue here instead.)
-->

<!--
    Please ensure your pull request is ready:

    - Read the pull request guide (http://eslint.org/docs/developer-guide/contributing/pull-requests)
    - Include tests for this change
    - Update documentation for this change (if appropriate)
-->

<!--
    The following is required for all pull requests:
-->

**What changes did you make? (Give an overview)**

This updates the uncaught exception handler to avoid printing the error message twice. The error message is included in the stack, so it's not necessary to print the error message and the stack separately.

**Is there anything you'd like reviewers to focus on?**

Nothing in particular


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/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"
     }]
 }
 ```


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.