From b23395af07fa88b5a866f3cf97f6b97f60fb1a0d Mon Sep 17 00:00:00 2001 From: camc314 <18101008+camc314@users.noreply.github.com> Date: Mon, 2 Feb 2026 15:30:50 +0000 Subject: [PATCH] feat(linter): enforce exporting an object with `defineConfig` (#18858) This isn't technically needed at this point in time, but if we consider the following, when we have support for extends - config A extends config B (lets assume this in DIR A) - config B specifies a JS plugin (relative path pointing to DIR C) Without using defineConfig, and setting the paths, we have no way of knowing the directory of B. By enforcing the use of defineConfig, we can mutate the config, to add the config dir property so the relative paths are resolved properly --- apps/oxlint/src-js/js_config.ts | 7 +++++++ apps/oxlint/src-js/package/config.ts | 9 +++++++++ .../fixtures/js_config_basic/oxlint.config.ts | 6 ++++-- .../files/oxlint.config.ts | 3 +++ .../js_config_missing_define_config/files/test.js | 1 + .../output.snap.md | 15 +++++++++++++++ .../files/nested/oxlint.config.ts | 4 +++- .../fixtures/js_config_overrides/oxlint.config.ts | 6 ++++-- .../fixtures/js_config_throws_err/output.snap.md | 2 +- .../js_config_throws_err/oxlint.config.ts | 4 +++- 10 files changed, 50 insertions(+), 7 deletions(-) create mode 100644 apps/oxlint/test/fixtures/js_config_missing_define_config/files/oxlint.config.ts create mode 100644 apps/oxlint/test/fixtures/js_config_missing_define_config/files/test.js create mode 100644 apps/oxlint/test/fixtures/js_config_missing_define_config/output.snap.md diff --git a/apps/oxlint/src-js/js_config.ts b/apps/oxlint/src-js/js_config.ts index 6408d0dca0709..6ddcc43073b63 100644 --- a/apps/oxlint/src-js/js_config.ts +++ b/apps/oxlint/src-js/js_config.ts @@ -1,4 +1,5 @@ import { getErrorMessage } from "./utils/utils.ts"; +import { isDefineConfig } from "./package/config.ts"; import { JSONStringify } from "./utils/globals.ts"; interface JsConfigResult { @@ -36,6 +37,12 @@ export async function loadJsConfigs(paths: string[]): Promise { throw new Error(`Configuration file must have a default export that is an object.`); } + if (!isDefineConfig(config)) { + throw new Error( + `Configuration file must wrap its default export with defineConfig() from "oxlint".`, + ); + } + return { path, config }; }), ); diff --git a/apps/oxlint/src-js/package/config.ts b/apps/oxlint/src-js/package/config.ts index 75f2858c63232..ce706226e5b61 100644 --- a/apps/oxlint/src-js/package/config.ts +++ b/apps/oxlint/src-js/package/config.ts @@ -34,6 +34,8 @@ export type OxlintConfig = Oxlintrc; export type { OxlintOverride }; +const DEFINE_CONFIG_REGISTRY = new WeakSet(); + /** * Define an Oxlint configuration with type inference. * @@ -41,5 +43,12 @@ export type { OxlintOverride }; * @returns Config unchanged */ export function defineConfig(config: T): T { + DEFINE_CONFIG_REGISTRY.add(config as object); return config; } + +export function isDefineConfig(config: unknown): boolean { + return ( + typeof config === "object" && config !== null && DEFINE_CONFIG_REGISTRY.has(config as object) + ); +} diff --git a/apps/oxlint/test/fixtures/js_config_basic/oxlint.config.ts b/apps/oxlint/test/fixtures/js_config_basic/oxlint.config.ts index eb3b55a5f1e73..79e377b8b3e66 100644 --- a/apps/oxlint/test/fixtures/js_config_basic/oxlint.config.ts +++ b/apps/oxlint/test/fixtures/js_config_basic/oxlint.config.ts @@ -1,7 +1,9 @@ // Basic test for oxlint.config.ts support -export default { +import { defineConfig } from "#oxlint"; + +export default defineConfig({ rules: { "no-debugger": "error", eqeqeq: "warn", }, -}; +}); diff --git a/apps/oxlint/test/fixtures/js_config_missing_define_config/files/oxlint.config.ts b/apps/oxlint/test/fixtures/js_config_missing_define_config/files/oxlint.config.ts new file mode 100644 index 0000000000000..4f00b08c16ffa --- /dev/null +++ b/apps/oxlint/test/fixtures/js_config_missing_define_config/files/oxlint.config.ts @@ -0,0 +1,3 @@ +export default { + rules: {}, +}; diff --git a/apps/oxlint/test/fixtures/js_config_missing_define_config/files/test.js b/apps/oxlint/test/fixtures/js_config_missing_define_config/files/test.js new file mode 100644 index 0000000000000..4f252c003683a --- /dev/null +++ b/apps/oxlint/test/fixtures/js_config_missing_define_config/files/test.js @@ -0,0 +1 @@ +console.log("ok"); diff --git a/apps/oxlint/test/fixtures/js_config_missing_define_config/output.snap.md b/apps/oxlint/test/fixtures/js_config_missing_define_config/output.snap.md new file mode 100644 index 0000000000000..dc461dcfb657a --- /dev/null +++ b/apps/oxlint/test/fixtures/js_config_missing_define_config/output.snap.md @@ -0,0 +1,15 @@ +# Exit code +1 + +# stdout +``` +Failed to parse oxlint configuration file. + + x Failed to load config: /files/oxlint.config.ts + | + | Error: Configuration file must wrap its default export with defineConfig() from "oxlint". +``` + +# stderr +``` +``` diff --git a/apps/oxlint/test/fixtures/js_config_nested_conflict/files/nested/oxlint.config.ts b/apps/oxlint/test/fixtures/js_config_nested_conflict/files/nested/oxlint.config.ts index 08717a09a7300..aeaeb12258072 100644 --- a/apps/oxlint/test/fixtures/js_config_nested_conflict/files/nested/oxlint.config.ts +++ b/apps/oxlint/test/fixtures/js_config_nested_conflict/files/nested/oxlint.config.ts @@ -1 +1,3 @@ -export default { rules: {} }; +import { defineConfig } from "#oxlint"; + +export default defineConfig({ rules: {} }); diff --git a/apps/oxlint/test/fixtures/js_config_overrides/oxlint.config.ts b/apps/oxlint/test/fixtures/js_config_overrides/oxlint.config.ts index 6bac6874e56e0..298e7158b1bd2 100644 --- a/apps/oxlint/test/fixtures/js_config_overrides/oxlint.config.ts +++ b/apps/oxlint/test/fixtures/js_config_overrides/oxlint.config.ts @@ -1,5 +1,7 @@ // Test that overrides work in oxlint.config.ts -export default { +import { defineConfig } from "#oxlint"; + +export default defineConfig({ rules: { "no-debugger": "off", }, @@ -11,4 +13,4 @@ export default { }, }, ], -}; +}); diff --git a/apps/oxlint/test/fixtures/js_config_throws_err/output.snap.md b/apps/oxlint/test/fixtures/js_config_throws_err/output.snap.md index 815872f706b75..7c29c1dd7a86a 100644 --- a/apps/oxlint/test/fixtures/js_config_throws_err/output.snap.md +++ b/apps/oxlint/test/fixtures/js_config_throws_err/output.snap.md @@ -8,7 +8,7 @@ Failed to parse oxlint configuration file. x Failed to load config: /oxlint.config.ts | | Error: This is a test error - | at /oxlint.config.ts:1:7 + | at /oxlint.config.ts:3:7 ``` # stderr diff --git a/apps/oxlint/test/fixtures/js_config_throws_err/oxlint.config.ts b/apps/oxlint/test/fixtures/js_config_throws_err/oxlint.config.ts index ddd4800bd9c4a..032ce2c00b4e4 100644 --- a/apps/oxlint/test/fixtures/js_config_throws_err/oxlint.config.ts +++ b/apps/oxlint/test/fixtures/js_config_throws_err/oxlint.config.ts @@ -1,3 +1,5 @@ +import { defineConfig } from "#oxlint"; + throw new Error("This is a test error"); -export default {}; +export default defineConfig({});