diff --git a/apps/oxfmt/src-js/cli/js_config.ts b/apps/oxfmt/src-js/cli/js_config.ts index 9798b563894c2..1fc9b488e4dae 100644 --- a/apps/oxfmt/src-js/cli/js_config.ts +++ b/apps/oxfmt/src-js/cli/js_config.ts @@ -1,10 +1,16 @@ +import { basename as pathBasename } from "node:path"; import { pathToFileURL } from "node:url"; +const VITE_CONFIG_NAME = "vite.config.ts"; +const VITE_OXFMT_CONFIG_FIELD = "fmt"; + /** * Load a JavaScript/TypeScript config file. * * Uses native Node.js `import()` to evaluate the config file. - * The config file should have a default export containing the oxfmt configuration object. + * For: + * - oxfmt.config.ts files, the entire default export is used as the config object. + * - vite.config.ts files, the config is read from the `fmt` field of the default export. If the `fmt` field is missing, an empty object is used as the config. * * @param path - Absolute path to the JavaScript/TypeScript config file * @returns Config object @@ -13,12 +19,26 @@ export async function loadJsConfig(path: string): Promise { // Bypass Node.js module cache to allow reloading changed config files (used for LSP) const fileUrl = pathToFileURL(path); fileUrl.searchParams.set("cache", Date.now().toString()); - const { default: config } = await import(fileUrl.href); + const { default: rawConfig } = await import(fileUrl.href); + + let config = rawConfig; if (config === undefined) throw new Error(`Configuration file has no default export: ${path}`); if (typeof config !== "object" || config === null || Array.isArray(config)) { throw new Error(`Configuration file must have a default export that is an object: ${path}`); } + if (pathBasename(path) === VITE_CONFIG_NAME) { + config = + VITE_OXFMT_CONFIG_FIELD in config + ? (config as Record)[VITE_OXFMT_CONFIG_FIELD] + : {}; + if (typeof config !== "object" || config === null || Array.isArray(config)) { + throw new Error( + `The \`${VITE_OXFMT_CONFIG_FIELD}\` field in the default export must be an object: ${path}`, + ); + } + } + return config; } diff --git a/apps/oxfmt/src/core/config.rs b/apps/oxfmt/src/core/config.rs index b556a2a4595bf..d8320186521f1 100644 --- a/apps/oxfmt/src/core/config.rs +++ b/apps/oxfmt/src/core/config.rs @@ -32,8 +32,6 @@ const OXFMT_JS_CONFIG_NAME: &str = "oxfmt.config.ts"; /// Vite+ config file name that may contain Oxfmt config under a `.fmt` field. /// Only `.ts` extension is supported, matching oxlint's behavior. const VITE_PLUS_CONFIG_NAME: &str = "vite.config.ts"; -#[cfg(feature = "napi")] -const VITE_PLUS_OXFMT_CONFIG_FIELD: &str = "fmt"; /// Returns an iterator of all supported config file names, in priority order. pub fn all_config_file_names() -> impl Iterator { @@ -269,20 +267,6 @@ impl ConfigResolver { ) })?; - // Vite+ config files (e.g. `vite.config.ts`), - // under a `.fmt` field instead of the default export directly. - let is_vite_plus = path - .file_name() - .and_then(|f| f.to_str()) - .is_some_and(|name| name == VITE_PLUS_CONFIG_NAME); - let raw_config = if is_vite_plus { - raw_config.get(VITE_PLUS_OXFMT_CONFIG_FIELD).cloned().ok_or_else(|| { - format!("{}\nExpected a `{VITE_PLUS_OXFMT_CONFIG_FIELD}` field in the default export.", path.display()) - })? - } else { - raw_config - }; - let config_dir = path.parent().map(Path::to_path_buf); let editorconfig = load_editorconfig(cwd, editorconfig_path)?; diff --git a/apps/oxfmt/test/cli/vite_config/__snapshots__/vite_config.test.ts.snap b/apps/oxfmt/test/cli/vite_config/__snapshots__/vite_config.test.ts.snap index 9de7b5a52ca08..9a55372fdc4a8 100644 --- a/apps/oxfmt/test/cli/vite_config/__snapshots__/vite_config.test.ts.snap +++ b/apps/oxfmt/test/cli/vite_config/__snapshots__/vite_config.test.ts.snap @@ -17,17 +17,18 @@ Finished in ms on 1 files using 1 threads. --------------------" `; -exports[`vite_config > error: no fmt field in vite.config.ts 1`] = ` +exports[`vite_config > missing fmt field in vite.config.ts falls back to default config 1`] = ` "-------------------- arguments: --check test.ts -working directory: vite_config/fixtures/error_no_fmt_field -exit code: 1 +working directory: vite_config/fixtures/missing_fmt_field +exit code: 0 --- STDOUT --------- +Checking formatting... +All matched files use the correct format. +Finished in ms on 1 files using 1 threads. --- STDERR --------- -Failed to load configuration file. -/vite.config.ts -Expected a \`fmt\` field in the default export. + --------------------" `; diff --git a/apps/oxfmt/test/cli/vite_config/fixtures/error_no_fmt_field/test.ts b/apps/oxfmt/test/cli/vite_config/fixtures/missing_fmt_field/test.ts similarity index 100% rename from apps/oxfmt/test/cli/vite_config/fixtures/error_no_fmt_field/test.ts rename to apps/oxfmt/test/cli/vite_config/fixtures/missing_fmt_field/test.ts diff --git a/apps/oxfmt/test/cli/vite_config/fixtures/error_no_fmt_field/vite.config.ts b/apps/oxfmt/test/cli/vite_config/fixtures/missing_fmt_field/vite.config.ts similarity index 100% rename from apps/oxfmt/test/cli/vite_config/fixtures/error_no_fmt_field/vite.config.ts rename to apps/oxfmt/test/cli/vite_config/fixtures/missing_fmt_field/vite.config.ts diff --git a/apps/oxfmt/test/cli/vite_config/vite_config.test.ts b/apps/oxfmt/test/cli/vite_config/vite_config.test.ts index 955476a8a7c41..2bd609dbadc48 100644 --- a/apps/oxfmt/test/cli/vite_config/vite_config.test.ts +++ b/apps/oxfmt/test/cli/vite_config/vite_config.test.ts @@ -11,8 +11,8 @@ describe("vite_config", () => { expect(snapshot).toMatchSnapshot(); }); - it("error: no fmt field in vite.config.ts", async () => { - const cwd = join(fixturesDir, "error_no_fmt_field"); + it("missing fmt field in vite.config.ts falls back to default config", async () => { + const cwd = join(fixturesDir, "missing_fmt_field"); const snapshot = await runAndSnapshot(cwd, [["--check", "test.ts"]]); expect(snapshot).toMatchSnapshot(); }); diff --git a/apps/oxlint/src-js/js_config.ts b/apps/oxlint/src-js/js_config.ts index 50cd0aeafb759..926f9ef287148 100644 --- a/apps/oxlint/src-js/js_config.ts +++ b/apps/oxlint/src-js/js_config.ts @@ -111,7 +111,10 @@ export async function loadJsConfigs(paths: string[]): Promise { } if (pathBasename(path) === VITE_CONFIG_NAME) { - config = (config as Record)[VITE_OXLINT_CONFIG_FIELD] ?? {}; + config = + VITE_OXLINT_CONFIG_FIELD in config + ? (config as Record)[VITE_OXLINT_CONFIG_FIELD] + : {}; if (typeof config !== "object" || config === null || Array.isArray(config)) { throw new Error( `The \`${VITE_OXLINT_CONFIG_FIELD}\` field in the default export must be an object.`,