Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions apps/oxfmt/src-js/cli/js_config.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -13,12 +19,26 @@ export async function loadJsConfig(path: string): Promise<object> {
// 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<string, unknown>)[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;
}
16 changes: 0 additions & 16 deletions apps/oxfmt/src/core/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Item = String> {
Expand Down Expand Up @@ -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)?;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,18 @@ Finished in <variable>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 <variable>ms on 1 files using 1 threads.
--- STDERR ---------
Failed to load configuration file.
<cwd>/vite.config.ts
Expected a \`fmt\` field in the default export.

--------------------"
`;

Expand Down
4 changes: 2 additions & 2 deletions apps/oxfmt/test/cli/vite_config/vite_config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
Expand Down
5 changes: 4 additions & 1 deletion apps/oxlint/src-js/js_config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,10 @@ export async function loadJsConfigs(paths: string[]): Promise<string> {
}

if (pathBasename(path) === VITE_CONFIG_NAME) {
config = (config as Record<string, unknown>)[VITE_OXLINT_CONFIG_FIELD] ?? {};
config =
VITE_OXLINT_CONFIG_FIELD in config
? (config as Record<string, unknown>)[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.`,
Expand Down
Loading