Repository: prettier/prettier (JavaScript)
Date: 2019-10-20 05:59:18+00:00
Task type: Bug Fix

Issue:
Passing a full path via --stdin-filepath causes an error
**Environments:**
- Prettier Version: master
- Running Prettier via: CLI
- Runtime: Node.js
- Operating System: Linux

**Steps to reproduce:**
When prettier is given a simple file name, like `foo.tsx` in its `--stdin-filepath` arg, everything works fine.

However, things fail if you try to pass a full "path", like `bar/foo.tsx`, for example. The following should reproduce the issue at master:

`echo "meep" | prettier --stdin-filepath=bar/foo.tsx`

**Expected behavior:**
Expected Prettier to use the `stdin-filepath` argument as normal to determine e.g. what parser to use.

**Actual behavior:**
Prettier fails with the following error:

```
[error] Invalid configuration file: ENOENT: no such file or directory, scandir '/path/to/cwd/bar'
```

AFAIK this wasn't an issue with 1.16.1, but is an issue with the more recent release.


Issue discussion:

Solution patch:
diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md
index bd578028f8f2..e4ade133c80c 100644
--- a/CHANGELOG.unreleased.md
+++ b/CHANGELOG.unreleased.md
@@ -1317,6 +1317,20 @@ $ prettier --parser babel < test.js
 [error] Invalid printWidth value. Expected an integer, but received "nope".
 ```
 
+#### CLI: Gracefully handle nonexistent paths passed to --stdin-filepath ([#6687] by [@voithos])
+
+Previously, if you passed a nonexistent subdirectory to --stdin-filepath, Prettier would throw an error. Now, Prettier gracefully handles this.
+
+```
+# Prettier stable
+$ prettier --stdin-filepath does/not/exist.js < test.js
+[error] Invalid configuration file: ENOENT: no such file or directory, scandir '/home/lydell/forks/prettier/does/not'
+
+# Prettier master
+$ prettier --stdin-filepath does/not/exist.js < test.js
+test;
+```
+
 [#5682]: https://github.com/prettier/prettier/pull/5682
 [#6657]: https://github.com/prettier/prettier/pull/6657
 [#5910]: https://github.com/prettier/prettier/pull/5910
@@ -1362,6 +1376,7 @@ $ prettier --parser babel < test.js
 [#6717]: https://github.com/prettier/prettier/pull/6717
 [#6728]: https://github.com/prettier/prettier/pull/6728
 [#6708]: https://github.com/prettier/prettier/pull/6708
+[#6687]: https://github.com/prettier/prettier/pull/6687
 [@brainkim]: https://github.com/brainkim
 [@duailibe]: https://github.com/duailibe
 [@gavinjoyce]: https://github.com/gavinjoyce
diff --git a/cspell.json b/cspell.json
index dddb81ae6b67..b57e67befa79 100644
--- a/cspell.json
+++ b/cspell.json
@@ -91,6 +91,7 @@
         "editorconfig",
         "ekkhus",
         "elektronik",
+        "ENOENT",
         "Eneman",
         "ericsakmar",
         "Ericsburgh",
@@ -303,6 +304,7 @@
         "ruleset",
         "Sapegin",
         "sbdchd",
+        "scandir",
         "schemastore",
         "serializer",
         "setlocal",
@@ -393,4 +395,4 @@
         "\\[`\\w+`\\]",
         "ve+r+y+(long\\w+)?"
     ]
-}
\ No newline at end of file
+}
diff --git a/src/config/resolve-config-editorconfig.js b/src/config/resolve-config-editorconfig.js
index 07c209250125..deab857d4c46 100644
--- a/src/config/resolve-config-editorconfig.js
+++ b/src/config/resolve-config-editorconfig.js
@@ -1,5 +1,6 @@
 "use strict";
 
+const fs = require("fs");
 const path = require("path");
 
 const editorconfig = require("editorconfig");
@@ -8,7 +9,16 @@ const editorConfigToPrettier = require("editorconfig-to-prettier");
 const findProjectRoot = require("find-project-root");
 
 const maybeParse = (filePath, config, parse) => {
-  const root = findProjectRoot(path.dirname(path.resolve(filePath)));
+  // findProjectRoot will throw an error if we pass a nonexistent directory to
+  // it, which is possible, for example, when the path is given via
+  // --stdin-filepath. So, first, traverse up until we find an existing
+  // directory.
+  let dirPath = path.dirname(path.resolve(filePath));
+  const fsRoot = path.parse(dirPath).root;
+  while (dirPath !== fsRoot && !fs.existsSync(dirPath)) {
+    dirPath = path.dirname(dirPath);
+  }
+  const root = findProjectRoot(dirPath);
   return filePath && parse(filePath, { root });
 };
 


Test patch:
diff --git a/tests_integration/__tests__/__snapshots__/stdin-filepath.js.snap b/tests_integration/__tests__/__snapshots__/stdin-filepath.js.snap
index a109655c8ed4..2f065a2b1d21 100644
--- a/tests_integration/__tests__/__snapshots__/stdin-filepath.js.snap
+++ b/tests_integration/__tests__/__snapshots__/stdin-filepath.js.snap
@@ -1,5 +1,38 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
+exports[`apply editorconfig for stdin-filepath with nonexistent directory (stderr) 1`] = `""`;
+
+exports[`apply editorconfig for stdin-filepath with nonexistent directory (stdout) 1`] = `
+"function f() {
+	console.log(\\"should be indented with a tab\\")
+}
+"
+`;
+
+exports[`apply editorconfig for stdin-filepath with nonexistent directory (write) 1`] = `Array []`;
+
+exports[`apply editorconfig for stdin-filepath with nonexistent file (stderr) 1`] = `""`;
+
+exports[`apply editorconfig for stdin-filepath with nonexistent file (stdout) 1`] = `
+"function f() {
+	console.log(\\"should be indented with a tab\\")
+}
+"
+`;
+
+exports[`apply editorconfig for stdin-filepath with nonexistent file (write) 1`] = `Array []`;
+
+exports[`don’t apply editorconfig outside project for stdin-filepath with nonexistent directory (stderr) 1`] = `""`;
+
+exports[`don’t apply editorconfig outside project for stdin-filepath with nonexistent directory (stdout) 1`] = `
+"function f() {
+  console.log(\\"should be indented with 2 spaces\\")
+}
+"
+`;
+
+exports[`don’t apply editorconfig outside project for stdin-filepath with nonexistent directory (write) 1`] = `Array []`;
+
 exports[`format correctly if stdin content compatible with stdin-filepath (stderr) 1`] = `""`;
 
 exports[`format correctly if stdin content compatible with stdin-filepath (stdout) 1`] = `
