Repository: prettier/prettier (JavaScript)

Issue:
Load plugins from `.prettierrc` when initializing CLI context
## Description

<!-- Please provide a brief summary of your changes: -->

Fixes #15079 

## Checklist

<!-- Please ensure you’ve done all of these things (if applicable). -->
<!-- You can replace the `[ ]` with `[x]` to mark each task as done. -->

- [x] I’ve added tests to confirm my change works.
- [ ] (If the change is user-facing) I’ve added my changes to `changelog_unreleased/*/XXXX.md` file following `changelog_unreleased/TEMPLATE.md`.
- [x] I’ve read the [contributing guidelines](https://github.com/prettier/prettier/blob/main/CONTRIBUTING.md).

<!-- Please DO NOT remove the playground link -->

**✨[Try the playground for this PR](https://prettier.io/playground-redirect)✨**

[v3] plugins declared in `.prettierrc` are not loaded on directory
<!--

BEFORE SUBMITTING AN ISSUE:

1.  Search for your issue on GitHub: https://github.com/prettier/prettier/issues
    A large number of opened issues are duplicates of existing issues.
    If someone has already opened an issue for what you are experiencing,
    you do not need to open a new issue — please add a 👍 reaction to the
    existing issue instead.

2.  If your issue is with a Prettier editor extension or add-on, please open the
    issue in the repo for that extension or add-on, instead of this repo.

-->

**Environments:**

- Prettier Version: 3.0.0
- Running Prettier via: <!-- CLI, Node.js API, Browser API, etc. --> CLI
- Runtime: <!-- Node.js v14, Chrome v83, etc. --> v18.16.0
- Operating System: <!-- Windows, Linux, macOS, etc. --> Linux
- Prettier plugins (if any): <!-- e.g. @prettier/plugin-php --> prettier-plugin-sh

**Steps to reproduce:**

<!-- shell script, js code, or a link to the minimal reproducible repository -->

https://github.com/un-ts/prettier/issues/273#issuecomment-1629109603

**Expected behavior:**

Like prettier v2, `.sh` files should be checked

**Actual behavior:**

`.sh` files are not checked

---

After debugging, `getSupportedFilesGlob` here https://github.com/prettier/prettier/blob/79d8a3983842d1d8a077f177ed8b983c1c596088/src/cli/expand-patterns.js#L130-L145
does not resolve `.sh` files correctly.


Hints:

It's a known issue, are you sure it works in v2?
@fisker I tried @devcrev's reproduction at https://github.com/devcrev/p-sh-old, it works on v2.

```log
> p-sh-old@1.0.0 format
> prettier --check .

Checking formatting...
[warn] level-0/go.sh
[warn] level-0/hello.sh
[warn] level-0/level-1/another.sh
[warn] level-0/level-1/library.js
[warn] level-0/level-1/runme.sh
[warn] level-0/program.js
[warn] Code style issues found in 6 files. Forgot to run Prettier?
```
Could be related to ***Plugin search feature has been removed***?
I am having this same issue with the `.xml` files and `.properties` files in my project. I'm using [plugin-xml](https://github.com/prettier/plugin-xml) version 3.1.0 and the latest commit of[ prettier-plugin-properties](https://github.com/eemeli/prettier-plugin-properties) (`npm install -D git@github.com:eemeli/prettier-plugin-properties.git#0c7072dae2006c0d0a588f70b19940390a50add1`)
I've noticed that you now need to specify something like `**/*.*` (rather than a folder, such as `.`) to the CLI command in order to parse files that have been registered by a plugin declared in the prettier configuration file. This wasn't the case with 2.*. It might be related to https://github.com/prettier/prettier/pull/14759, as @JounQin pointed out.

Related issue I've opened in a plugin's repo (as I thought it no longer worked): https://github.com/shufo/prettier-plugin-blade/issues/213
The current documentation seems to indicate that the behavior should be as it was in version 2.

![image](https://github.com/prettier/prettier/assets/44066444/8265801a-6176-4c33-90f5-dc758cc0007a)

This `package.json` script for v3 does what I want and what v2 did with `prettier . --check`:

 ```
 "scripts": {
    "format": "prettier './**/*' --check --ignore-unknown"
  },
```

I'm observing this bug, too. Running `npx prettier --write .` will ignore any `.svelte` files, running `npx prettier --plugin prettier-plugin-svelte --write . ` works. My debugging so far lead my to believe that this issue is with the file glob. The file glob is created from the context that is created at the start of the CLI run. If I pass the plugin, the context contains the info that Svelte files are eligible for formatting so the file glob includes Svelte files, else it doesn't.

So the fix is either to adjust the context creation to also search for a `.prettierrc` starting from `process.cwd()` and load any plugins from there, or to widen the file glob to include all files, not just the ones the context says it knows about.
@dummdidumm Indeed. Config files are resolved for each processed file, which is too late.
I'll work on this

Patch:
diff --git a/src/cli/expand-patterns.js b/src/cli/expand-patterns.js
--- a/src/cli/expand-patterns.js
+++ b/src/cli/expand-patterns.js
@@ -11,22 +11,24 @@ async function* expandPatterns(context) {
   const seen = new Set();
   let noResults = true;
 
-  for await (const pathOrError of expandPatternsInternal(context)) {
+  for await (const { filePath, ignoreUnknown, error } of expandPatternsInternal(
+    context,
+  )) {
     noResults = false;
-    if (typeof pathOrError !== "string") {
-      yield pathOrError;
+    if (error) {
+      yield { error };
       continue;
     }
 
-    const fileName = path.resolve(pathOrError);
+    const filename = path.resolve(filePath);
 
     // filter out duplicates
-    if (seen.has(fileName)) {
+    if (seen.has(filename)) {
       continue;
     }
 
-    seen.add(fileName);
-    yield fileName;
+    seen.add(filename);
+    yield { filename, ignoreUnknown };
   }
 
   if (noResults && context.argv.errorOnUnmatchedPattern !== false) {
@@ -52,7 +54,6 @@ async function* expandPatternsInternal(context) {
     followSymbolicLinks: false,
   };
 
-  let supportedFilesGlob;
   const cwd = process.cwd();
 
   /** @type {Array<{ type: 'file' | 'dir' | 'glob'; glob: string; input: string; }>} */
@@ -87,10 +88,9 @@ async function* expandPatternsInternal(context) {
         const prefix = escapePathForGlob(fixWindowsSlashes(relativePath));
         entries.push({
           type: "dir",
-          glob: getSupportedFilesGlob().map(
-            (pattern) => `${prefix}/**/${pattern}`,
-          ),
+          glob: `${prefix}/**/*`,
           input: pattern,
+          ignoreUnknown: true,
         });
       }
     } else if (pattern[0] === "!") {
@@ -105,7 +105,7 @@ async function* expandPatternsInternal(context) {
     }
   }
 
-  for (const { type, glob, input } of entries) {
+  for (const { type, glob, input, ignoreUnknown } of entries) {
     let result;
 
     try {
@@ -123,22 +123,7 @@ async function* expandPatternsInternal(context) {
         yield { error: `${errorMessages.emptyResults[type]}: "${input}".` };
       }
     } else {
-      yield* sortPaths(result);
-    }
-  }
-
-  function getSupportedFilesGlob() {
-    supportedFilesGlob ??= [...getSupportedFilesGlobWithoutCache()];
-    return supportedFilesGlob;
-  }
-
-  function* getSupportedFilesGlobWithoutCache() {
-    for (const { extensions = [], filenames = [] } of context.languages) {
-      yield* filenames;
-
-      for (const extension of extensions) {
-        yield `*${extension.startsWith(".") ? extension : `.${extension}`}`;
-      }
+      yield* sortPaths(result).map((filePath) => ({ filePath, ignoreUnknown }));
     }
   }
 }
diff --git a/src/cli/format.js b/src/cli/format.js
--- a/src/cli/format.js
+++ b/src/cli/format.js
@@ -22,17 +22,16 @@ class DebugError extends Error {
   name = "DebugError";
 }
 
-function handleError(context, filename, error, printedFilename) {
+function handleError(context, filename, error, printedFilename, ignoreUnknown) {
+  ignoreUnknown ||= context.argv.ignoreUnknown;
+
   const errorIsUndefinedParseError =
     error instanceof errors.UndefinedParserError;
 
   if (printedFilename) {
     // Can't test on CI, `isTTY()` is always false, see ./is-tty.js
     /* c8 ignore next 3 */
-    if (
-      (context.argv.write || context.argv.ignoreUnknown) &&
-      errorIsUndefinedParseError
-    ) {
+    if ((context.argv.write || ignoreUnknown) && errorIsUndefinedParseError) {
       printedFilename.clear();
     } else {
       // Add newline to split errors from filename line.
@@ -41,7 +40,7 @@ function handleError(context, filename, error, printedFilename) {
   }
 
   if (errorIsUndefinedParseError) {
-    if (context.argv.ignoreUnknown) {
+    if (ignoreUnknown) {
       return;
     }
     if (!context.argv.check && !context.argv.listDifferent) {
@@ -313,16 +312,16 @@ async function formatFiles(context) {
     }
   }
 
-  for await (const pathOrError of expandPatterns(context)) {
-    if (typeof pathOrError === "object") {
-      context.logger.error(pathOrError.error);
+  for await (const { error, filename, ignoreUnknown } of expandPatterns(
+    context,
+  )) {
+    if (error) {
+      context.logger.error(error);
       // Don't exit, but set the exit code to 2
       process.exitCode = 2;
       continue;
     }
 
-    const filename = pathOrError;
-
     const isFileIgnored = isIgnored(filename);
     if (
       isFileIgnored &&
@@ -391,7 +390,13 @@ async function formatFiles(context) {
       }
       output = result.formatted;
     } catch (error) {
-      handleError(context, fileNameToDisplay, error, printedFilename);
+      handleError(
+        context,
+        fileNameToDisplay,
+        error,
+        printedFilename,
+        ignoreUnknown,
+      );
       continue;
     }
 


Test patch:
diff --git a/tests/integration/__tests__/__snapshots__/ignore-unknown.js.snap b/tests/integration/__tests__/__snapshots__/ignore-unknown.js.snap
--- a/tests/integration/__tests__/__snapshots__/ignore-unknown.js.snap
+++ b/tests/integration/__tests__/__snapshots__/ignore-unknown.js.snap
@@ -18,25 +18,35 @@ exports[`Not matching pattern (stdout) 1`] = `""`;
 
 exports[`Not matching pattern (write) 1`] = `[]`;
 
-exports[`ignore-unknown alias (stdout) 1`] = `"javascript.js"`;
+exports[`ignore-unknown alias (stdout) 1`] = `
+"javascript.js
+override.as-js-file"
+`;
 
 exports[`ignore-unknown check (stderr) 1`] = `
 "[warn] javascript.js
-[warn] Code style issues found in the above file. Run Prettier to fix."
+[warn] override.as-js-file
+[warn] Code style issues found in 2 files. Run Prettier to fix."
 `;
 
 exports[`ignore-unknown check (stdout) 1`] = `"Checking formatting..."`;
 
 exports[`ignore-unknown check (write) 1`] = `[]`;
 
-exports[`ignore-unknown dir (stdout) 1`] = `"javascript.js"`;
+exports[`ignore-unknown dir (stdout) 1`] = `
+"javascript.js
+override.as-js-file"
+`;
 
 exports[`ignore-unknown pattern (stdout) 1`] = `
 "javascript.js
 override.as-js-file"
 `;
 
-exports[`ignore-unknown write (stdout) 1`] = `"javascript.js"`;
+exports[`ignore-unknown write (stdout) 1`] = `
+"javascript.js
+override.as-js-file"
+`;
 
 exports[`ignore-unknown write (write) 1`] = `
 [
@@ -45,5 +55,10 @@ exports[`ignore-unknown write (write) 1`] = `
 ",
     "filename": "javascript.js",
   },
+  {
+    "content": "const foo = "bar";
+",
+    "filename": "override.as-js-file",
+  },
 ]
 `;
diff --git a/tests/integration/__tests__/__snapshots__/infer-plugins-ext-dir.js.snap b/tests/integration/__tests__/__snapshots__/infer-plugins-ext-dir.js.snap
new file mode 100644
--- /dev/null
+++ b/tests/integration/__tests__/__snapshots__/infer-plugins-ext-dir.js.snap
@@ -0,0 +1,17 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`infer file ext that supported by only plugins basic (stderr) 1`] = `""`;
+
+exports[`infer file ext that supported by only plugins with config option (stderr) 1`] = `""`;
+
+exports[`infer file ext that supported by only plugins with defaultOptions (stderr) 1`] = `""`;
+
+exports[`infer file ext that supported by only plugins with multiple config for nested dir (stderr) 1`] = `""`;
+
+exports[`infer file ext that supported by only plugins with multiple config for nested dir 2 (stderr) 1`] = `""`;
+
+exports[`infer file ext that supported by only plugins with multiple config for nested dir 2 (stderr) 2`] = `""`;
+
+exports[`infer file ext that supported by only plugins with overrides and defaultOptions (stderr) 1`] = `""`;
+
+exports[`infer file ext that supported by only plugins with overrides options (stderr) 1`] = `""`;
diff --git a/tests/integration/__tests__/__snapshots__/patterns-dirs.js.snap b/tests/integration/__tests__/__snapshots__/patterns-dirs.js.snap
--- a/tests/integration/__tests__/__snapshots__/patterns-dirs.js.snap
+++ b/tests/integration/__tests__/__snapshots__/patterns-dirs.js.snap
@@ -9,7 +9,8 @@ dir1/nested1/bn1.js
 dir2/a2.js
 dir2/b2.js
 dir2/nested2/an2.js
-dir2/nested2/bn2.js"
+dir2/nested2/bn2.js
+dir2/node-shebang"
 `;
 
 exports[`1a - with *.foo plugin: prettier dir1 dir2 --plugin=../../plugins/extensions/plugin.cjs (stdout) 1`] = `
@@ -23,10 +24,11 @@ dir2/a2.foo
 dir2/a2.js
 dir2/b2.js
 dir2/nested2/an2.js
-dir2/nested2/bn2.js"
+dir2/nested2/bn2.js
+dir2/node-shebang"
 `;
 
-exports[`1c: prettier dir1 empty (stderr) 1`] = `"[error] No supported files were found in the directory: "empty"."`;
+exports[`1c: prettier dir1 empty (stderr) 1`] = `""`;
 
 exports[`1c: prettier dir1 empty (stdout) 1`] = `
 "dir1/a1.js
@@ -64,7 +66,7 @@ dir2/nested2/bn2.js
 dir2/node-shebang"
 `;
 
-exports[`4: prettier . 'dir2/**/*' (stderr) 1`] = `"[error] No parser could be inferred for file "<cli>/patterns-dirs/dir2/a2.foo"."`;
+exports[`4: prettier . 'dir2/**/*' (stderr) 1`] = `""`;
 
 exports[`4: prettier . 'dir2/**/*' (stdout) 1`] = `
 "!dir/a.js
@@ -97,7 +99,8 @@ dir1/b1.js
 dir2/a2.js
 dir2/b2.js
 dir2/nested2/an2.js
-dir2/nested2/bn2.js"
+dir2/nested2/bn2.js
+dir2/node-shebang"
 `;
 
 exports[`Negative patterns 3: prettier . '!dir1/nested1/an1.js' (stdout) 1`] = `
@@ -109,7 +112,8 @@ dir1/nested1/bn1.js
 dir2/a2.js
 dir2/b2.js
 dir2/nested2/an2.js
-dir2/nested2/bn2.js"
+dir2/nested2/bn2.js
+dir2/node-shebang"
 `;
 
 exports[`Negative patterns 4: prettier '!nonexistent-dir1 !nonexistent-dir2' (stderr) 1`] = `"[error] No matching files. Patterns: !nonexistent-dir1 !nonexistent-dir2"`;
@@ -130,7 +134,8 @@ dir1/nested1/bn1.js
 dir2/a2.js
 dir2/b2.js
 dir2/nested2/an2.js
-dir2/nested2/bn2.js"
+dir2/nested2/bn2.js
+dir2/node-shebang"
 `;
 
 exports[`Trailing slash 2: prettier .// (stdout) 1`] = `
@@ -143,7 +148,8 @@ dir1/nested1/bn1.js
 dir2/a2.js
 dir2/b2.js
 dir2/nested2/an2.js
-dir2/nested2/bn2.js"
+dir2/nested2/bn2.js
+dir2/node-shebang"
 `;
 
 exports[`Trailing slash 3: prettier dir1/ (stdout) 1`] = `
@@ -180,7 +186,8 @@ exports[`Trailing slash run in sub dir 1: prettier .. (stdout) 1`] = `
 a2.js
 b2.js
 nested2/an2.js
-nested2/bn2.js"
+nested2/bn2.js
+node-shebang"
 `;
 
 exports[`Trailing slash run in sub dir 2: prettier ../ (stdout) 1`] = `
@@ -193,7 +200,8 @@ exports[`Trailing slash run in sub dir 2: prettier ../ (stdout) 1`] = `
 a2.js
 b2.js
 nested2/an2.js
-nested2/bn2.js"
+nested2/bn2.js
+node-shebang"
 `;
 
 exports[`Trailing slash run in sub dir 3: prettier ../dir1 (stdout) 1`] = `
diff --git a/tests/integration/__tests__/infer-plugins-ext-dir.js b/tests/integration/__tests__/infer-plugins-ext-dir.js
new file mode 100644
--- /dev/null
+++ b/tests/integration/__tests__/infer-plugins-ext-dir.js
@@ -0,0 +1,208 @@
+import { outdent } from "outdent";
+
+describe("infer file ext that supported by only plugins", () => {
+  describe("basic", () => {
+    runCli("cli/infer-plugins-ext-dir/", ["--write", "src"]).test({
+      status: 0,
+      stdout: "src/file.foo 0ms",
+      write: [
+        {
+          content: "!contents\n",
+          filename: "src/file.foo",
+        },
+      ],
+    });
+  });
+
+  describe("with config option", () => {
+    runCli("cli/infer-plugins-ext-dir-with-config/", [
+      "--config",
+      "foo.mjs",
+      "--write",
+      "src",
+    ]).test({
+      status: 0,
+      stdout: "src/file.foo 0ms",
+      write: [
+        {
+          content: "!contents\n",
+          filename: "src/file.foo",
+        },
+      ],
+    });
+  });
+
+  describe("with overrides options", () => {
+    runCli("cli/infer-plugins-ext-dir-with-overrides/", [
+      "--write",
+      "src",
+    ]).test({
+      status: 0,
+      stdout: "src/file.bar 0ms\nsrc/file.foo 0ms",
+      write: [
+        {
+          content: "!contents\n",
+          filename: "src/file.bar",
+        },
+        {
+          content: "!contents\n",
+          filename: "src/file.foo",
+        },
+      ],
+    });
+  });
+
+  describe("with defaultOptions", () => {
+    runCli("cli/infer-plugins-ext-dir-with-default-options/", [
+      "--write",
+      "--no-editorconfig",
+      "src",
+    ]).test({
+      status: 0,
+      stdout: "src/file.foo 0ms\nsrc/index.js 0ms",
+      write: [
+        {
+          content: '{"tabWidth":8,"bracketSpacing":false}',
+          filename: "src/file.foo",
+        },
+        {
+          // formatted with `tabWidth: 2`
+          content: outdent`
+            function main() {
+              console.log("Hello, World!");
+            }\n
+          `,
+          filename: "src/index.js",
+        },
+      ],
+    });
+  });
+
+  describe("with overrides and defaultOptions", () => {
+    runCli("cli/infer-plugins-ext-dir-with-overrides-and-default-options/", [
+      "--write",
+      "--no-editorconfig",
+      "src",
+    ]).test({
+      status: 0,
+      stdout: "src/file.foo 0ms\nsrc/index.js 0ms",
+      write: [
+        {
+          content: '{"tabWidth":8,"bracketSpacing":false}',
+          filename: "src/file.foo",
+        },
+        {
+          // formatted with `tabWidth: 2`
+          content: outdent`
+            function main() {
+              console.log("Hello, World!");
+            }\n
+          `,
+          filename: "src/index.js",
+        },
+      ],
+    });
+  });
+
+  describe("with multiple config for nested dir", () => {
+    runCli("cli/infer-plugins-with-multiple-config", [
+      "--write",
+      "--no-editorconfig",
+      ".",
+    ]).test({
+      status: 0,
+      stdout: outdent`
+        dir/.prettierrc.mjs 0ms
+        dir/subdir/.prettierrc.mjs 0ms
+        dir/subdir/2.foo 0ms
+      `,
+      write: [
+        {
+          content: "export default {};\n",
+          filename: "dir/.prettierrc.mjs",
+        },
+        {
+          content: outdent`
+            export default {
+              plugins: ["../../../../plugins/defaultOptions/plugin.cjs"],
+            };\n
+          `,
+          filename: "dir/subdir/.prettierrc.mjs",
+        },
+        {
+          content: '{"tabWidth":8,"bracketSpacing":false}',
+          filename: "dir/subdir/2.foo",
+        },
+      ],
+    });
+  });
+
+  describe("with multiple config for nested dir 2", () => {
+    runCli("cli/infer-plugins-with-multiple-config", [
+      "--write",
+      "--no-editorconfig",
+      "dir",
+      "dir/subdir",
+    ]).test({
+      status: 0,
+      stdout: outdent`
+        dir/.prettierrc.mjs 0ms
+        dir/subdir/.prettierrc.mjs 0ms
+        dir/subdir/2.foo 0ms
+      `,
+      write: [
+        {
+          content: "export default {};\n",
+          filename: "dir/.prettierrc.mjs",
+        },
+        {
+          content: outdent`
+            export default {
+              plugins: ["../../../../plugins/defaultOptions/plugin.cjs"],
+            };\n
+          `,
+          filename: "dir/subdir/.prettierrc.mjs",
+        },
+        {
+          content: '{"tabWidth":8,"bracketSpacing":false}',
+          filename: "dir/subdir/2.foo",
+        },
+      ],
+    });
+  });
+
+  describe("with multiple config for nested dir 2", () => {
+    runCli("cli/infer-plugins-ext-dir-with-complex-overrides", [
+      "--write",
+      "--no-editorconfig",
+      ".",
+    ]).test({
+      status: 0,
+      stdout: outdent`
+        .prettierrc.mjs 0ms
+        dir/2.foo 0ms
+      `,
+      write: [
+        {
+          content: outdent`
+            export default {
+              overrides: [
+                {
+                  files: ["dir/*.foo"],
+                  options: {
+                    plugins: ["../../plugins/extensions/plugin.cjs"],
+                  },
+                },
+              ],
+            };\n
+          `,
+          filename: ".prettierrc.mjs",
+        },
+        {
+          content: "!2.foo\n",
+          filename: "dir/2.foo",
+        },
+      ],
+    });
+  });
+});
diff --git a/tests/integration/__tests__/patterns-dirs.js b/tests/integration/__tests__/patterns-dirs.js
--- a/tests/integration/__tests__/patterns-dirs.js
+++ b/tests/integration/__tests__/patterns-dirs.js
@@ -12,22 +12,25 @@ const runCliWithoutGitignore = (dir, args, options) =>
   runCli(dir, [...args, "--ignore-path", ".prettierignore"], options);
 
 // ESLint-like behavior
+//
 // https://github.com/prettier/prettier/pull/6639#issuecomment-548949954
 //
+// Since 3.0: https://github.com/prettier/prettier/pull/15155#issuecomment-1723654981
+//
 // 1. `prettier dir1 dir2` – prettify all files with supported extensions inside `dir1` and `dir2`.
 //
 // 2. `prettier dir1 "dir2/**/*"` – prettify all files with supported extensions inside `dir1`
 //     as well as all files matched by the `dir2/**/*` glob.
-//     If any of the latter files have unknown extensions – log an error for them. (*)
+//     Unsupported files are ignored as with `--ignore-unknown`.
 //
 // 3. `prettier non-exists-dir "dir2/**/*""` – log an error that `non-exists-dir` resulted in 0 files
 //     and prettify all files matched by the `dir2/**/*` glob.
-//     If any of the latter files have unknown extensions – log an error for them. (*)
+//     Unsupported files are ignored as with `--ignore-unknown`.
 //     (Note: ESLint just prints an error and doesn't process anything.)
 //
 // 4. `prettier . "dir2/**/*"` – prettify all files with supported extensions in `.`
 //     and all files matched by the `dir2/**/*` glob.
-//     If any of the latter files have unknown extensions – log an error for them. (*)
+//     Unsupported files are ignored as with `--ignore-unknown`.
 //
 // (*) That error ("No parser could be inferred for file") doesn't affect the error code.
 
@@ -40,7 +43,7 @@ testPatterns("1a - with *.foo plugin", [
 testPatterns("1b - special characters in dir name", ["dir1", "!dir"], {
   stdout: expect.stringMatching(/!dir[/\\]a\.js/),
 });
-testPatterns("1c", ["dir1", "empty"], { status: 2 });
+testPatterns("1c", ["dir1", "empty"], { status: 1 });
 
 testPatterns("2", ["dir1", "dir2/**/*"], { status: 1 });
 
diff --git a/tests/integration/cli/infer-plugins-ext-dir-with-complex-overrides/.prettierrc.mjs b/tests/integration/cli/infer-plugins-ext-dir-with-complex-overrides/.prettierrc.mjs
new file mode 100644
--- /dev/null
+++ b/tests/integration/cli/infer-plugins-ext-dir-with-complex-overrides/.prettierrc.mjs
@@ -0,0 +1,10 @@
+export default {
+  overrides: [
+    {
+      files: ["dir/*.foo"],
+      options: {
+        plugins: ["../../plugins/extensions/plugin.cjs"]
+      }
+    },
+  ]
+};
diff --git a/tests/integration/cli/infer-plugins-ext-dir-with-complex-overrides/1.foo b/tests/integration/cli/infer-plugins-ext-dir-with-complex-overrides/1.foo
new file mode 100644
--- /dev/null
+++ b/tests/integration/cli/infer-plugins-ext-dir-with-complex-overrides/1.foo
@@ -0,0 +1 @@
+1.foo
diff --git a/tests/integration/cli/infer-plugins-ext-dir-with-complex-overrides/dir/2.foo b/tests/integration/cli/infer-plugins-ext-dir-with-complex-overrides/dir/2.foo
new file mode 100644
--- /dev/null
+++ b/tests/integration/cli/infer-plugins-ext-dir-with-complex-overrides/dir/2.foo
@@ -0,0 +1 @@
+2.foo
diff --git a/tests/integration/cli/infer-plugins-ext-dir-with-config/foo.mjs b/tests/integration/cli/infer-plugins-ext-dir-with-config/foo.mjs
new file mode 100644
--- /dev/null
+++ b/tests/integration/cli/infer-plugins-ext-dir-with-config/foo.mjs
@@ -0,0 +1,3 @@
+export default {
+  plugins: ["../../plugins/extensions/plugin.cjs"],
+};
diff --git a/tests/integration/cli/infer-plugins-ext-dir-with-config/src/file.foo b/tests/integration/cli/infer-plugins-ext-dir-with-config/src/file.foo
new file mode 100644
--- /dev/null
+++ b/tests/integration/cli/infer-plugins-ext-dir-with-config/src/file.foo
@@ -0,0 +1 @@
+contents
diff --git a/tests/integration/cli/infer-plugins-ext-dir-with-default-options/.prettierrc.mjs b/tests/integration/cli/infer-plugins-ext-dir-with-default-options/.prettierrc.mjs
new file mode 100644
--- /dev/null
+++ b/tests/integration/cli/infer-plugins-ext-dir-with-default-options/.prettierrc.mjs
@@ -0,0 +1,3 @@
+export default {
+  plugins: ["../../plugins/defaultOptions/plugin.cjs"]
+};
diff --git a/tests/integration/cli/infer-plugins-ext-dir-with-default-options/src/file.foo b/tests/integration/cli/infer-plugins-ext-dir-with-default-options/src/file.foo
new file mode 100644
--- /dev/null
+++ b/tests/integration/cli/infer-plugins-ext-dir-with-default-options/src/file.foo
@@ -0,0 +1 @@
+contents
diff --git a/tests/integration/cli/infer-plugins-ext-dir-with-default-options/src/index.js b/tests/integration/cli/infer-plugins-ext-dir-with-default-options/src/index.js
new file mode 100644
--- /dev/null
+++ b/tests/integration/cli/infer-plugins-ext-dir-with-default-options/src/index.js
@@ -0,0 +1,3 @@
+function main() {
+    console.log("Hello, World!");
+}
diff --git a/tests/integration/cli/infer-plugins-ext-dir-with-overrides-and-default-options/.prettierrc.mjs b/tests/integration/cli/infer-plugins-ext-dir-with-overrides-and-default-options/.prettierrc.mjs
new file mode 100644
--- /dev/null
+++ b/tests/integration/cli/infer-plugins-ext-dir-with-overrides-and-default-options/.prettierrc.mjs
@@ -0,0 +1,10 @@
+export default {
+  overrides: [
+    {
+      files: ["*.foo"],
+      options: {
+        plugins: ["../../plugins/defaultOptions/plugin.cjs"]
+      }
+    }
+  ]
+};
diff --git a/tests/integration/cli/infer-plugins-ext-dir-with-overrides-and-default-options/src/file.foo b/tests/integration/cli/infer-plugins-ext-dir-with-overrides-and-default-options/src/file.foo
new file mode 100644
--- /dev/null
+++ b/tests/integration/cli/infer-plugins-ext-dir-with-overrides-and-default-options/src/file.foo
@@ -0,0 +1 @@
+contents
diff --git a/tests/integration/cli/infer-plugins-ext-dir-with-overrides-and-default-options/src/index.js b/tests/integration/cli/infer-plugins-ext-dir-with-overrides-and-default-options/src/index.js
new file mode 100644
--- /dev/null
+++ b/tests/integration/cli/infer-plugins-ext-dir-with-overrides-and-default-options/src/index.js
@@ -0,0 +1,3 @@
+function main() {
+    console.log("Hello, World!");
+}
diff --git a/tests/integration/cli/infer-plugins-ext-dir-with-overrides/.prettierrc.mjs b/tests/integration/cli/infer-plugins-ext-dir-with-overrides/.prettierrc.mjs
new file mode 100644
--- /dev/null
+++ b/tests/integration/cli/infer-plugins-ext-dir-with-overrides/.prettierrc.mjs
@@ -0,0 +1,16 @@
+export default {
+  overrides: [
+    {
+      files: ["*.foo"],
+      options: {
+        plugins: ["../../plugins/extensions/plugin.cjs"]
+      }
+    },
+    {
+      files: ["*.bar"],
+      options: {
+        plugins: ["../../plugins/extensions2/plugin.cjs"]
+      }
+    }
+  ]
+};
diff --git a/tests/integration/cli/infer-plugins-ext-dir-with-overrides/src/file.bar b/tests/integration/cli/infer-plugins-ext-dir-with-overrides/src/file.bar
new file mode 100644
--- /dev/null
+++ b/tests/integration/cli/infer-plugins-ext-dir-with-overrides/src/file.bar
@@ -0,0 +1 @@
+contents
diff --git a/tests/integration/cli/infer-plugins-ext-dir-with-overrides/src/file.foo b/tests/integration/cli/infer-plugins-ext-dir-with-overrides/src/file.foo
new file mode 100644
--- /dev/null
+++ b/tests/integration/cli/infer-plugins-ext-dir-with-overrides/src/file.foo
@@ -0,0 +1 @@
+contents
diff --git a/tests/integration/cli/infer-plugins-ext-dir/.prettierrc.mjs b/tests/integration/cli/infer-plugins-ext-dir/.prettierrc.mjs
new file mode 100644
--- /dev/null
+++ b/tests/integration/cli/infer-plugins-ext-dir/.prettierrc.mjs
@@ -0,0 +1,3 @@
+export default {
+  plugins: ["../../plugins/extensions/plugin.cjs"],
+};
diff --git a/tests/integration/cli/infer-plugins-ext-dir/src/file.foo b/tests/integration/cli/infer-plugins-ext-dir/src/file.foo
new file mode 100644
--- /dev/null
+++ b/tests/integration/cli/infer-plugins-ext-dir/src/file.foo
@@ -0,0 +1 @@
+contents
diff --git a/tests/integration/cli/infer-plugins-with-multiple-config/dir/.prettierrc.mjs b/tests/integration/cli/infer-plugins-with-multiple-config/dir/.prettierrc.mjs
new file mode 100644
--- /dev/null
+++ b/tests/integration/cli/infer-plugins-with-multiple-config/dir/.prettierrc.mjs
@@ -0,0 +1 @@
+export default { };
diff --git a/tests/integration/cli/infer-plugins-with-multiple-config/dir/1.foo b/tests/integration/cli/infer-plugins-with-multiple-config/dir/1.foo
new file mode 100644
--- /dev/null
+++ b/tests/integration/cli/infer-plugins-with-multiple-config/dir/1.foo
@@ -0,0 +1 @@
+1.foo
diff --git a/tests/integration/cli/infer-plugins-with-multiple-config/dir/subdir/.prettierrc.mjs b/tests/integration/cli/infer-plugins-with-multiple-config/dir/subdir/.prettierrc.mjs
new file mode 100644
--- /dev/null
+++ b/tests/integration/cli/infer-plugins-with-multiple-config/dir/subdir/.prettierrc.mjs
@@ -0,0 +1,3 @@
+export default {
+  plugins: ["../../../../plugins/defaultOptions/plugin.cjs"]
+}
diff --git a/tests/integration/cli/infer-plugins-with-multiple-config/dir/subdir/2.foo b/tests/integration/cli/infer-plugins-with-multiple-config/dir/subdir/2.foo
new file mode 100644
--- /dev/null
+++ b/tests/integration/cli/infer-plugins-with-multiple-config/dir/subdir/2.foo
@@ -0,0 +1 @@
+2.foo
diff --git a/tests/integration/plugins/extensions2/plugin.cjs b/tests/integration/plugins/extensions2/plugin.cjs
new file mode 100644
--- /dev/null
+++ b/tests/integration/plugins/extensions2/plugin.cjs
@@ -0,0 +1,24 @@
+"use strict";
+
+module.exports = {
+  languages: [
+    {
+      name: "bar",
+      parsers: ["bar-parser"],
+      extensions: [".bar"],
+    },
+  ],
+  parsers: {
+    "bar-parser": {
+      parse: (text) => ({ text }),
+      astFormat: "bar-ast",
+    },
+  },
+  printers: {
+    "bar-ast": {
+      print(path) {
+        return ["!", path.getValue().text];
+      },
+    },
+  },
+};