Skip to content
Merged
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
32 changes: 29 additions & 3 deletions apps/oxlint/src/config_loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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<Option<Oxlintrc>, 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<Oxlintrc, OxcDiagnostic> {
match self.load_js_configs(&[path.to_path_buf()]) {
Ok(mut configs) => Ok(configs.pop().unwrap_or_default()),
Expand Down
2 changes: 2 additions & 0 deletions apps/oxlint/src/js_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ fn parse_js_config_response(json: &str) -> Result<Vec<JsConfigResult>, Vec<OxcDi
if let Some(v) = entry.config.get(VITE_OXLINT_CONFIG_FIELD).cloned() {
v
} else {
// NOTE: This error message is shown to users (explicit `--config`) and also
// matched by `try_load_root_vite_config` in config_loader.rs (auto-discovery skip).
errors.push(OxcDiagnostic::error(format!(
"Expected a `{VITE_OXLINT_CONFIG_FIELD}` field in the default export of {}",
entry.path
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
debugger;
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"singleThread": true,
"args": ["--config", "vite.config.ts"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Exit code
1

# stdout
```
Failed to parse oxlint configuration file.

x Expected a `lint` field in the default export of <fixture>/vite.config.ts
```

# stderr
```
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
plugins: [],
};
Original file line number Diff line number Diff line change
@@ -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 <fixture>/vite.config.ts
Found 1 warning and 0 errors.
Finished in Xms on 1 file with 93 rules using X threads.
```

# stderr
Expand Down
19 changes: 19 additions & 0 deletions apps/oxlint/test/lsp/lint/__snapshots__/lint.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"rules": {
"no-debugger": "error",
"eqeqeq": "warn"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
debugger;
if (x == 1) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
plugins: [],
};
1 change: 1 addition & 0 deletions apps/oxlint/test/lsp/lint/lint.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
Expand Down
Loading