diff --git a/crates/oxc_language_server/src/formatter/mod.rs b/crates/oxc_language_server/src/formatter/mod.rs new file mode 100644 index 0000000000000..66dd7795f500f --- /dev/null +++ b/crates/oxc_language_server/src/formatter/mod.rs @@ -0,0 +1 @@ +pub mod options; diff --git a/crates/oxc_language_server/src/formatter/options.rs b/crates/oxc_language_server/src/formatter/options.rs new file mode 100644 index 0000000000000..adba1d3d58e3d --- /dev/null +++ b/crates/oxc_language_server/src/formatter/options.rs @@ -0,0 +1,5 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Default, Serialize, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct FormatOptions; diff --git a/crates/oxc_language_server/src/linter/mod.rs b/crates/oxc_language_server/src/linter/mod.rs index 26b904384637a..4353f7a2d2b1c 100644 --- a/crates/oxc_language_server/src/linter/mod.rs +++ b/crates/oxc_language_server/src/linter/mod.rs @@ -1,5 +1,6 @@ pub mod config_walker; pub mod error_with_position; pub mod isolated_lint_handler; +pub mod options; pub mod server_linter; pub mod tsgo_linter; diff --git a/crates/oxc_language_server/src/linter/options.rs b/crates/oxc_language_server/src/linter/options.rs new file mode 100644 index 0000000000000..be0a38c5c7d5f --- /dev/null +++ b/crates/oxc_language_server/src/linter/options.rs @@ -0,0 +1,201 @@ +use log::info; +use rustc_hash::{FxBuildHasher, FxHashMap}; +use serde::{Deserialize, Deserializer, Serialize, de::Error}; +use serde_json::Value; + +use oxc_linter::FixKind; + +#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Eq, Clone, Copy)] +#[serde(rename_all = "camelCase")] +pub enum UnusedDisableDirectives { + #[default] + Allow, + Warn, + Deny, +} + +#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Eq, Clone, Copy)] +#[serde(rename_all = "camelCase")] +pub enum Run { + OnSave, + #[default] + OnType, +} + +#[derive(Debug, Default, Serialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct LintOptions { + pub run: Run, // TODO: the client wants maybe only the formatter, make it optional + pub config_path: Option, + pub ts_config_path: Option, + pub unused_disable_directives: UnusedDisableDirectives, + pub type_aware: bool, + pub flags: FxHashMap, +} + +impl LintOptions { + pub fn use_nested_configs(&self) -> bool { + !self.flags.contains_key("disable_nested_config") && self.config_path.is_none() + } + + pub fn fix_kind(&self) -> FixKind { + self.flags.get("fix_kind").map_or(FixKind::SafeFix, |kind| match kind.as_str() { + "safe_fix" => FixKind::SafeFix, + "safe_fix_or_suggestion" => FixKind::SafeFixOrSuggestion, + "dangerous_fix" => FixKind::DangerousFix, + "dangerous_fix_or_suggestion" => FixKind::DangerousFixOrSuggestion, + "none" => FixKind::None, + "all" => FixKind::All, + _ => { + info!("invalid fix_kind flag `{kind}`, fallback to `safe_fix`"); + FixKind::SafeFix + } + }) + } +} + +impl<'de> Deserialize<'de> for LintOptions { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let value = Value::deserialize(deserializer)?; + LintOptions::try_from(value).map_err(Error::custom) + } +} + +impl TryFrom for LintOptions { + type Error = String; + + fn try_from(value: Value) -> Result { + let Some(object) = value.as_object() else { + return Err("no object passed".to_string()); + }; + + let mut flags = FxHashMap::with_capacity_and_hasher(2, FxBuildHasher); + if let Some(json_flags) = object.get("flags").and_then(|value| value.as_object()) { + if let Some(disable_nested_config) = + json_flags.get("disable_nested_config").and_then(|value| value.as_str()) + { + flags + .insert("disable_nested_config".to_string(), disable_nested_config.to_string()); + } + + if let Some(fix_kind) = json_flags.get("fix_kind").and_then(|value| value.as_str()) { + flags.insert("fix_kind".to_string(), fix_kind.to_string()); + } + } + + Ok(Self { + run: object + .get("run") + .map(|run| serde_json::from_value::(run.clone()).unwrap_or_default()) + .unwrap_or_default(), + unused_disable_directives: object + .get("unusedDisableDirectives") + .map(|key| { + serde_json::from_value::(key.clone()) + .unwrap_or_default() + }) + .unwrap_or_default(), + config_path: object + .get("configPath") + .and_then(|config_path| serde_json::from_value::(config_path.clone()).ok()), + ts_config_path: object + .get("tsConfigPath") + .and_then(|config_path| serde_json::from_value::(config_path.clone()).ok()), + type_aware: object + .get("typeAware") + .is_some_and(|key| serde_json::from_value::(key.clone()).unwrap_or_default()), + flags, + }) + } +} + +#[cfg(test)] +mod test { + use rustc_hash::FxHashMap; + use serde_json::json; + + use super::{LintOptions, Run, UnusedDisableDirectives}; + + #[test] + fn test_valid_options_json() { + let json = json!({ + "run": "onSave", + "configPath": "./custom.json", + "unusedDisableDirectives": "warn", + "typeAware": true, + "flags": { + "disable_nested_config": "true", + "fix_kind": "dangerous_fix" + } + }); + + let options = LintOptions::try_from(json).unwrap(); + assert_eq!(options.run, Run::OnSave); + assert_eq!(options.config_path, Some("./custom.json".into())); + assert_eq!(options.unused_disable_directives, UnusedDisableDirectives::Warn); + assert!(options.type_aware); + assert_eq!(options.flags.get("disable_nested_config"), Some(&"true".to_string())); + assert_eq!(options.flags.get("fix_kind"), Some(&"dangerous_fix".to_string())); + } + + #[test] + fn test_empty_options_json() { + let json = json!({}); + + let options = LintOptions::try_from(json).unwrap(); + assert_eq!(options.run, Run::OnType); + assert_eq!(options.config_path, None); + assert_eq!(options.unused_disable_directives, UnusedDisableDirectives::Allow); + assert!(!options.type_aware); + assert!(options.flags.is_empty()); + } + + #[test] + fn test_invalid_options_json() { + let json = json!({ + "run": true, + "configPath": "./custom.json" + }); + + let options = LintOptions::try_from(json).unwrap(); + assert_eq!(options.run, Run::OnType); // fallback + assert_eq!(options.config_path, Some("./custom.json".into())); + assert!(options.flags.is_empty()); + } + + #[test] + fn test_invalid_flags_options_json() { + let json = json!({ + "configPath": "./custom.json", + "flags": { + "disable_nested_config": true, // should be string + "fix_kind": "dangerous_fix" + } + }); + + let options = LintOptions::try_from(json).unwrap(); + assert_eq!(options.run, Run::OnType); // fallback + assert_eq!(options.config_path, Some("./custom.json".into())); + assert_eq!(options.flags.get("disable_nested_config"), None); + assert_eq!(options.flags.get("fix_kind"), Some(&"dangerous_fix".to_string())); + } + + #[test] + fn test_use_nested_configs() { + let options = LintOptions::default(); + assert!(options.use_nested_configs()); + + let options = + LintOptions { config_path: Some("config.json".to_string()), ..Default::default() }; + assert!(!options.use_nested_configs()); + + let mut flags = FxHashMap::default(); + flags.insert("disable_nested_config".to_string(), "true".to_string()); + + let options = LintOptions { flags, ..Default::default() }; + assert!(!options.use_nested_configs()); + } +} diff --git a/crates/oxc_language_server/src/linter/server_linter.rs b/crates/oxc_language_server/src/linter/server_linter.rs index 48ad4de222f6e..431bd1535658e 100644 --- a/crates/oxc_language_server/src/linter/server_linter.rs +++ b/crates/oxc_language_server/src/linter/server_linter.rs @@ -18,10 +18,10 @@ use tower_lsp_server::UriExt; use crate::linter::{ error_with_position::DiagnosticReport, isolated_lint_handler::{IsolatedLintHandler, IsolatedLintHandlerOptions}, + options::{LintOptions as LSPLintOptions, Run, UnusedDisableDirectives}, tsgo_linter::TsgoLinter, }; -use crate::options::{Run, UnusedDisableDirectives}; -use crate::{ConcurrentHashMap, OXC_CONFIG_FILE, Options}; +use crate::{ConcurrentHashMap, OXC_CONFIG_FILE}; use super::config_walker::ConfigWalker; @@ -80,7 +80,7 @@ impl ServerLinterDiagnostics { } impl ServerLinter { - pub fn new(root_uri: &Uri, options: &Options) -> Self { + pub fn new(root_uri: &Uri, options: &LSPLintOptions) -> Self { let root_path = root_uri.to_file_path().unwrap(); let mut nested_ignore_patterns = Vec::new(); let (nested_configs, mut extended_paths) = @@ -186,7 +186,7 @@ impl ServerLinter { /// and insert them inside the nested configuration fn create_nested_configs( root_path: &Path, - options: &Options, + options: &LSPLintOptions, nested_ignore_patterns: &mut Vec<(Vec, PathBuf)>, ) -> (ConcurrentHashMap, Vec) { let mut extended_paths = Vec::new(); @@ -397,9 +397,10 @@ mod test { use std::path::{Path, PathBuf}; use crate::{ - Options, - linter::server_linter::{ServerLinter, normalize_path}, - options::Run, + linter::{ + options::{LintOptions, Run, UnusedDisableDirectives}, + server_linter::{ServerLinter, normalize_path}, + }, tester::{Tester, get_file_path}, }; use rustc_hash::FxHashMap; @@ -420,7 +421,7 @@ mod test { let mut nested_ignore_patterns = Vec::new(); let (configs, _) = ServerLinter::create_nested_configs( Path::new("/root/"), - &Options { flags, ..Options::default() }, + &LintOptions { flags, ..LintOptions::default() }, &mut nested_ignore_patterns, ); @@ -432,7 +433,7 @@ mod test { let mut nested_ignore_patterns = Vec::new(); let (configs, _) = ServerLinter::create_nested_configs( &get_file_path("fixtures/linter/init_nested_configs"), - &Options::default(), + &LintOptions::default(), &mut nested_ignore_patterns, ); let configs = configs.pin(); @@ -451,7 +452,7 @@ mod test { fn test_lint_on_run_on_type_on_type() { Tester::new( "fixtures/linter/lint_on_run/on_type", - Some(Options { type_aware: true, run: Run::OnType, ..Default::default() }), + Some(LintOptions { type_aware: true, run: Run::OnType, ..Default::default() }), ) .test_and_snapshot_single_file_with_run_type("on-type.ts", Run::OnType); } @@ -461,7 +462,7 @@ mod test { fn test_lint_on_run_on_type_on_save() { Tester::new( "fixtures/linter/lint_on_run/on_save", - Some(Options { type_aware: true, run: Run::OnType, ..Default::default() }), + Some(LintOptions { type_aware: true, run: Run::OnType, ..Default::default() }), ) .test_and_snapshot_single_file_with_run_type("on-save.ts", Run::OnSave); } @@ -471,7 +472,7 @@ mod test { fn test_lint_on_run_on_save_on_type() { Tester::new( "fixtures/linter/lint_on_run/on_save", - Some(Options { type_aware: true, run: Run::OnSave, ..Default::default() }), + Some(LintOptions { type_aware: true, run: Run::OnSave, ..Default::default() }), ) .test_and_snapshot_single_file_with_run_type("on-type.ts", Run::OnType); } @@ -481,7 +482,7 @@ mod test { fn test_lint_on_run_on_save_on_save() { Tester::new( "fixtures/linter/lint_on_run/on_type", - Some(Options { type_aware: true, run: Run::OnSave, ..Default::default() }), + Some(LintOptions { type_aware: true, run: Run::OnSave, ..Default::default() }), ) .test_and_snapshot_single_file_with_run_type("on-save.ts", Run::OnSave); } @@ -491,7 +492,7 @@ mod test { fn test_lint_on_run_on_type_on_save_without_type_aware() { Tester::new( "fixtures/linter/lint_on_run/on_type", - Some(Options { type_aware: false, run: Run::OnType, ..Default::default() }), + Some(LintOptions { type_aware: false, run: Run::OnType, ..Default::default() }), ) .test_and_snapshot_single_file_with_run_type("on-save-no-type-aware.ts", Run::OnSave); } @@ -566,12 +567,12 @@ mod test { fn test_multiple_suggestions() { Tester::new( "fixtures/linter/multiple_suggestions", - Some(Options { + Some(LintOptions { flags: FxHashMap::from_iter([( "fix_kind".to_string(), "safe_fix_or_suggestion".to_string(), )]), - ..Options::default() + ..Default::default() }), ) .test_and_snapshot_single_file("forward_ref.ts"); @@ -579,10 +580,9 @@ mod test { #[test] fn test_report_unused_directives() { - use crate::options::UnusedDisableDirectives; Tester::new( "fixtures/linter/unused_disabled_directives", - Some(Options { + Some(LintOptions { unused_disable_directives: UnusedDisableDirectives::Deny, ..Default::default() }), @@ -601,7 +601,7 @@ mod test { fn test_ts_alias() { Tester::new( "fixtures/linter/ts_path_alias", - Some(Options { + Some(LintOptions { ts_config_path: Some("./deep/tsconfig.json".to_string()), ..Default::default() }), @@ -614,7 +614,7 @@ mod test { fn test_tsgo_lint() { let tester = Tester::new( "fixtures/linter/tsgolint", - Some(Options { type_aware: true, run: Run::OnSave, ..Default::default() }), + Some(LintOptions { type_aware: true, run: Run::OnSave, ..Default::default() }), ); tester.test_and_snapshot_single_file("no-floating-promises/index.ts"); } diff --git a/crates/oxc_language_server/src/main.rs b/crates/oxc_language_server/src/main.rs index dc93726976aa3..4253076772897 100644 --- a/crates/oxc_language_server/src/main.rs +++ b/crates/oxc_language_server/src/main.rs @@ -3,6 +3,7 @@ use std::{str::FromStr, sync::Arc}; use futures::future::join_all; use log::{debug, info, warn}; use rustc_hash::FxBuildHasher; +use serde::Deserialize; use serde_json::json; use tokio::sync::{OnceCell, RwLock, SetError}; use tower_lsp_server::{ @@ -21,6 +22,7 @@ use tower_lsp_server::{ mod capabilities; mod code_actions; mod commands; +mod formatter; mod linter; mod options; #[cfg(test)] @@ -64,7 +66,7 @@ impl LanguageServer for Backend { return Some(new_settings); } - let deprecated_settings = Options::try_from(value.get_mut("settings")?.take()).ok(); + let deprecated_settings = Options::deserialize(value.get_mut("settings")?.take()).ok(); // the client has deprecated settings and has a deprecated root uri. // handle all things like the old way diff --git a/crates/oxc_language_server/src/options.rs b/crates/oxc_language_server/src/options.rs index 0ed0961c7cd02..9f686d6ddd912 100644 --- a/crates/oxc_language_server/src/options.rs +++ b/crates/oxc_language_server/src/options.rs @@ -1,115 +1,15 @@ -use log::info; -use oxc_linter::FixKind; -use rustc_hash::{FxBuildHasher, FxHashMap}; -use serde::{Deserialize, Deserializer, Serialize, de::Error}; -use serde_json::Value; +use serde::{Deserialize, Serialize}; use tower_lsp_server::lsp_types::Uri; -#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Eq, Clone, Copy)] -#[serde(rename_all = "camelCase")] -pub enum Run { - OnSave, - #[default] - OnType, -} - -#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Eq, Clone, Copy)] -#[serde(rename_all = "camelCase")] -pub enum UnusedDisableDirectives { - #[default] - Allow, - Warn, - Deny, -} +use crate::{formatter::options::FormatOptions, linter::options::LintOptions}; -#[derive(Debug, Default, Serialize, Clone)] +#[derive(Debug, Default, Serialize, Deserialize, Clone)] #[serde(rename_all = "camelCase")] pub struct Options { - pub run: Run, - pub config_path: Option, - pub ts_config_path: Option, - pub unused_disable_directives: UnusedDisableDirectives, - pub type_aware: bool, - pub flags: FxHashMap, -} - -impl Options { - pub fn use_nested_configs(&self) -> bool { - !self.flags.contains_key("disable_nested_config") && self.config_path.is_none() - } - - pub fn fix_kind(&self) -> FixKind { - self.flags.get("fix_kind").map_or(FixKind::SafeFix, |kind| match kind.as_str() { - "safe_fix" => FixKind::SafeFix, - "safe_fix_or_suggestion" => FixKind::SafeFixOrSuggestion, - "dangerous_fix" => FixKind::DangerousFix, - "dangerous_fix_or_suggestion" => FixKind::DangerousFixOrSuggestion, - "none" => FixKind::None, - "all" => FixKind::All, - _ => { - info!("invalid fix_kind flag `{kind}`, fallback to `safe_fix`"); - FixKind::SafeFix - } - }) - } -} - -impl<'de> Deserialize<'de> for Options { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let value = Value::deserialize(deserializer)?; - Options::try_from(value).map_err(Error::custom) - } -} - -impl TryFrom for Options { - type Error = String; - - fn try_from(value: Value) -> Result { - let Some(object) = value.as_object() else { - return Err("no object passed".to_string()); - }; - - let mut flags = FxHashMap::with_capacity_and_hasher(2, FxBuildHasher); - if let Some(json_flags) = object.get("flags").and_then(|value| value.as_object()) { - if let Some(disable_nested_config) = - json_flags.get("disable_nested_config").and_then(|value| value.as_str()) - { - flags - .insert("disable_nested_config".to_string(), disable_nested_config.to_string()); - } - - if let Some(fix_kind) = json_flags.get("fix_kind").and_then(|value| value.as_str()) { - flags.insert("fix_kind".to_string(), fix_kind.to_string()); - } - } - - Ok(Self { - run: object - .get("run") - .map(|run| serde_json::from_value::(run.clone()).unwrap_or_default()) - .unwrap_or_default(), - unused_disable_directives: object - .get("unusedDisableDirectives") - .map(|key| { - serde_json::from_value::(key.clone()) - .unwrap_or_default() - }) - .unwrap_or_default(), - config_path: object - .get("configPath") - .and_then(|config_path| serde_json::from_value::(config_path.clone()).ok()), - ts_config_path: object - .get("tsConfigPath") - .and_then(|config_path| serde_json::from_value::(config_path.clone()).ok()), - type_aware: object - .get("typeAware") - .is_some_and(|key| serde_json::from_value::(key.clone()).unwrap_or_default()), - flags, - }) - } + #[serde(flatten)] + pub lint: LintOptions, + #[serde(flatten)] + pub format: FormatOptions, } #[derive(Debug, Serialize, Deserialize, Clone)] @@ -121,74 +21,11 @@ pub struct WorkspaceOption { #[cfg(test)] mod test { - use rustc_hash::FxHashMap; use serde_json::json; - use super::{Options, Run, UnusedDisableDirectives, WorkspaceOption}; + use crate::linter::options::Run; - #[test] - fn test_valid_options_json() { - let json = json!({ - "run": "onSave", - "configPath": "./custom.json", - "unusedDisableDirectives": "warn", - "typeAware": true, - "flags": { - "disable_nested_config": "true", - "fix_kind": "dangerous_fix" - } - }); - - let options = Options::try_from(json).unwrap(); - assert_eq!(options.run, Run::OnSave); - assert_eq!(options.config_path, Some("./custom.json".into())); - assert_eq!(options.unused_disable_directives, UnusedDisableDirectives::Warn); - assert!(options.type_aware); - assert_eq!(options.flags.get("disable_nested_config"), Some(&"true".to_string())); - assert_eq!(options.flags.get("fix_kind"), Some(&"dangerous_fix".to_string())); - } - - #[test] - fn test_empty_options_json() { - let json = json!({}); - - let options = Options::try_from(json).unwrap(); - assert_eq!(options.run, Run::OnType); - assert_eq!(options.config_path, None); - assert_eq!(options.unused_disable_directives, UnusedDisableDirectives::Allow); - assert!(!options.type_aware); - assert!(options.flags.is_empty()); - } - - #[test] - fn test_invalid_options_json() { - let json = json!({ - "run": true, - "configPath": "./custom.json" - }); - - let options = Options::try_from(json).unwrap(); - assert_eq!(options.run, Run::OnType); // fallback - assert_eq!(options.config_path, Some("./custom.json".into())); - assert!(options.flags.is_empty()); - } - - #[test] - fn test_invalid_flags_options_json() { - let json = json!({ - "configPath": "./custom.json", - "flags": { - "disable_nested_config": true, // should be string - "fix_kind": "dangerous_fix" - } - }); - - let options = Options::try_from(json).unwrap(); - assert_eq!(options.run, Run::OnType); // fallback - assert_eq!(options.config_path, Some("./custom.json".into())); - assert_eq!(options.flags.get("disable_nested_config"), None); - assert_eq!(options.flags.get("fix_kind"), Some(&"dangerous_fix".to_string())); - } + use super::WorkspaceOption; #[test] fn test_invalid_workspace_options_json() { @@ -196,7 +33,7 @@ mod test { "workspaceUri": "file:///root/", "options": { "run": true, - "configPath": "./custom.json" + "configPath": "./custom.json", } }]); @@ -206,24 +43,8 @@ mod test { assert_eq!(workspace[0].workspace_uri.path().as_str(), "/root/"); let options = &workspace[0].options; - assert_eq!(options.run, Run::OnType); // fallback - assert_eq!(options.config_path, Some("./custom.json".into())); - assert!(options.flags.is_empty()); - } - - #[test] - fn test_use_nested_configs() { - let options = Options::default(); - assert!(options.use_nested_configs()); - - let options = - Options { config_path: Some("config.json".to_string()), ..Default::default() }; - assert!(!options.use_nested_configs()); - - let mut flags = FxHashMap::default(); - flags.insert("disable_nested_config".to_string(), "true".to_string()); - - let options = Options { flags, ..Default::default() }; - assert!(!options.use_nested_configs()); + assert_eq!(options.lint.run, Run::OnType); // fallback + assert_eq!(options.lint.config_path, Some("./custom.json".into())); + assert!(options.lint.flags.is_empty()); } } diff --git a/crates/oxc_language_server/src/tester.rs b/crates/oxc_language_server/src/tester.rs index f4575a2fb3968..6de1667534ed2 100644 --- a/crates/oxc_language_server/src/tester.rs +++ b/crates/oxc_language_server/src/tester.rs @@ -6,7 +6,12 @@ use tower_lsp_server::{ }; use crate::{ - Options, linter::server_linter::ServerLinterRun, options::Run, worker::WorkspaceWorker, + Options, + linter::{ + options::{LintOptions, Run}, + server_linter::ServerLinterRun, + }, + worker::WorkspaceWorker, }; use super::linter::error_with_position::DiagnosticReport; @@ -94,11 +99,11 @@ fixed: {fixed:?} /// Testing struct for the [linter server][crate::linter::server_linter::ServerLinter]. pub struct Tester<'t> { relative_root_dir: &'t str, - options: Option, + options: Option, } impl Tester<'_> { - pub fn new(relative_root_dir: &'static str, options: Option) -> Self { + pub fn new(relative_root_dir: &'static str, options: Option) -> Self { Self { relative_root_dir, options } } @@ -108,7 +113,12 @@ impl Tester<'_> { .join(self.relative_root_dir); let uri = Uri::from_file_path(absolute_path).expect("could not convert current dir to uri"); let worker = WorkspaceWorker::new(uri); - worker.init_linter(&self.options.clone().unwrap_or_default()).await; + worker + .init_linter(&Options { + lint: self.options.clone().unwrap_or_default(), + ..Default::default() + }) + .await; worker } diff --git a/crates/oxc_language_server/src/worker.rs b/crates/oxc_language_server/src/worker.rs index 938a7983783ad..12241c31090b5 100644 --- a/crates/oxc_language_server/src/worker.rs +++ b/crates/oxc_language_server/src/worker.rs @@ -18,6 +18,7 @@ use crate::{ }, linter::{ error_with_position::{DiagnosticReport, PossibleFixContent}, + options::LintOptions, server_linter::{ServerLinter, ServerLinterRun, normalize_path}, }, }; @@ -46,7 +47,7 @@ impl WorkspaceWorker { pub async fn init_linter(&self, options: &Options) { *self.options.lock().await = options.clone(); - *self.server_linter.write().await = Some(ServerLinter::new(&self.root_uri, options)); + *self.server_linter.write().await = Some(ServerLinter::new(&self.root_uri, &options.lint)); } // WARNING: start all programs (linter, formatter) before calling this function @@ -56,13 +57,14 @@ impl WorkspaceWorker { // clone the options to avoid locking the mutex let options = self.options.lock().await; - let use_nested_configs = options.use_nested_configs(); + let use_nested_configs = options.lint.use_nested_configs(); // append the base watcher watchers.push(FileSystemWatcher { glob_pattern: GlobPattern::Relative(RelativePattern { base_uri: OneOf::Right(self.root_uri.clone()), pattern: options + .lint .config_path .as_ref() .unwrap_or(&"**/.oxlintrc.json".to_owned()) @@ -115,12 +117,12 @@ impl WorkspaceWorker { async fn refresh_server_linter(&self) { let options = self.options.lock().await; - let server_linter = ServerLinter::new(&self.root_uri, &options); + let server_linter = ServerLinter::new(&self.root_uri, &options.lint); *self.server_linter.write().await = Some(server_linter); } - fn needs_linter_restart(old_options: &Options, new_options: &Options) -> bool { + fn needs_linter_restart(old_options: &LintOptions, new_options: &LintOptions) -> bool { old_options.config_path != new_options.config_path || old_options.ts_config_path != new_options.ts_config_path || old_options.use_nested_configs() != new_options.use_nested_configs() @@ -306,7 +308,7 @@ impl WorkspaceWorker { *options_guard = changed_options.clone(); } - if Self::needs_linter_restart(¤t_option, changed_options) { + if Self::needs_linter_restart(¤t_option.lint, &changed_options.lint) { let files = { let server_linter_guard = self.server_linter.read().await; let server_linter = server_linter_guard.as_ref(); @@ -318,13 +320,14 @@ impl WorkspaceWorker { }; self.refresh_server_linter().await; - if current_option.config_path != changed_options.config_path { + if current_option.lint.config_path != changed_options.lint.config_path { return ( Some(self.revalidate_diagnostics(files).await), Some(FileSystemWatcher { glob_pattern: GlobPattern::Relative(RelativePattern { base_uri: OneOf::Right(self.root_uri.clone()), pattern: changed_options + .lint .config_path .as_ref() .unwrap_or(&"**/.oxlintrc.json".to_string())