diff --git a/apps/oxlint/src/config_loader.rs b/apps/oxlint/src/config_loader.rs index 98adef789bfee..60ff1d88294d2 100644 --- a/apps/oxlint/src/config_loader.rs +++ b/apps/oxlint/src/config_loader.rs @@ -14,6 +14,7 @@ use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet}; use crate::{ DEFAULT_JSONC_OXLINTRC_NAME, DEFAULT_OXLINTRC_NAME, DEFAULT_TS_OXLINTRC_NAME, VITE_CONFIG_NAME, + VITE_OXLINT_CONFIG_FIELD, }; #[cfg(feature = "napi")] @@ -490,9 +491,12 @@ impl<'a> ConfigLoader<'a> { } // Fallback: check for vite.config.ts with .lint field (lowest priority) - let vite_path = dir.join(VITE_CONFIG_NAME); - if vite_path.is_file() { - return self.load_root_js_config(&vite_path).map(Some); + // If .lint field is missing, skip it and continue config search. + let vite_config_path = dir.join(VITE_CONFIG_NAME); + if vite_config_path.is_file() + && let Some(config) = self.try_load_root_vite_config(&vite_config_path)? + { + return Ok(Some(config)); } Ok(None) @@ -556,6 +560,28 @@ impl<'a> ConfigLoader<'a> { Ok(Oxlintrc::default()) } + /// Try to load vite.config.ts, returning `Ok(None)` if `.lint` field is missing. + /// Other errors (e.g., JS runtime not available, parse errors) are propagated. + fn try_load_root_vite_config(&self, path: &Path) -> Result, OxcDiagnostic> { + match self.load_root_js_config(path) { + Ok(config) => Ok(Some(config)), + Err(diagnostic) => { + let msg = diagnostic.message.to_string(); + // NOTE: This relies on matching the error message from `parse_js_config_response` in js_config.rs. + // If that message changes, this match must be updated accordingly. + if msg.contains(&format!("Expected a `{VITE_OXLINT_CONFIG_FIELD}` field")) { + tracing::debug!( + "Skipping {} (no `{VITE_OXLINT_CONFIG_FIELD}` field), continuing config search...", + path.display(), + ); + Ok(None) + } else { + Err(diagnostic) + } + } + } + } + fn load_root_js_config(&self, path: &Path) -> Result { match self.load_js_configs(&[path.to_path_buf()]) { Ok(mut configs) => Ok(configs.pop().unwrap_or_default()), diff --git a/apps/oxlint/src/js_config.rs b/apps/oxlint/src/js_config.rs index 3b5bb9430ca35..722011241403c 100644 --- a/apps/oxlint/src/js_config.rs +++ b/apps/oxlint/src/js_config.rs @@ -129,6 +129,8 @@ fn parse_js_config_response(json: &str) -> Result, Vec/vite.config.ts +``` + +# stderr +``` +``` diff --git a/apps/oxlint/test/fixtures/vite_config_explicit_no_lint_field/vite.config.ts b/apps/oxlint/test/fixtures/vite_config_explicit_no_lint_field/vite.config.ts new file mode 100644 index 0000000000000..4ed8f62dd39b0 --- /dev/null +++ b/apps/oxlint/test/fixtures/vite_config_explicit_no_lint_field/vite.config.ts @@ -0,0 +1,3 @@ +export default { + plugins: [], +}; diff --git a/apps/oxlint/test/fixtures/vite_config_no_lint_field/output.snap.md b/apps/oxlint/test/fixtures/vite_config_no_lint_field/output.snap.md index 80e33f5608bfe..d5673ed74d025 100644 --- a/apps/oxlint/test/fixtures/vite_config_no_lint_field/output.snap.md +++ b/apps/oxlint/test/fixtures/vite_config_no_lint_field/output.snap.md @@ -1,11 +1,17 @@ # Exit code -1 +0 # stdout ``` -Failed to parse oxlint configuration file. + ! eslint(no-debugger): `debugger` statement is not allowed + ,-[files/test.js:1:1] + 1 | debugger; + : ^^^^^^^^^ + `---- + help: Remove the debugger statement - x Expected a `lint` field in the default export of /vite.config.ts +Found 1 warning and 0 errors. +Finished in Xms on 1 file with 93 rules using X threads. ``` # stderr diff --git a/apps/oxlint/test/lsp/lint/__snapshots__/lint.test.ts.snap b/apps/oxlint/test/lsp/lint/__snapshots__/lint.test.ts.snap index 607b827b4f889..06e695f1c5895 100644 --- a/apps/oxlint/test/lsp/lint/__snapshots__/lint.test.ts.snap +++ b/apps/oxlint/test/lsp/lint/__snapshots__/lint.test.ts.snap @@ -140,6 +140,25 @@ unused-disable-directive-from-config/test.ts --------------------" `; +exports[`LSP linting > config options > should apply config from vite-config-skip-finds-parent/child/test.js 1`] = ` +"--- FILE ----------- +vite-config-skip-finds-parent/child/test.js +--- Diagnostics --------- + 1 | debugger; +> 2 | if (x == 1) { + | ^^ Warning: Expected === and instead saw == +help: Prefer === operator + 3 | } + 4 | +--------------------> 1 | debugger; + | ^^^^^^^^^ Error: \`debugger\` statement is not allowed +help: Remove the debugger statement + 2 | if (x == 1) { + 3 | } + 4 | +--------------------" +`; + exports[`LSP linting > initializationOptions > should use custom config path from configPath 1`] = ` "--- FILE ----------- custom-config-path/test.ts diff --git a/apps/oxlint/test/lsp/lint/fixtures/vite-config-skip-finds-parent/.oxlintrc.json b/apps/oxlint/test/lsp/lint/fixtures/vite-config-skip-finds-parent/.oxlintrc.json new file mode 100644 index 0000000000000..c32cf554d2a7b --- /dev/null +++ b/apps/oxlint/test/lsp/lint/fixtures/vite-config-skip-finds-parent/.oxlintrc.json @@ -0,0 +1,6 @@ +{ + "rules": { + "no-debugger": "error", + "eqeqeq": "warn" + } +} diff --git a/apps/oxlint/test/lsp/lint/fixtures/vite-config-skip-finds-parent/child/test.js b/apps/oxlint/test/lsp/lint/fixtures/vite-config-skip-finds-parent/child/test.js new file mode 100644 index 0000000000000..d4e2f029dd793 --- /dev/null +++ b/apps/oxlint/test/lsp/lint/fixtures/vite-config-skip-finds-parent/child/test.js @@ -0,0 +1,3 @@ +debugger; +if (x == 1) { +} diff --git a/apps/oxlint/test/lsp/lint/fixtures/vite-config-skip-finds-parent/child/vite.config.ts b/apps/oxlint/test/lsp/lint/fixtures/vite-config-skip-finds-parent/child/vite.config.ts new file mode 100644 index 0000000000000..4ed8f62dd39b0 --- /dev/null +++ b/apps/oxlint/test/lsp/lint/fixtures/vite-config-skip-finds-parent/child/vite.config.ts @@ -0,0 +1,3 @@ +export default { + plugins: [], +}; diff --git a/apps/oxlint/test/lsp/lint/lint.test.ts b/apps/oxlint/test/lsp/lint/lint.test.ts index 9018594bbf45d..d1452b1782df1 100644 --- a/apps/oxlint/test/lsp/lint/lint.test.ts +++ b/apps/oxlint/test/lsp/lint/lint.test.ts @@ -24,6 +24,7 @@ describe("LSP linting", () => { ["config-ts-type-aware/test.ts", "typescript"], ["config-ts-nested-type-aware-invalid/nested/test.ts", "typescript"], ["unused-disable-directive-from-config/test.ts", "typescript"], + ["vite-config-skip-finds-parent/child/test.js", "javascript"], ])("should apply config from %s", async (path, languageId) => { expect(await lintFixture(FIXTURES_DIR, path, languageId)).toMatchSnapshot(); });