@@ -11,6 +44,17 @@ exports[`format correctly if stdin content compatible with stdin-filepath (stdou
 
 exports[`format correctly if stdin content compatible with stdin-filepath (write) 1`] = `Array []`;
 
+exports[`gracefully handle stdin-filepath with nonexistent directory (stderr) 1`] = `""`;
+
+exports[`gracefully handle stdin-filepath with nonexistent directory (stdout) 1`] = `
+".name {
+  display: none;
+}
+"
+`;
+
+exports[`gracefully handle stdin-filepath with nonexistent directory (write) 1`] = `Array []`;
+
 exports[`output file as-is if stdin-filepath matched patterns in ignore-path (stderr) 1`] = `""`;
 
 exports[`output file as-is if stdin-filepath matched patterns in ignore-path (write) 1`] = `Array []`;
diff --git a/tests_integration/__tests__/stdin-filepath.js b/tests_integration/__tests__/stdin-filepath.js
index bc01b90c8d1d..983b6a64a097 100644
--- a/tests_integration/__tests__/stdin-filepath.js
+++ b/tests_integration/__tests__/stdin-filepath.js
@@ -22,6 +22,67 @@ describe("throw error if stdin content incompatible with stdin-filepath", () =>
   });
 });
 
+describe("gracefully handle stdin-filepath with nonexistent directory", () => {
+  runPrettier(
+    "cli",
+    ["--stdin-filepath", "definitely/nonexistent/path.css"],
+    { input: ".name { display: none; }" } // css
+  ).test({
+    status: 0
+  });
+});
+
+describe("apply editorconfig for stdin-filepath with nonexistent file", () => {
+  runPrettier(
+    "cli",
+    ["--stdin-filepath", "config/editorconfig/nonexistent.js"],
+    {
+      input: `
+function f() {
+  console.log("should be indented with a tab");
+}
+`.trim() // js
+    }
+  ).test({
+    status: 0
+  });
+});
+
+describe("apply editorconfig for stdin-filepath with nonexistent directory", () => {
+  runPrettier(
+    "cli",
+    ["--stdin-filepath", "config/editorconfig/nonexistent/one/two/three.js"],
+    {
+      input: `
+function f() {
+  console.log("should be indented with a tab");
+}
+`.trim() // js
+    }
+  ).test({
+    status: 0
+  });
+});
+
+describe("don’t apply editorconfig outside project for stdin-filepath with nonexistent directory", () => {
+  runPrettier(
+    "cli",
+    [
+      "--stdin-filepath",
+      "config/editorconfig/repo-root/nonexistent/one/two/three.js"
+    ],
+    {
+      input: `
+function f() {
+  console.log("should be indented with 2 spaces");
+}
+`.trim() // js
+    }
+  ).test({
+    status: 0
+  });
+});
+
 describe("output file as-is if stdin-filepath matched patterns in ignore-path", () => {
   runPrettier("cli/stdin-ignore", ["--stdin-filepath", "ignore/example.js"], {
     input: "hello_world( );"