From 4b0f279d723e86942ccacb467ea761e264aaec95 Mon Sep 17 00:00:00 2001 From: bluwy Date: Thu, 13 Aug 2026 16:20:09 +0800 Subject: [PATCH] Allow formatting unmatched patterns --- .changeset/cold-steaks-trade.md | 5 +++++ README.md | 2 +- src/format.ts | 3 ++- src/formatters.ts | 22 +++++++++++++++++----- tests/fixtures.test.ts | 17 ++++------------- tests/format.test.ts | 29 ++++++++++++++++++++++++----- 6 files changed, 53 insertions(+), 25 deletions(-) create mode 100644 .changeset/cold-steaks-trade.md diff --git a/.changeset/cold-steaks-trade.md b/.changeset/cold-steaks-trade.md new file mode 100644 index 0000000..6ed7dc3 --- /dev/null +++ b/.changeset/cold-steaks-trade.md @@ -0,0 +1,5 @@ +--- +"@changesets/format": patch +--- + +Allow `format()` to pass even if the patterns didn't match any files diff --git a/README.md b/README.md index 2da1183..0031bc2 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Options can be passed to the first parameter: ### `format()` -Format the given patterns with a specified or auto-detected formatter. It returns `true` if a formatter is found and the formatting process passes, otherwise returns `false`. +Format the given patterns with a specified or auto-detected formatter. It returns `true` if a formatter is found and the formatting process passes (even if the patterns didn't match any files), otherwise returns `false`. diff --git a/src/format.ts b/src/format.ts index a1115b7..8849ed5 100644 --- a/src/format.ts +++ b/src/format.ts @@ -29,7 +29,8 @@ export interface FormatOptions { /** * Format the given patterns with a specified or auto-detected formatter. It returns `true` if a - * formatter is found and the formatting process passes, otherwise returns `false`. + * formatter is found and the formatting process passes (even if the patterns didn't match any + * files), otherwise returns `false`. */ export async function format(patterns: string[], options: FormatOptions = {}): Promise { const formatterName = diff --git a/src/formatters.ts b/src/formatters.ts index e281f4a..029b6f0 100644 --- a/src/formatters.ts +++ b/src/formatters.ts @@ -58,7 +58,11 @@ const prettier: Formatter = { ], async format(files, ctx) { const pm = await ctx.getPackageManager(); - await packageManagerExecute(pm, ["prettier", "--write", ...files], ctx.cwd); + await packageManagerExecute( + pm, + ["prettier", "--write", "--no-error-on-unmatched-pattern", ...files], + ctx.cwd, + ); }, }; @@ -69,7 +73,11 @@ const biome: Formatter = { configFiles: ["biome.json", "biome.jsonc", ".biome.json", ".biome.jsonc"], async format(files, ctx) { const pm = await ctx.getPackageManager(); - await packageManagerExecute(pm, ["@biomejs/biome", "format", "--write", ...files], ctx.cwd); + await packageManagerExecute( + pm, + ["@biomejs/biome", "format", "--write", "--no-errors-on-unmatched", ...files], + ctx.cwd, + ); }, }; @@ -80,7 +88,11 @@ const oxfmt: Formatter = { configFiles: [".oxfmtrc.json", ".oxfmtrc.jsonc", "oxfmt.config.ts", "oxfmt.config.mts"], async format(files, ctx) { const pm = await ctx.getPackageManager(); - await packageManagerExecute(pm, ["oxfmt", "--write", ...files], ctx.cwd); + await packageManagerExecute( + pm, + ["oxfmt", "--write", "--no-error-on-unmatched-pattern", ...files], + ctx.cwd, + ); }, }; @@ -89,7 +101,7 @@ const deno: Formatter = { // https://docs.deno.com/runtime/reference/cli/fmt/#configuring-the-formatter configFiles: ["deno.json", "deno.jsonc", { file: "deno.json", key: "fmt" }], async format(files, ctx) { - await spawnProcess("deno", ["fmt", ...files], ctx.cwd); + await spawnProcess("deno", ["fmt", "--permit-no-files", ...files], ctx.cwd); }, }; @@ -102,7 +114,7 @@ const dprint: Formatter = { // NOTE: dprint could be installed in many ways globally, but for all the tools here, we assume // that they're installed locally in node_modules for now. const pm = await ctx.getPackageManager(); - await packageManagerExecute(pm, ["dprint", "fmt", ...files], ctx.cwd); + await packageManagerExecute(pm, ["dprint", "fmt", "--allow-no-files", ...files], ctx.cwd); }, }; diff --git a/tests/fixtures.test.ts b/tests/fixtures.test.ts index b7c2f57..a2e4f30 100644 --- a/tests/fixtures.test.ts +++ b/tests/fixtures.test.ts @@ -13,16 +13,6 @@ function canRunDeno() { }); } -// dprint requires an internet connection to download plugins (on the first run) -async function canRunDprint() { - try { - await dns.lookup("plugins.dprint.dev"); - return true; - } catch { - return false; - } -} - const cases: FormatterName[] = ["biome", "deno", "dprint", "oxfmt", "prettier"]; test.for(cases)("detect and format %s fixture", { timeout: 10000 }, async (name, ctx) => { @@ -32,9 +22,6 @@ test.for(cases)("detect and format %s fixture", { timeout: 10000 }, async (name, if (name === "deno" && !(await canRunDeno())) { ctx.skip("deno is not installed"); } - if (name === "dprint" && !(await canRunDprint())) { - ctx.skip("no internet connection for dprint plugin download"); - } } const fixtureTemplateDir = path.join(import.meta.dirname, "fixtures", name); @@ -47,4 +34,8 @@ test.for(cases)("detect and format %s fixture", { timeout: 10000 }, async (name, const formatResult = await format(["file.ts"], { cwd: fixture.path, formatter: name }); expect(formatResult).toBe(true); expect(await fixture.readFile("file.ts", "utf-8")).toMatchSnapshot(); + + // Formatting a missing file should be fine + const formatMissingResult = await format(["missing.ts"], { cwd: fixture.path, formatter: name }); + expect(formatMissingResult).toBe(true); }); diff --git a/tests/format.test.ts b/tests/format.test.ts index 5f20dba..a045e86 100644 --- a/tests/format.test.ts +++ b/tests/format.test.ts @@ -9,14 +9,33 @@ const mocks = vi.hoisted(() => ({ vi.mock(import("tinyexec"), () => ({ exec: mocks.exec })); const cases: { formatter: FormatterName; expectedCommand: string[] }[] = [ - { formatter: "prettier", expectedCommand: ["npx", "prettier", "--write", "file.ts"] }, + { + formatter: "prettier", + expectedCommand: ["npx", "prettier", "--write", "--no-error-on-unmatched-pattern", "file.ts"], + }, { formatter: "biome", - expectedCommand: ["npx", "@biomejs/biome", "format", "--write", "file.ts"], + expectedCommand: [ + "npx", + "@biomejs/biome", + "format", + "--write", + "--no-errors-on-unmatched", + "file.ts", + ], + }, + { + formatter: "oxfmt", + expectedCommand: ["npx", "oxfmt", "--write", "--no-error-on-unmatched-pattern", "file.ts"], + }, + { + formatter: "deno", + expectedCommand: ["deno", "fmt", "--permit-no-files", "file.ts"], + }, + { + formatter: "dprint", + expectedCommand: ["npx", "dprint", "fmt", "--allow-no-files", "file.ts"], }, - { formatter: "oxfmt", expectedCommand: ["npx", "oxfmt", "--write", "file.ts"] }, - { formatter: "deno", expectedCommand: ["deno", "fmt", "file.ts"] }, - { formatter: "dprint", expectedCommand: ["npx", "dprint", "fmt", "file.ts"] }, ]; test.for(cases)("executes the correct command for $formatter", async (c) => {