diff --git a/crates/oxc_language_server/src/formatter/server_formatter.rs b/crates/oxc_language_server/src/formatter/server_formatter.rs index e2d463705c76f..9905934d4fbc4 100644 --- a/crates/oxc_language_server/src/formatter/server_formatter.rs +++ b/crates/oxc_language_server/src/formatter/server_formatter.rs @@ -1,4 +1,4 @@ -use std::path::Path; +use std::path::{Path, PathBuf}; use log::warn; use oxc_allocator::Allocator; @@ -13,7 +13,7 @@ use tower_lsp_server::{ }; use crate::formatter::options::FormatOptions as LSPFormatOptions; -use crate::{FORMAT_CONFIG_FILE, utils::normalize_path}; +use crate::{FORMAT_CONFIG_FILES, utils::normalize_path}; pub struct ServerFormatter { options: FormatOptions, } @@ -77,10 +77,28 @@ impl ServerFormatter { )]) } + fn search_config_file(root_path: &Path, config_path: Option<&String>) -> Option { + if let Some(config_path) = config_path { + let config = normalize_path(root_path.join(config_path)); + if config.try_exists().is_ok_and(|exists| exists) { + return Some(config); + } + + warn!( + "Config file not found: {}, searching for `{}` in the root path", + config.to_string_lossy(), + FORMAT_CONFIG_FILES.join(", ") + ); + } + + FORMAT_CONFIG_FILES.iter().find_map(|&file| { + let config = normalize_path(root_path.join(file)); + config.try_exists().is_ok_and(|exists| exists).then_some(config) + }) + } + fn get_format_options(root_path: &Path, config_path: Option<&String>) -> FormatOptions { - let config_path = config_path.map_or(FORMAT_CONFIG_FILE, |v| v); - let config = normalize_path(root_path.join(config_path)); - let oxfmtrc = if config.try_exists().is_ok_and(|exists| exists) { + let oxfmtrc = if let Some(config) = Self::search_config_file(root_path, config_path) { if let Ok(oxfmtrc) = Oxfmtrc::from_file(&config) { oxfmtrc } else { @@ -90,7 +108,7 @@ impl ServerFormatter { } else { warn!( "Config file not found: {}, fallback to default config", - config.to_string_lossy() + config_path.unwrap_or(&FORMAT_CONFIG_FILES.join(", ")) ); Oxfmtrc::default() }; @@ -105,15 +123,19 @@ impl ServerFormatter { } #[expect(clippy::unused_self)] - pub fn get_watcher_patterns(&self, options: &LSPFormatOptions) -> Pattern { - options.config_path.as_ref().map_or(FORMAT_CONFIG_FILE, |v| v).to_owned() + pub fn get_watcher_patterns(&self, options: &LSPFormatOptions) -> Vec { + if let Some(config_path) = options.config_path.as_ref() { + return vec![config_path.to_string()]; + } + + FORMAT_CONFIG_FILES.iter().map(|file| (*file).to_string()).collect() } pub fn get_changed_watch_patterns( &self, old_options: &LSPFormatOptions, new_options: &LSPFormatOptions, - ) -> Option { + ) -> Option> { if old_options != new_options && new_options.experimental { return Some(self.get_watcher_patterns(new_options)); } diff --git a/crates/oxc_language_server/src/main.rs b/crates/oxc_language_server/src/main.rs index 74d5777d5bba2..72722db4fcca9 100644 --- a/crates/oxc_language_server/src/main.rs +++ b/crates/oxc_language_server/src/main.rs @@ -19,7 +19,7 @@ use crate::backend::Backend; type ConcurrentHashMap = papaya::HashMap; const LINT_CONFIG_FILE: &str = ".oxlintrc.json"; -const FORMAT_CONFIG_FILE: &str = ".oxfmtrc.json"; +const FORMAT_CONFIG_FILES: &[&str; 2] = &[".oxfmtrc.json", ".oxfmtrc.jsonc"]; #[tokio::main] async fn main() { diff --git a/crates/oxc_language_server/src/worker.rs b/crates/oxc_language_server/src/worker.rs index bc4d2f87b1fa0..e3dcaabb55aa6 100644 --- a/crates/oxc_language_server/src/worker.rs +++ b/crates/oxc_language_server/src/worker.rs @@ -129,13 +129,16 @@ impl WorkspaceWorker { id: format!("watcher-formatter-{}", self.root_uri.as_str()), method: "workspace/didChangeWatchedFiles".to_string(), register_options: Some(json!(DidChangeWatchedFilesRegistrationOptions { - watchers: vec![FileSystemWatcher { - glob_pattern: GlobPattern::Relative(RelativePattern { - base_uri: OneOf::Right(self.root_uri.clone()), - pattern: format_patterns - }), - kind: Some(WatchKind::all()), // created, deleted, changed - }] + watchers: format_patterns + .into_iter() + .map(|pattern| FileSystemWatcher { + glob_pattern: GlobPattern::Relative(RelativePattern { + base_uri: OneOf::Right(self.root_uri.clone()), + pattern, + }), + kind: Some(WatchKind::all()), // created, deleted, changed + }) + .collect::>(), })), }); } @@ -378,7 +381,7 @@ impl WorkspaceWorker { formatting = true; // Extract pattern data without holding the lock - let pattern = { + let patterns = { let formatter_guard = self.server_formatter.read().await; formatter_guard.as_ref().and_then(|formatter| { formatter.get_changed_watch_patterns( @@ -388,7 +391,7 @@ impl WorkspaceWorker { }) }; - if let Some(pattern) = pattern { + if let Some(patterns) = patterns { if current_option.format.experimental { // unregister the old watcher unregistrations.push(Unregistration { @@ -401,13 +404,16 @@ impl WorkspaceWorker { id: format!("watcher-formatter-{}", self.root_uri.as_str()), method: "workspace/didChangeWatchedFiles".to_string(), register_options: Some(json!(DidChangeWatchedFilesRegistrationOptions { - watchers: vec![FileSystemWatcher { - glob_pattern: GlobPattern::Relative(RelativePattern { - base_uri: OneOf::Right(self.root_uri.clone()), - pattern - }), - kind: Some(WatchKind::all()), // created, deleted, changed - }] + watchers: patterns + .into_iter() + .map(|pattern| FileSystemWatcher { + glob_pattern: GlobPattern::Relative(RelativePattern { + base_uri: OneOf::Right(self.root_uri.clone()), + pattern, + }), + kind: Some(WatchKind::all()), // created, deleted, changed + }) + .collect::>(), })), }); } @@ -679,7 +685,11 @@ mod test_watchers { assert_eq!(watchers.len(), 2); tester.assert_eq_registration(&watchers[0], "linter", &["**/.oxlintrc.json"]); - tester.assert_eq_registration(&watchers[1], "formatter", &[".oxfmtrc.json"]); + tester.assert_eq_registration( + &watchers[1], + "formatter", + &[".oxfmtrc.json", ".oxfmtrc.jsonc"], + ); } #[test] @@ -835,7 +845,11 @@ mod test_watchers { assert_eq!(unregistrations.len(), 0); assert_eq!(registration.len(), 1); - tester.assert_eq_registration(®istration[0], "formatter", &[".oxfmtrc.json"]); + tester.assert_eq_registration( + ®istration[0], + "formatter", + &[".oxfmtrc.json", ".oxfmtrc.jsonc"], + ); } #[test]