Repository: prettier/eslint-plugin-prettier (JavaScript)

Issue:
ENOTDIR with virtual filenames from processors
<!-- If you're reporting a bug, please provide the following information to make it easier to triage your issue. If you don't provide this information, your issue may be closed. If a question isn't applicable to your report, feel free to skip it/delete it. -->

**What version of `eslint` are you using?** 7.21.0

**What version of `prettier` are you using?** 2.2.1

**What version of `eslint-plugin-prettier` are you using?** 3.3.1

```json
{
  "script": {
    "test": "eslint README.md"
  },
  "devDependencies": {
    "eslint": "^7.21.0",
    "eslint-config-prettier": "^8.1.0",
    "eslint-plugin-markdown": "^2.0.0",
    "eslint-plugin-prettier": "^3.3.1",
    "prettier": "^2.2.1"
  }
}
```

**Please paste any applicable config files that you're using (e.g. `.prettierrc` or `.eslintrc` files)**

No `.prettierrc`.

```js
// .eslintrc.js
module.exports = {
    root: true,
    extends: ['plugin:prettier/recommended', 'plugin:markdown/recommended'],
};
```

**What source code are you linting?**

````md
# README.md

```js
var x = 123;
```
````

**What did you expect to happen?**

`npm test` should run without throwing an exception.

**What actually happened?**

```
$ npm test
Oops! Something went wrong! :(

ESLint: 7.21.0

Error: ENOTDIR: not a directory, stat '.../README.md/0_0.js'
Occurred while linting .../README.md/0_0.js:1
    at Object.statSync (node:fs:1128:3)
    at isTypeSync (.../node_modules/prettier/third-party.js:9748:46)
    at getDirectorySync (.../node_modules/prettier/third-party.js:9802:62)
    at ExplorerSync.searchSync (.../node_modules/prettier/third-party.js:9950:66)
    at _resolveConfig (.../node_modules/prettier/index.js:25592:50)
    at Function.resolveConfig.sync (.../node_modules/prettier/index.js:25621:42)
    at Program (.../node_modules/eslint-plugin-prettier/eslint-plugin-prettier.js:167:40)
    at .../node_modules/eslint/lib/linter/safe-emitter.js:45:58
    at Array.forEach (<anonymous>)
    at Object.emit (.../node_modules/eslint/lib/linter/safe-emitter.js:45:38)
```

Credit to https://github.com/eslint/eslint-plugin-markdown/issues/174 for originally reporting this. My examples are based on that issue.

