Repository: eslint/eslint
Pull request #10393 (2018-05-23 01:01:04)

Fix: ensure --stdin flag works when stdin is piped asynchronously
<!--
    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

**Tell us about your environment**

* **ESLint Version:** master
* **Node Version:** 8.11.1
* **npm Version:** 5.6.0

**What parser (default, Babel-ESLint, etc.) are you using?**

N/A

**Please show your full configuration:**

N/A

**What did you do? Please include the actual source code causing the issue.**

This issue is simplest to reproduce with bash:

```bash
$ (echo 'a' && sleep 1 && echo 'b') | eslint --stdin --no-eslintrc
```

(I originally encountered this issue when using `curl` to fetch a JS file and pipe it to `eslint --stdin`.)

**What did you expect to happen?**

I expected ESLint to read stdin until the end (resulting in two lines of input), and then lint those two lines.

**What actually happened? Please include the actual, raw output from ESLint.**

An error was thrown:

```
EAGAIN: resource temporarily unavailable, read
Error: EAGAIN: resource temporarily unavailable, read
    at Object.fs.readSync (fs.js:675:18)
    at tryReadSync (fs.js:540:20)
    at Object.fs.readFileSync (fs.js:583:19)
    at Object.<anonymous> (/path/to/eslint/bin/eslint.js:59:53)
    at Module._compile (module.js:652:30)
    at Object.Module._extensions..js (module.js:663:10)
    at Module.load (module.js:565:32)
    at tryModuleLoad (module.js:505:12)
    at Function.Module._load (module.js:497:3)
    at Function.Module.runMain (module.js:693:10)
```

Based on https://github.com/nodejs/node/issues/7439, this probably only occurs on macOS.

**What changes did you make? (Give an overview)**

This updates `bin/eslint.js` to avoid accessing the `process.stdin` getter, which seems to change the state of file descriptor 0.

**Is there anything you'd like reviewers to focus on?**

Should this be filed as a Node bug instead? Based on https://github.com/nodejs/node/issues/7439, it seems to at least partially be working as intended, but the behavior is somewhat surprising.



Patch:
diff --git a/bin/eslint.js b/bin/eslint.js
index 1255e3d8b..adc07c2e7 100755
--- a/bin/eslint.js
+++ b/bin/eslint.js
@@ -56,7 +56,15 @@ process.once("uncaughtException", err => {
 });
 
 if (useStdIn) {
-    process.exitCode = cli.execute(process.argv, fs.readFileSync(process.stdin.fd, "utf8"));
+
+    /*
+     * 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.
+     */
+    const STDIN_FILE_DESCRIPTOR = 0;
+
+    process.exitCode = cli.execute(process.argv, fs.readFileSync(STDIN_FILE_DESCRIPTOR, "utf8"));
 } else if (init) {
     const configInit = require("../lib/config/config-initializer");
 
diff --git a/lib/config/config-initializer.js b/lib/config/config-initializer.js
index fe42880b4..ab06909d8 100644
--- a/lib/config/config-initializer.js
+++ b/lib/config/config-initializer.js
@@ -504,7 +504,7 @@ function promptUser() {
                 earlyAnswers.styleguide = "airbnb-base";
             }
 
-            const config = getConfigForStyleGuide(earlyAnswers.styleguide, earlyAnswers.installESLint);
+            const config = getConfigForStyleGuide(earlyAnswers.styleguide);
             const modules = getModulesList(config);
 
             return askInstallModules(modules, earlyAnswers.packageJsonExists)
diff --git a/lib/rules/one-var.js b/lib/rules/one-var.js
index 70223dd2c..412fe615d 100644
--- a/lib/rules/one-var.js
+++ b/lib/rules/one-var.js
@@ -293,7 +293,11 @@ module.exports = {
                 const res = [];
 
                 if (previousNode && previousNode.kind === sourceCode.getText(type)) {
-                    res.push(fixer.replaceText(prevSemi, ","));
+                    if (prevSemi.value === ";") {
+                        res.push(fixer.replaceText(prevSemi, ","));
+                    } else {
+                        res.push(fixer.insertTextAfter(prevSemi, ","));
+                    }
                     res.push(fixer.replaceText(type, ""));
                 }
 


Test patch:
diff --git a/tests/bin/eslint.js b/tests/bin/eslint.js
index 5fc818f28..75e7c2c4b 100644
--- a/tests/bin/eslint.js
+++ b/tests/bin/eslint.js
@@ -152,6 +152,17 @@ describe("bin/eslint.js", () => {
             }
         );
 
+        it("successfully reads from an asynchronous pipe", () => {
+            const child = runESLint(["--stdin", "--no-eslintrc"]);
+
+            child.stdin.write("var foo = bar;\n");
+            return new Promise(resolve => setTimeout(resolve, 300)).then(() => {
+                child.stdin.write("var baz = qux;\n");
+                child.stdin.end();
+
+                return assertExitCode(child, 0);
+            });
+        });
     });
 
     describe("running on files", () => {
diff --git a/tests/lib/rules/one-var.js b/tests/lib/rules/one-var.js
index 50a2ecf1d..1f6626ad8 100644
--- a/tests/lib/rules/one-var.js
+++ b/tests/lib/rules/one-var.js
@@ -1701,6 +1701,17 @@ ruleTester.run("one-var", rule, {
                 line: 1,
                 column: 41
             }]
+        },
+        {
+            code: "var a\nvar b",
+            output: "var a,\n b",
+            options: ["always"],
+            errors: [{
+                message: "Combine this with the previous 'var' statement.",
+                type: "VariableDeclaration",
+                line: 2,
+                column: 1
+            }]
         }
     ]
 });