diff --git a/crates/ty_server/src/server.rs b/crates/ty_server/src/server.rs index 9be62c8b40..dc1ea3a2b7 100644 --- a/crates/ty_server/src/server.rs +++ b/crates/ty_server/src/server.rs @@ -98,6 +98,15 @@ impl Server { let (main_loop_sender, main_loop_receiver) = crossbeam::channel::bounded(32); let client = Client::new(main_loop_sender.clone(), connection.sender.clone()); + if !initialization_options.options.unknown.is_empty() { + let options = serde_json::to_string_pretty(&initialization_options.options.unknown) + .unwrap_or_else(|_| "".to_string()); + tracing::warn!("Received unknown options during initialization: {options}"); + client.show_warning_message(format_args!( + "Received unknown options during initialization: {options}" + )); + } + // Get workspace URLs without settings - settings will come from workspace/configuration let workspace_urls = workspace_folders .filter(|folders| !folders.is_empty()) diff --git a/crates/ty_server/src/session.rs b/crates/ty_server/src/session.rs index f40ba03f8a..e036937e8d 100644 --- a/crates/ty_server/src/session.rs +++ b/crates/ty_server/src/session.rs @@ -450,12 +450,23 @@ impl Session { // Combine the global options specified during initialization with the // workspace-specific options to create the final workspace options. - let ClientOptions { global, workspace } = self + let ClientOptions { + global, workspace, .. + } = self .initialization_options .options .clone() .combine(options.clone()); + if !options.unknown.is_empty() { + let options = serde_json::to_string_pretty(&options.unknown) + .unwrap_or_else(|_| "".to_string()); + tracing::warn!("Received unknown options for workspace `{url}`: {options}"); + client.show_warning_message(format!( + "Received unknown options for workspace `{url}`: {options}", + )); + } + combined_global_options.combine_with(Some(global)); let workspace_settings = workspace.into_settings(); diff --git a/crates/ty_server/src/session/options.rs b/crates/ty_server/src/session/options.rs index 9de441b6f5..875118193d 100644 --- a/crates/ty_server/src/session/options.rs +++ b/crates/ty_server/src/session/options.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use lsp_types::Url; use ruff_db::system::SystemPathBuf; use ruff_macros::Combine; @@ -84,6 +86,11 @@ pub struct ClientOptions { #[serde(flatten)] pub(crate) workspace: WorkspaceOptions, + + /// Additional options that aren't valid as per the schema but we accept it to provide better + /// error message to the user. + #[serde(flatten)] + pub(crate) unknown: HashMap, } impl ClientOptions { @@ -98,6 +105,12 @@ impl ClientOptions { self.workspace.disable_language_services = Some(disable_language_services); self } + + #[must_use] + pub fn with_unknown(mut self, unknown: HashMap) -> Self { + self.unknown = unknown; + self + } } /// Options that are global to the language server. diff --git a/crates/ty_server/tests/e2e/initialize.rs b/crates/ty_server/tests/e2e/initialize.rs index f5b6453d27..8e07b34f9f 100644 --- a/crates/ty_server/tests/e2e/initialize.rs +++ b/crates/ty_server/tests/e2e/initialize.rs @@ -1,6 +1,7 @@ use anyhow::Result; -use lsp_types::{Position, request::RegisterCapability}; +use lsp_types::{Position, notification::ShowMessage, request::RegisterCapability}; use ruff_db::system::SystemPath; +use serde_json::Value; use ty_server::{ClientOptions, DiagnosticMode}; use crate::TestServerBuilder; @@ -382,3 +383,57 @@ def bar() -> str: Ok(()) } + +/// Tests that the server sends a warning notification if user provided unknown options during +/// initialization. +#[test] +fn unknown_initialization_options() -> Result<()> { + let workspace_root = SystemPath::new("foo"); + let mut server = TestServerBuilder::new()? + .with_workspace(workspace_root, None)? + .with_initialization_options( + ClientOptions::default() + .with_unknown([("foo".to_string(), Value::String("bar".to_string()))].into()), + ) + .build()? + .wait_until_workspaces_are_initialized()?; + + let show_message_params = server.await_notification::()?; + + insta::assert_json_snapshot!(show_message_params, @r#" + { + "type": 2, + "message": "Received unknown options during initialization: {\n /"foo/": /"bar/"\n}" + } + "#); + + Ok(()) +} + +/// Tests that the server sends a warning notification if user provided unknown options in the +/// workspace configuration. +#[test] +fn unknown_options_in_workspace_configuration() -> Result<()> { + let workspace_root = SystemPath::new("foo"); + let mut server = TestServerBuilder::new()? + .with_workspace( + workspace_root, + Some( + ClientOptions::default() + .with_unknown([("foo".to_string(), Value::String("bar".to_string()))].into()), + ), + )? + .build()? + .wait_until_workspaces_are_initialized()?; + + let show_message_params = server.await_notification::()?; + + insta::assert_json_snapshot!(show_message_params, @r#" + { + "type": 2, + "message": "Received unknown options for workspace `file:///foo`: {\n /"foo/": /"bar/"\n}" + } + "#); + + Ok(()) +}