`eslint-plugin-markdown` v2 switched to use the [new processor API](https://github.com/eslint/rfcs/tree/master/designs/2018-processors-improvements) that was released as part of ESLint v6. Instead of returning each extracted code block as a string, processors can now return both the source code `text` and a `filename`. In the example code above, the code block's returned `filename` would be `0.js`, with the extension coming from the code block's syntax tag.

The new processor API uses the code block's `filename` to construct a [virtual filename](https://github.com/eslint/rfcs/tree/master/designs/2018-processors-improvements#virtual-filenames-for-code-blocks) as `parentFilename + path.sep + index + filenameFromPreprocess`. In the example above, the code block's virtual filename becomes `README.md/0_0.js`.

Based on the stack trace and a bit of stepping in a debugger, it looks like the exception is thrown when the rule [tries to load the `.prettierrc` for the file](https://github.com/prettier/eslint-plugin-prettier/blob/7d5d3ddbabd3b6e9e2347879889dfef0acaeb7b1/eslint-plugin-prettier.js#L127). If I set `usePrettierrc: false`, a very similar exception is thrown in a call [a few lines later](https://github.com/prettier/eslint-plugin-prettier/blob/7d5d3ddbabd3b6e9e2347879889dfef0acaeb7b1/eslint-plugin-prettier.js#L132).

This exception would occur with any processor that uses the new processor API and yields virtual filenames.

Depending on what it might take to handle code blocks with virtual filenames, https://github.com/eslint/eslint/issues/11989 may be a related issue.


Discussion:
I have the same situation when implementing linting code blocks for `eslint-plugin-mdx` which use virtual filename same as `eslint-plugin-markdown`.

Personally I add a hack check for it in `eslint-plugin-mdx`, if you like it I can raise a PR.

https://github.com/mdx-js/eslint-mdx/blob/master/packages/eslint-plugin-mdx/src/rules/helpers.ts#L58-L71

---

@lydell @fisker @BPScott 
For anyone maybe interested, #401 should fix this issue, I just published `@rxts/eslint-plugin-prettier@^3.4.0-1` as workaround, you can use `"eslint-plugin-prettier": "npm:@rxts/eslint-plugin-prettier@^3.4.0-1"` in `package.json.`

Once that PR been merged and released, I'll deprecate it.
Thank you, this solves the problem! Hope this PR will get merged.



Patch:
diff --git a/eslint-plugin-prettier.js b/eslint-plugin-prettier.js
index 5916b5cd..3e446cfc 100644
--- a/eslint-plugin-prettier.js
+++ b/eslint-plugin-prettier.js
@@ -9,6 +9,9 @@
 //  Requirements
 // ------------------------------------------------------------------------------
 
+const fs = require('fs');
+const path = require('path');
+
 const {
   showInvisibles,
   generateDifferences
@@ -25,6 +28,9 @@ const { INSERT, DELETE, REPLACE } = generateDifferences;
 // ------------------------------------------------------------------------------
 
 // Lazily-loaded Prettier.
+/**
+ * @type {import('prettier')}
+ */
 let prettier;
 
 // ------------------------------------------------------------------------------
@@ -55,6 +61,26 @@ function reportDifference(context, difference) {
   });
 }
 
+/**
+ * get normalized filepath in case of virtual filename
+ * @param {string} filepath
+ * @returns {string}
+ */
+function normalizeFilepath(filepath) {
+  try {
+    if (fs.statSync(filepath).isFile()) {
+      return filepath;
+    }
+  } catch (err) {
+    // https://github.com/eslint/eslint/issues/11989
+    if (err.code === 'ENOTDIR') {
+      return normalizeFilepath(path.dirname(filepath));
+    }
+  }
+
+  return filepath;
+}
+
 // ------------------------------------------------------------------------------
 //  Module Definition
 // ------------------------------------------------------------------------------
@@ -112,6 +138,7 @@ module.exports = {
           (context.options[1] && context.options[1].fileInfoOptions) || {};
         const sourceCode = context.getSourceCode();
         const filepath = context.getFilename();
+        const normalizedFilepath = normalizeFilepath(filepath);
         const source = sourceCode.text;
 
         return {
@@ -124,13 +151,13 @@ module.exports = {
             const eslintPrettierOptions = context.options[0] || {};
 
             const prettierRcOptions = usePrettierrc
-              ? prettier.resolveConfig.sync(filepath, {
+              ? prettier.resolveConfig.sync(normalizedFilepath, {
                   editorconfig: true
                 })
               : null;
 
             const prettierFileInfo = prettier.getFileInfo.sync(
-              filepath,
+              normalizedFilepath,
               Object.assign(
                 {},
                 { resolveConfig: true, ignorePath: '.prettierignore' },
@@ -145,7 +172,8 @@ module.exports = {
 
             const initialOptions = {};
 
-            // ESLint suppports processors that let you extract and lint JS
+            // for ESLint < 6.0
+            // it supports processors that let you extract and lint JS
             // fragments within a non-JS language. In the cases where prettier
             // supports the same language as a processor, we want to process
             // the provided source code as javascript (as ESLint provides the
@@ -165,8 +193,13 @@ module.exports = {
             // * Prettier supports parsing the file type
             // * There is an ESLint processor that extracts JavaScript snippets
             //   from the file type.
+            //
+            // for ESLint >= 6.0
+            // it supports virtual filename, if filepath is not same as normalizedFilepath,
+            // it means filepath is virtual name, and we can guess the file type by prettier automatically
             const parserBlocklist = [null, 'graphql', 'markdown', 'html'];
             if (
+              filepath === normalizedFilepath &&
               parserBlocklist.indexOf(prettierFileInfo.inferredParser) !== -1
             ) {
               // Prettier v1.16.0 renamed the `babylon` parser to `babel`
@@ -187,7 +220,7 @@ module.exports = {
             );
 
             // prettier.format() may throw a SyntaxError if it cannot parse the
-            // source code it is given. Ususally for JS files this isn't a
+            // source code it is given. Usually for JS files this isn't a
             // problem as ESLint will report invalid syntax before trying to
             // pass it to the prettier plugin. However this might be a problem
             // for non-JS languages that are handled by a plugin. Notably Vue
@@ -205,7 +238,7 @@ module.exports = {
               let message = 'Parsing error: ' + err.message;
 
               // Prettier's message contains a codeframe style preview of the
-              // invalid code and the line/column at which the error occured.
+              // invalid code and the line/column at which the error occurred.
               // ESLint shows those pieces of information elsewhere already so
               // remove them from the message
               if (err.codeFrame) {


Test patch:
diff --git a/test/prettier.js b/test/prettier.js
index c5e8a6af..ff859461 100644
--- a/test/prettier.js
+++ b/test/prettier.js
@@ -66,6 +66,10 @@ ruleTester.run('prettier', rule, {
     {
       code: 'a();;;;;;\n',
       filename: 'node_modules/dummy.js'
+    },
+    {
+      code: `('');\n`,
+      filename: path.join(__filename, '0_fake_virtual_name.js')
     }
   ],
   invalid: [