diff --git a/crates/oxc_language_server/src/main.rs b/crates/oxc_language_server/src/main.rs index bcbaa8f128c21..a20a45a26f4f6 100644 --- a/crates/oxc_language_server/src/main.rs +++ b/crates/oxc_language_server/src/main.rs @@ -1,14 +1,14 @@ mod linter; -use std::{fmt::Debug, path::PathBuf, str::FromStr}; - use dashmap::DashMap; use futures::future::join_all; use globset::Glob; use ignore::gitignore::Gitignore; -use log::{debug, error, info}; +use log::{debug, error, info, warn}; use oxc_linter::{FixKind, LinterBuilder, Oxlintrc}; use serde::{Deserialize, Serialize}; +use std::path::{Path, PathBuf}; +use std::{fmt::Debug, str::FromStr}; use tokio::sync::{Mutex, OnceCell, RwLock, SetError}; use tower_lsp::{ jsonrpc::{Error, ErrorCode, Result}, @@ -47,12 +47,12 @@ enum Run { struct Options { run: Run, enable: bool, - config_path: String, + config_path: Option, } impl Default for Options { fn default() -> Self { - Self { enable: true, run: Run::default(), config_path: ".eslintrc".into() } + Self { enable: true, run: Run::default(), config_path: None } } } @@ -68,11 +68,44 @@ impl Options { } } - fn get_config_path(&self) -> Option { - if self.config_path.is_empty() { + fn get_config_path(&self, root_path: &Path) -> Option { + let Some(config_path) = &self.config_path else { + // no config file is provided, let search in the root_path for one + let search_configs = vec![ + "oxlintrc.json", + "oxlint.json", + ".oxlintrc.json", + ".oxlint.json", + ".oxlintrc", + ".eslintrc", + ".eslintrc.json", + ]; + + for search_config in search_configs { + let config_path = root_path.join(search_config); + + if config_path.exists() { + info!("Automatically detected config {config_path:?}"); + return Some(config_path); + } + } + + return None; + }; + + // config path is provided and is empty, e.g "oxc.configPath": "" + if config_path.is_empty() { None } else { - Some(PathBuf::from(&self.config_path)) + // provided config path is not empty + let config_path = root_path.join(config_path); + + if config_path.exists() { + return Some(config_path); + } + + warn!("could not find oxlint config file {:?}", config_path); + None } } } @@ -362,11 +395,9 @@ impl Backend { let Ok(root_path) = uri.to_file_path() else { return; }; - let mut config_path = None; - let config = root_path.join(self.options.lock().await.get_config_path().unwrap()); - if config.exists() { - config_path = Some(config); - } + + let config_path = self.options.lock().await.get_config_path(&root_path); + if let Some(config_path) = config_path { let mut linter = self.server_linter.write().await; *linter = ServerLinter::new_with_linter( diff --git a/editors/vscode/client/config.ts b/editors/vscode/client/config.ts index d2eabe94ec753..528e011a15ebf 100644 --- a/editors/vscode/client/config.ts +++ b/editors/vscode/client/config.ts @@ -8,7 +8,7 @@ export class ConfigService implements Config, IDisposable { private _runTrigger!: Trigger; private _enable!: boolean; private _trace!: TraceLevel; - private _configPath!: string; + private _configPath!: string | undefined; private _binPath: string | undefined; public onConfigChange: @@ -31,7 +31,7 @@ export class ConfigService implements Config, IDisposable { this._runTrigger = this._inner.get('lint.run') || 'onType'; this._enable = this._inner.get('enable') ?? true; this._trace = this._inner.get('trace.server') || 'off'; - this._configPath = this._inner.get('configPath') || '.eslintrc'; + this._configPath = this._inner.get('configPath'); this._binPath = this._inner.get('path.server'); } @@ -68,11 +68,11 @@ export class ConfigService implements Config, IDisposable { .update('trace.server', value); } - get configPath(): string { + get configPath(): string | undefined { return this._configPath; } - set configPath(value: string) { + set configPath(value: string | undefined) { this._configPath = value; workspace .getConfiguration(ConfigService._namespace) @@ -116,7 +116,7 @@ type Trigger = 'onSave' | 'onType'; type TraceLevel = 'off' | 'messages' | 'verbose'; interface LanguageServerConfig { - configPath: string; + configPath: string | undefined; enable: boolean; run: Trigger; } @@ -150,9 +150,9 @@ interface Config { * * `oxc.configPath` * - * @default ".eslintrc" + * @default undefined */ - configPath: string; + configPath: string | undefined; /** * Path to LSP binary * `oxc.path.server` diff --git a/editors/vscode/package.json b/editors/vscode/package.json index ae7f70f04c774..5fd8bd9e04378 100644 --- a/editors/vscode/package.json +++ b/editors/vscode/package.json @@ -90,10 +90,10 @@ "description": "Traces the communication between VS Code and the language server." }, "oxc.configPath": { - "type": "string", + "type": ["string", "null"], "scope": "window", - "default": ".eslintrc", - "description": "Path to ESlint configuration." + "default": null, + "description": "Path to oxlint configuration." }, "oxc.path.server": { "type": "string",