From a4ac6a3f5f1b02b5290ab603d76b5cd9030731ca Mon Sep 17 00:00:00 2001 From: Nayeem Rahman Date: Thu, 21 Sep 2023 06:46:39 +0100 Subject: [PATCH] refactor(lsp): store language sections in WorkspaceSettings (#20593) When sending configuration requests to the client, reads `javascript` and `typescript` sections in addition to `deno`. The LSP's initialization options now accepts `javascript` and `typescript` namespaces. --- cli/lsp/client.rs | 51 ++-- cli/lsp/config.rs | 377 +++++++++++++++++++++++------ cli/lsp/language_server.rs | 81 ++++--- cli/lsp/repl.rs | 27 ++- cli/lsp/tsc.rs | 65 ++--- cli/tests/integration/lsp_tests.rs | 62 ++--- test_util/src/lsp.rs | 23 +- 7 files changed, 501 insertions(+), 185 deletions(-) diff --git a/cli/lsp/client.rs b/cli/lsp/client.rs index acef59f97d..b5cdf8eb9f 100644 --- a/cli/lsp/client.rs +++ b/cli/lsp/client.rs @@ -7,7 +7,6 @@ use deno_core::anyhow::anyhow; use deno_core::anyhow::bail; use deno_core::error::AnyError; use deno_core::serde_json; -use deno_core::serde_json::Value; use deno_core::unsync::spawn; use tower_lsp::lsp_types as lsp; use tower_lsp::lsp_types::ConfigurationItem; @@ -15,6 +14,7 @@ use tower_lsp::lsp_types::ConfigurationItem; use crate::lsp::repl::get_repl_workspace_settings; use super::config::SpecifierSettings; +use super::config::WorkspaceSettings; use super::config::SETTINGS_SECTION; use super::lsp_custom; use super::testing::lsp_custom as testing_lsp_custom; @@ -148,7 +148,9 @@ impl OutsideLockClient { } } - pub async fn workspace_configuration(&self) -> Result { + pub async fn workspace_configuration( + &self, + ) -> Result { self.0.workspace_configuration().await } @@ -186,7 +188,9 @@ trait ClientTrait: Send + Sync { &self, uris: Vec, ) -> Result>, AnyError>; - async fn workspace_configuration(&self) -> Result; + async fn workspace_configuration( + &self, + ) -> Result; async fn show_message(&self, message_type: lsp::MessageType, text: String); async fn register_capability( &self, @@ -284,19 +288,36 @@ impl ClientTrait for TowerClient { ) } - async fn workspace_configuration(&self) -> Result { + async fn workspace_configuration( + &self, + ) -> Result { let config_response = self .0 - .configuration(vec![ConfigurationItem { - scope_uri: None, - section: Some(SETTINGS_SECTION.to_string()), - }]) + .configuration(vec![ + ConfigurationItem { + scope_uri: None, + section: Some(SETTINGS_SECTION.to_string()), + }, + ConfigurationItem { + scope_uri: None, + section: Some("javascript".to_string()), + }, + ConfigurationItem { + scope_uri: None, + section: Some("typescript".to_string()), + }, + ]) .await; match config_response { - Ok(value_vec) => match value_vec.get(0).cloned() { - Some(value) => Ok(value), - None => bail!("Missing response workspace configuration."), - }, + Ok(configs) => { + let mut configs = configs.into_iter(); + let deno = serde_json::to_value(configs.next()).unwrap(); + let javascript = serde_json::to_value(configs.next()).unwrap(); + let typescript = serde_json::to_value(configs.next()).unwrap(); + Ok(WorkspaceSettings::from_raw_settings( + deno, javascript, typescript, + )) + } Err(err) => { bail!("Error getting workspace configuration: {}", err) } @@ -367,8 +388,10 @@ impl ClientTrait for ReplClient { Ok(settings) } - async fn workspace_configuration(&self) -> Result { - Ok(serde_json::to_value(get_repl_workspace_settings()).unwrap()) + async fn workspace_configuration( + &self, + ) -> Result { + Ok(get_repl_workspace_settings()) } async fn show_message( diff --git a/cli/lsp/config.rs b/cli/lsp/config.rs index 7bbf90d21c..1a83c00ebd 100644 --- a/cli/lsp/config.rs +++ b/cli/lsp/config.rs @@ -5,8 +5,9 @@ use crate::args::ConfigFile; use crate::lsp::logging::lsp_warn; use crate::util::fs::canonicalize_path_maybe_not_exists; use crate::util::path::specifier_to_file_path; -use deno_core::error::AnyError; +use deno_ast::MediaType; use deno_core::parking_lot::Mutex; +use deno_core::serde::de::DeserializeOwned; use deno_core::serde::Deserialize; use deno_core::serde::Serialize; use deno_core::serde_json; @@ -86,6 +87,13 @@ impl Default for CodeLensSpecifierSettings { } } +#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct DenoCompletionSettings { + #[serde(default)] + pub imports: ImportCompletionSettings, +} + #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct CompletionSettings { @@ -97,8 +105,6 @@ pub struct CompletionSettings { pub paths: bool, #[serde(default = "is_true")] pub auto_imports: bool, - #[serde(default)] - pub imports: ImportCompletionSettings, } impl Default for CompletionSettings { @@ -108,7 +114,6 @@ impl Default for CompletionSettings { names: true, paths: true, auto_imports: true, - imports: ImportCompletionSettings::default(), } } } @@ -281,6 +286,15 @@ fn empty_string_none<'de, D: serde::Deserializer<'de>>( Ok(o.filter(|s| !s.is_empty())) } +#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct LanguageWorkspaceSettings { + #[serde(default)] + pub inlay_hints: InlayHintsSettings, + #[serde(default)] + pub suggest: CompletionSettings, +} + /// Deno language server specific settings that are applied to a workspace. #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] @@ -319,9 +333,6 @@ pub struct WorkspaceSettings { #[serde(default)] pub code_lens: CodeLensSettings, - #[serde(default)] - pub inlay_hints: InlayHintsSettings, - /// A flag that indicates if internal debug logging should be made available. #[serde(default)] pub internal_debug: bool, @@ -334,10 +345,8 @@ pub struct WorkspaceSettings { #[serde(default = "default_document_preload_limit")] pub document_preload_limit: usize, - /// A flag that indicates if Dene should validate code against the unstable - /// APIs for the workspace. #[serde(default)] - pub suggest: CompletionSettings, + pub suggest: DenoCompletionSettings, /// Testing settings for the workspace. #[serde(default)] @@ -355,6 +364,12 @@ pub struct WorkspaceSettings { #[serde(default)] pub unstable: bool, + + #[serde(default)] + pub javascript: LanguageWorkspaceSettings, + + #[serde(default)] + pub typescript: LanguageWorkspaceSettings, } impl Default for WorkspaceSettings { @@ -368,7 +383,6 @@ impl Default for WorkspaceSettings { config: None, import_map: None, code_lens: Default::default(), - inlay_hints: Default::default(), internal_debug: false, lint: true, document_preload_limit: default_document_preload_limit(), @@ -377,28 +391,220 @@ impl Default for WorkspaceSettings { tls_certificate: None, unsafely_ignore_certificate_errors: None, unstable: false, + javascript: Default::default(), + typescript: Default::default(), } } } impl WorkspaceSettings { + pub fn from_raw_settings( + deno: Value, + javascript: Value, + typescript: Value, + ) -> Self { + fn parse_or_default( + value: Value, + description: &str, + ) -> T { + if value.is_null() { + return T::default(); + } + match serde_json::from_value(value) { + Ok(v) => v, + Err(err) => { + lsp_warn!("Couldn't parse {description}: {err}"); + T::default() + } + } + } + let deno_inlay_hints = + deno.as_object().and_then(|o| o.get("inlayHints").cloned()); + let deno_suggest = deno.as_object().and_then(|o| o.get("suggest").cloned()); + let mut settings: Self = parse_or_default(deno, "settings under \"deno\""); + settings.javascript = + parse_or_default(javascript, "settings under \"javascript\""); + settings.typescript = + parse_or_default(typescript, "settings under \"typescript\""); + if let Some(inlay_hints) = deno_inlay_hints { + let inlay_hints: InlayHintsSettings = + parse_or_default(inlay_hints, "settings under \"deno.inlayHints\""); + if inlay_hints.parameter_names.enabled != Default::default() { + lsp_warn!("\"deno.inlayHints.parameterNames.enabled\" is deprecated. Instead use \"javascript.inlayHints.parameterNames.enabled\" and \"typescript.inlayHints.parameterNames.enabled\"."); + settings.javascript.inlay_hints.parameter_names.enabled = + inlay_hints.parameter_names.enabled.clone(); + settings.typescript.inlay_hints.parameter_names.enabled = + inlay_hints.parameter_names.enabled; + } + if !inlay_hints + .parameter_names + .suppress_when_argument_matches_name + { + lsp_warn!("\"deno.inlayHints.parameterNames.suppressWhenArgumentMatchesName\" is deprecated. Instead use \"javascript.inlayHints.parameterNames.suppressWhenArgumentMatchesName\" and \"typescript.inlayHints.parameterNames.suppressWhenArgumentMatchesName\"."); + settings + .javascript + .inlay_hints + .parameter_names + .suppress_when_argument_matches_name = inlay_hints + .parameter_names + .suppress_when_argument_matches_name; + settings + .typescript + .inlay_hints + .parameter_names + .suppress_when_argument_matches_name = inlay_hints + .parameter_names + .suppress_when_argument_matches_name; + } + if inlay_hints.parameter_types.enabled { + lsp_warn!("\"deno.inlayHints.parameterTypes.enabled\" is deprecated. Instead use \"javascript.inlayHints.parameterTypes.enabled\" and \"typescript.inlayHints.parameterTypes.enabled\"."); + settings.javascript.inlay_hints.parameter_types.enabled = + inlay_hints.parameter_types.enabled; + settings.typescript.inlay_hints.parameter_types.enabled = + inlay_hints.parameter_types.enabled; + } + if inlay_hints.variable_types.enabled { + lsp_warn!("\"deno.inlayHints.variableTypes.enabled\" is deprecated. Instead use \"javascript.inlayHints.variableTypes.enabled\" and \"typescript.inlayHints.variableTypes.enabled\"."); + settings.javascript.inlay_hints.variable_types.enabled = + inlay_hints.variable_types.enabled; + settings.typescript.inlay_hints.variable_types.enabled = + inlay_hints.variable_types.enabled; + } + if !inlay_hints.variable_types.suppress_when_type_matches_name { + lsp_warn!("\"deno.inlayHints.variableTypes.suppressWhenTypeMatchesName\" is deprecated. Instead use \"javascript.inlayHints.variableTypes.suppressWhenTypeMatchesName\" and \"typescript.inlayHints.variableTypes.suppressWhenTypeMatchesName\"."); + settings + .javascript + .inlay_hints + .variable_types + .suppress_when_type_matches_name = + inlay_hints.variable_types.suppress_when_type_matches_name; + settings + .typescript + .inlay_hints + .variable_types + .suppress_when_type_matches_name = + inlay_hints.variable_types.suppress_when_type_matches_name; + } + if inlay_hints.property_declaration_types.enabled { + lsp_warn!("\"deno.inlayHints.propertyDeclarationTypes.enabled\" is deprecated. Instead use \"javascript.inlayHints.propertyDeclarationTypes.enabled\" and \"typescript.inlayHints.propertyDeclarationTypes.enabled\"."); + settings + .javascript + .inlay_hints + .property_declaration_types + .enabled = inlay_hints.property_declaration_types.enabled; + settings + .typescript + .inlay_hints + .property_declaration_types + .enabled = inlay_hints.property_declaration_types.enabled; + } + if inlay_hints.function_like_return_types.enabled { + lsp_warn!("\"deno.inlayHints.functionLikeReturnTypes.enabled\" is deprecated. Instead use \"javascript.inlayHints.functionLikeReturnTypes.enabled\" and \"typescript.inlayHints.functionLikeReturnTypes.enabled\"."); + settings + .javascript + .inlay_hints + .function_like_return_types + .enabled = inlay_hints.function_like_return_types.enabled; + settings + .typescript + .inlay_hints + .function_like_return_types + .enabled = inlay_hints.function_like_return_types.enabled; + } + if inlay_hints.enum_member_values.enabled { + lsp_warn!("\"deno.inlayHints.enumMemberValues.enabled\" is deprecated. Instead use \"javascript.inlayHints.enumMemberValues.enabled\" and \"typescript.inlayHints.enumMemberValues.enabled\"."); + settings.javascript.inlay_hints.enum_member_values.enabled = + inlay_hints.enum_member_values.enabled; + settings.typescript.inlay_hints.enum_member_values.enabled = + inlay_hints.enum_member_values.enabled; + } + } + if let Some(suggest) = deno_suggest { + let suggest: CompletionSettings = + parse_or_default(suggest, "settings under \"deno.suggest\""); + if suggest.complete_function_calls { + lsp_warn!("\"deno.suggest.completeFunctionCalls\" is deprecated. Instead use \"javascript.suggest.completeFunctionCalls\" and \"typescript.suggest.completeFunctionCalls\"."); + settings.javascript.suggest.complete_function_calls = + suggest.complete_function_calls; + settings.typescript.suggest.complete_function_calls = + suggest.complete_function_calls; + } + if !suggest.names { + lsp_warn!("\"deno.suggest.names\" is deprecated. Instead use \"javascript.suggest.names\" and \"typescript.suggest.names\"."); + settings.javascript.suggest.names = suggest.names; + settings.typescript.suggest.names = suggest.names; + } + if !suggest.paths { + lsp_warn!("\"deno.suggest.paths\" is deprecated. Instead use \"javascript.suggest.paths\" and \"typescript.suggest.paths\"."); + settings.javascript.suggest.paths = suggest.paths; + settings.typescript.suggest.paths = suggest.paths; + } + if !suggest.auto_imports { + lsp_warn!("\"deno.suggest.autoImports\" is deprecated. Instead use \"javascript.suggest.autoImports\" and \"typescript.suggest.autoImports\"."); + settings.javascript.suggest.auto_imports = suggest.auto_imports; + settings.typescript.suggest.auto_imports = suggest.auto_imports; + } + } + settings + } + + pub fn from_initialization_options(options: Value) -> Self { + let deno = options; + let javascript = deno + .as_object() + .and_then(|o| o.get("javascript").cloned()) + .unwrap_or_default(); + let typescript = deno + .as_object() + .and_then(|o| o.get("typescript").cloned()) + .unwrap_or_default(); + Self::from_raw_settings(deno, javascript, typescript) + } + /// Determine if any code lenses are enabled at all. This allows short /// circuiting when there are no code lenses enabled. pub fn enabled_code_lens(&self) -> bool { self.code_lens.implementations || self.code_lens.references } + pub fn language_settings_for_specifier( + &self, + specifier: &ModuleSpecifier, + ) -> Option<&LanguageWorkspaceSettings> { + match MediaType::from_specifier(specifier) { + MediaType::JavaScript + | MediaType::Jsx + | MediaType::Mjs + | MediaType::Cjs => Some(&self.javascript), + MediaType::TypeScript + | MediaType::Mts + | MediaType::Cts + | MediaType::Dts + | MediaType::Dmts + | MediaType::Dcts + | MediaType::Tsx => Some(&self.typescript), + MediaType::Json + | MediaType::Wasm + | MediaType::TsBuildInfo + | MediaType::SourceMap + | MediaType::Unknown => None, + } + } + /// Determine if any inlay hints are enabled. This allows short circuiting /// when there are no inlay hints enabled. - pub fn enabled_inlay_hints(&self) -> bool { + pub fn enabled_inlay_hints(&self, specifier: &ModuleSpecifier) -> bool { + let Some(settings) = self.language_settings_for_specifier(specifier) else { + return false; + }; !matches!( - self.inlay_hints.parameter_names.enabled, + settings.inlay_hints.parameter_names.enabled, InlayHintsParamNamesEnabled::None - ) || self.inlay_hints.parameter_types.enabled - || self.inlay_hints.variable_types.enabled - || self.inlay_hints.property_declaration_types.enabled - || self.inlay_hints.function_like_return_types.enabled - || self.inlay_hints.enum_member_values.enabled + ) || settings.inlay_hints.parameter_types.enabled + || settings.inlay_hints.variable_types.enabled + || settings.inlay_hints.property_declaration_types.enabled + || settings.inlay_hints.function_like_return_types.enabled + || settings.inlay_hints.enum_member_values.enabled } } @@ -593,16 +799,12 @@ impl Config { /// Set the workspace settings directly, which occurs during initialization /// and when the client does not support workspace configuration requests - pub fn set_workspace_settings( - &mut self, - value: Value, - ) -> Result<(), AnyError> { - self.settings.workspace = serde_json::from_value(value)?; + pub fn set_workspace_settings(&mut self, settings: WorkspaceSettings) { + self.settings.workspace = settings; // See https://github.com/denoland/vscode_deno/issues/908. if self.settings.workspace.enable_paths == Some(vec![]) { self.settings.workspace.enable_paths = None; } - Ok(()) } pub fn snapshot(&self) -> Arc { @@ -912,6 +1114,7 @@ fn resolve_lockfile_from_path(lockfile_path: PathBuf) -> Option { mod tests { use super::*; use deno_core::resolve_url; + use deno_core::serde_json; use deno_core::serde_json::json; use pretty_assertions::assert_eq; @@ -921,11 +1124,12 @@ mod tests { let mut config = Config::new_with_root(root_uri); let specifier = resolve_url("file:///a.ts").unwrap(); assert!(!config.specifier_enabled(&specifier)); - config - .set_workspace_settings(json!({ + config.set_workspace_settings( + serde_json::from_value(json!({ "enable": true })) - .expect("could not update"); + .unwrap(), + ); assert!(config.specifier_enabled(&specifier)); } @@ -935,11 +1139,12 @@ mod tests { let mut config = Config::new_with_root(root_uri); let specifier = resolve_url("file:///a.ts").unwrap(); assert!(!config.specifier_enabled(&specifier)); - config - .set_workspace_settings(json!({ + config.set_workspace_settings( + serde_json::from_value(json!({ "enable": true })) - .expect("could not update"); + .unwrap(), + ); let config_snapshot = config.snapshot(); assert!(config_snapshot.specifier_enabled(&specifier)); } @@ -954,7 +1159,7 @@ mod tests { assert!(!config.specifier_enabled(&specifier_b)); let workspace_settings = serde_json::from_str(r#"{ "enablePaths": ["worker"] }"#).unwrap(); - config.set_workspace_settings(workspace_settings).unwrap(); + config.set_workspace_settings(workspace_settings); assert!(config.specifier_enabled(&specifier_a)); assert!(!config.specifier_enabled(&specifier_b)); let config_snapshot = config.snapshot(); @@ -979,9 +1184,7 @@ mod tests { #[test] fn test_set_workspace_settings_defaults() { let mut config = Config::new(); - config - .set_workspace_settings(json!({})) - .expect("could not update"); + config.set_workspace_settings(serde_json::from_value(json!({})).unwrap()); assert_eq!( config.workspace_settings().clone(), WorkspaceSettings { @@ -998,34 +1201,10 @@ mod tests { references_all_functions: false, test: true, }, - inlay_hints: InlayHintsSettings { - parameter_names: InlayHintsParamNamesOptions { - enabled: InlayHintsParamNamesEnabled::None, - suppress_when_argument_matches_name: true - }, - parameter_types: InlayHintsParamTypesOptions { enabled: false }, - variable_types: InlayHintsVarTypesOptions { - enabled: false, - suppress_when_type_matches_name: true - }, - property_declaration_types: InlayHintsPropDeclTypesOptions { - enabled: false - }, - function_like_return_types: InlayHintsFuncLikeReturnTypesOptions { - enabled: false - }, - enum_member_values: InlayHintsEnumMemberValuesOptions { - enabled: false - }, - }, internal_debug: false, lint: true, document_preload_limit: 1_000, - suggest: CompletionSettings { - complete_function_calls: false, - names: true, - paths: true, - auto_imports: true, + suggest: DenoCompletionSettings { imports: ImportCompletionSettings { auto_discover: true, hosts: HashMap::new(), @@ -1037,6 +1216,62 @@ mod tests { tls_certificate: None, unsafely_ignore_certificate_errors: None, unstable: false, + javascript: LanguageWorkspaceSettings { + inlay_hints: InlayHintsSettings { + parameter_names: InlayHintsParamNamesOptions { + enabled: InlayHintsParamNamesEnabled::None, + suppress_when_argument_matches_name: true + }, + parameter_types: InlayHintsParamTypesOptions { enabled: false }, + variable_types: InlayHintsVarTypesOptions { + enabled: false, + suppress_when_type_matches_name: true + }, + property_declaration_types: InlayHintsPropDeclTypesOptions { + enabled: false + }, + function_like_return_types: InlayHintsFuncLikeReturnTypesOptions { + enabled: false + }, + enum_member_values: InlayHintsEnumMemberValuesOptions { + enabled: false + }, + }, + suggest: CompletionSettings { + complete_function_calls: false, + names: true, + paths: true, + auto_imports: true, + }, + }, + typescript: LanguageWorkspaceSettings { + inlay_hints: InlayHintsSettings { + parameter_names: InlayHintsParamNamesOptions { + enabled: InlayHintsParamNamesEnabled::None, + suppress_when_argument_matches_name: true + }, + parameter_types: InlayHintsParamTypesOptions { enabled: false }, + variable_types: InlayHintsVarTypesOptions { + enabled: false, + suppress_when_type_matches_name: true + }, + property_declaration_types: InlayHintsPropDeclTypesOptions { + enabled: false + }, + function_like_return_types: InlayHintsFuncLikeReturnTypesOptions { + enabled: false + }, + enum_member_values: InlayHintsEnumMemberValuesOptions { + enabled: false + }, + }, + suggest: CompletionSettings { + complete_function_calls: false, + names: true, + paths: true, + auto_imports: true, + }, + }, } ); } @@ -1044,9 +1279,9 @@ mod tests { #[test] fn test_empty_cache() { let mut config = Config::new(); - config - .set_workspace_settings(json!({ "cache": "" })) - .expect("could not update"); + config.set_workspace_settings( + serde_json::from_value(json!({ "cache": "" })).unwrap(), + ); assert_eq!( config.workspace_settings().clone(), WorkspaceSettings::default() @@ -1056,9 +1291,9 @@ mod tests { #[test] fn test_empty_import_map() { let mut config = Config::new(); - config - .set_workspace_settings(json!({ "import_map": "" })) - .expect("could not update"); + config.set_workspace_settings( + serde_json::from_value(json!({ "import_map": "" })).unwrap(), + ); assert_eq!( config.workspace_settings().clone(), WorkspaceSettings::default() @@ -1068,9 +1303,9 @@ mod tests { #[test] fn test_empty_tls_certificate() { let mut config = Config::new(); - config - .set_workspace_settings(json!({ "tls_certificate": "" })) - .expect("could not update"); + config.set_workspace_settings( + serde_json::from_value(json!({ "tls_certificate": "" })).unwrap(), + ); assert_eq!( config.workspace_settings().clone(), WorkspaceSettings::default() @@ -1080,9 +1315,9 @@ mod tests { #[test] fn test_empty_config() { let mut config = Config::new(); - config - .set_workspace_settings(json!({ "config": "" })) - .expect("could not update"); + config.set_workspace_settings( + serde_json::from_value(json!({ "config": "" })).unwrap(), + ); assert_eq!( config.workspace_settings().clone(), WorkspaceSettings::default() diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index 8ef7d124c4..be3d7f5af0 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -45,6 +45,7 @@ use super::code_lens; use super::completions; use super::config::Config; use super::config::ConfigSnapshot; +use super::config::WorkspaceSettings; use super::config::SETTINGS_SECTION; use super::diagnostics; use super::diagnostics::DiagnosticServerUpdateMessage; @@ -1265,11 +1266,10 @@ impl Inner { } { - if let Some(value) = params.initialization_options { - self.config.set_workspace_settings(value).map_err(|err| { - error!("Cannot set workspace settings: {}", err); - LspError::internal_error() - })?; + if let Some(options) = params.initialization_options { + self.config.set_workspace_settings( + WorkspaceSettings::from_initialization_options(options), + ); } if let Some(folders) = params.workspace_folders { self.config.workspace_folders = folders @@ -1472,24 +1472,26 @@ impl Inner { async fn did_change_configuration( &mut self, - client_workspace_config: Option, + client_workspace_config: Option, params: DidChangeConfigurationParams, ) { let maybe_config = if self.config.client_capabilities.workspace_configuration { client_workspace_config } else { - params - .settings - .as_object() - .and_then(|settings| settings.get(SETTINGS_SECTION)) - .cloned() + params.settings.as_object().map(|settings| { + let deno = + serde_json::to_value(settings.get(SETTINGS_SECTION)).unwrap(); + let javascript = + serde_json::to_value(settings.get("javascript")).unwrap(); + let typescript = + serde_json::to_value(settings.get("typescript")).unwrap(); + WorkspaceSettings::from_raw_settings(deno, javascript, typescript) + }) }; - if let Some(value) = maybe_config { - if let Err(err) = self.config.set_workspace_settings(value) { - error!("failed to update settings: {}", err); - } + if let Some(settings) = maybe_config { + self.config.set_workspace_settings(settings); } self.update_debug_flag(); @@ -1929,7 +1931,10 @@ impl Inner { (&self.fmt_options.options).into(), tsc::UserPreferences { quote_preference: Some((&self.fmt_options.options).into()), - ..self.config.workspace_settings().into() + ..tsc::UserPreferences::from_workspace_settings_for_specifier( + self.config.workspace_settings(), + &specifier, + ) }, ) .await; @@ -1990,7 +1995,10 @@ impl Inner { ..line_index.offset_tsc(params.range.end)?, Some(tsc::UserPreferences { quote_preference: Some((&self.fmt_options.options).into()), - ..self.config.workspace_settings().into() + ..tsc::UserPreferences::from_workspace_settings_for_specifier( + self.config.workspace_settings(), + &specifier, + ) }), only, ) @@ -2049,7 +2057,10 @@ impl Inner { (&self.fmt_options.options).into(), tsc::UserPreferences { quote_preference: Some((&self.fmt_options.options).into()), - ..self.config.workspace_settings().into() + ..tsc::UserPreferences::from_workspace_settings_for_specifier( + self.config.workspace_settings(), + &code_action_data.specifier, + ) }, ) .await?; @@ -2090,7 +2101,7 @@ impl Inner { .ts_server .get_edits_for_refactor( self.snapshot(), - action_data.specifier, + action_data.specifier.clone(), (&self.fmt_options.options).into(), line_index.offset_tsc(action_data.range.start)? ..line_index.offset_tsc(action_data.range.end)?, @@ -2098,7 +2109,10 @@ impl Inner { action_data.action_name, Some(tsc::UserPreferences { quote_preference: Some((&self.fmt_options.options).into()), - ..self.config.workspace_settings().into() + ..tsc::UserPreferences::from_workspace_settings_for_specifier( + self.config.workspace_settings(), + &action_data.specifier, + ) }), ) .await?; @@ -2425,9 +2439,11 @@ impl Inner { ), include_automatic_optional_chain_completions: Some(true), include_completions_for_import_statements: Some(true), - include_completions_for_module_exports: Some( - self.config.workspace_settings().suggest.auto_imports, - ), + include_completions_for_module_exports: self + .config + .workspace_settings() + .language_settings_for_specifier(&specifier) + .map(|s| s.suggest.auto_imports), include_completions_with_object_literal_method_snippets: Some( use_snippets, ), @@ -2454,7 +2470,13 @@ impl Inner { if let Some(completions) = maybe_completion_info { let results = completions.as_completion_response( line_index, - &self.config.workspace_settings().suggest, + &self + .config + .workspace_settings() + .language_settings_for_specifier(&specifier) + .cloned() + .unwrap_or_default() + .suggest, &specifier, position, self, @@ -3284,7 +3306,7 @@ impl tower_lsp::LanguageServer for LanguageServer { .workspace_configuration() .await; match config_response { - Ok(value) => Some(value), + Ok(settings) => Some(settings), Err(err) => { error!("{}", err); None @@ -3601,7 +3623,7 @@ impl Inner { let workspace_settings = self.config.workspace_settings(); if !self.is_diagnosable(&specifier) || !self.config.specifier_enabled(&specifier) - || !workspace_settings.enabled_inlay_hints() + || !workspace_settings.enabled_inlay_hints(&specifier) { return Ok(None); } @@ -3620,11 +3642,14 @@ impl Inner { .ts_server .provide_inlay_hints( self.snapshot(), - specifier, + specifier.clone(), text_span, tsc::UserPreferences { quote_preference: Some((&self.fmt_options.options).into()), - ..workspace_settings.into() + ..tsc::UserPreferences::from_workspace_settings_for_specifier( + workspace_settings, + &specifier, + ) }, ) .await?; diff --git a/cli/lsp/repl.rs b/cli/lsp/repl.rs index c09a142d68..5e1bb66ded 100644 --- a/cli/lsp/repl.rs +++ b/cli/lsp/repl.rs @@ -33,7 +33,9 @@ use tower_lsp::LanguageServer; use super::client::Client; use super::config::CompletionSettings; +use super::config::DenoCompletionSettings; use super::config::ImportCompletionSettings; +use super::config::LanguageWorkspaceSettings; use super::config::TestingSettings; use super::config::WorkspaceSettings; @@ -292,23 +294,36 @@ pub fn get_repl_workspace_settings() -> WorkspaceSettings { cache: None, import_map: None, code_lens: Default::default(), - inlay_hints: Default::default(), internal_debug: false, lint: false, document_preload_limit: 0, // don't pre-load any modules as it's expensive and not useful for the repl tls_certificate: None, unsafely_ignore_certificate_errors: None, unstable: false, - suggest: CompletionSettings { - complete_function_calls: false, - names: false, - paths: false, - auto_imports: false, + suggest: DenoCompletionSettings { imports: ImportCompletionSettings { auto_discover: false, hosts: HashMap::from([("https://deno.land".to_string(), true)]), }, }, testing: TestingSettings { args: vec![] }, + javascript: LanguageWorkspaceSettings { + inlay_hints: Default::default(), + suggest: CompletionSettings { + complete_function_calls: false, + names: false, + paths: false, + auto_imports: false, + }, + }, + typescript: LanguageWorkspaceSettings { + inlay_hints: Default::default(), + suggest: CompletionSettings { + complete_function_calls: false, + names: false, + paths: false, + auto_imports: false, + }, + }, } } diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs index 6ed0cf1384..e4c71e976f 100644 --- a/cli/lsp/tsc.rs +++ b/cli/lsp/tsc.rs @@ -3665,36 +3665,35 @@ pub struct UserPreferences { pub auto_import_file_exclude_patterns: Option>, } -impl From<&config::WorkspaceSettings> for UserPreferences { - fn from(workspace_settings: &config::WorkspaceSettings) -> Self { - let inlay_hints = &workspace_settings.inlay_hints; +impl UserPreferences { + pub fn from_workspace_settings_for_specifier( + settings: &config::WorkspaceSettings, + specifier: &ModuleSpecifier, + ) -> Self { + let language_settings = settings.language_settings_for_specifier(specifier); Self { - include_inlay_parameter_name_hints: Some( - (&inlay_hints.parameter_names.enabled).into(), - ), - include_inlay_parameter_name_hints_when_argument_matches_name: Some( - !inlay_hints - .parameter_names - .suppress_when_argument_matches_name, - ), - include_inlay_function_parameter_type_hints: Some( - inlay_hints.parameter_types.enabled, - ), - include_inlay_variable_type_hints: Some( - inlay_hints.variable_types.enabled, - ), - include_inlay_variable_type_hints_when_type_matches_name: Some( - !inlay_hints.variable_types.suppress_when_type_matches_name, - ), - include_inlay_property_declaration_type_hints: Some( - inlay_hints.property_declaration_types.enabled, - ), - include_inlay_function_like_return_type_hints: Some( - inlay_hints.function_like_return_types.enabled, - ), - include_inlay_enum_member_value_hints: Some( - inlay_hints.enum_member_values.enabled, - ), + include_inlay_parameter_name_hints: language_settings + .map(|s| (&s.inlay_hints.parameter_names.enabled).into()), + include_inlay_parameter_name_hints_when_argument_matches_name: + language_settings.map(|s| { + !s.inlay_hints + .parameter_names + .suppress_when_argument_matches_name + }), + include_inlay_function_parameter_type_hints: language_settings + .map(|s| s.inlay_hints.parameter_types.enabled), + include_inlay_variable_type_hints: language_settings + .map(|s| s.inlay_hints.variable_types.enabled), + include_inlay_variable_type_hints_when_type_matches_name: + language_settings.map(|s| { + !s.inlay_hints.variable_types.suppress_when_type_matches_name + }), + include_inlay_property_declaration_type_hints: language_settings + .map(|s| s.inlay_hints.property_declaration_types.enabled), + include_inlay_function_like_return_type_hints: language_settings + .map(|s| s.inlay_hints.function_like_return_types.enabled), + include_inlay_enum_member_value_hints: language_settings + .map(|s| s.inlay_hints.enum_member_values.enabled), ..Default::default() } } @@ -5153,14 +5152,20 @@ mod tests { fn include_suppress_inlay_hit_settings() { let mut settings = WorkspaceSettings::default(); settings + .typescript .inlay_hints .parameter_names .suppress_when_argument_matches_name = true; settings + .typescript .inlay_hints .variable_types .suppress_when_type_matches_name = true; - let user_preferences: UserPreferences = (&settings).into(); + let user_preferences = + UserPreferences::from_workspace_settings_for_specifier( + &settings, + &ModuleSpecifier::parse("file:///foo.ts").unwrap(), + ); assert_eq!( user_preferences.include_inlay_variable_type_hints_when_type_matches_name, Some(false) diff --git a/cli/tests/integration/lsp_tests.rs b/cli/tests/integration/lsp_tests.rs index c211bbae48..4bc4713c6b 100644 --- a/cli/tests/integration/lsp_tests.rs +++ b/cli/tests/integration/lsp_tests.rs @@ -776,12 +776,12 @@ fn lsp_import_attributes() { "text": "{\"a\":1}" } }), - json!([{ + &json!({ "deno": { "enable": true, "codeLens": { "test": true } - }]), + } }), ); let diagnostics = client.did_open(json!({ @@ -1131,7 +1131,7 @@ fn lsp_hover_disabled() { "text": "console.log(Date.now());\n" } }), - json!([{ "enable": false }]), + &json!({ "deno": { "enable": false } }), ); let res = client.write_request( @@ -1329,11 +1329,11 @@ fn lsp_workspace_disable_enable_paths() { }]) .set_deno_enable(false); }, - json!([{ + json!({ "deno": { "enable": false, "disablePaths": ["./worker/node.ts"], "enablePaths": ["./worker"], - }]), + } }), ); client.did_open(json!({ @@ -3552,12 +3552,12 @@ fn lsp_code_lens_test_disabled() { } }), // disable test code lens - json!([{ + &json!({ "deno": { "enable": true, "codeLens": { "test": false } - }]), + } }), ); let res = client.write_request( "textDocument/codeLens", @@ -5160,7 +5160,7 @@ fn lsp_code_actions_deadlock() { "text": large_file_text, } })); - client.handle_configuration_request(json!([{ "enable": true }])); + client.handle_configuration_request(&json!({ "deno": { "enable": true } })); client.write_request( "textDocument/semanticTokens/full", json!({ @@ -5796,9 +5796,9 @@ fn lsp_semantic_tokens_for_disabled_module() { |builder| { builder.set_deno_enable(false); }, - json!({ + json!({ "deno": { "enable": false - }), + } }), ); client.did_open(json!({ "textDocument": { @@ -8096,7 +8096,7 @@ fn lsp_configuration_did_change() { "settings": {} }), ); - let request = json!([{ + let settings = json!({ "deno": { "enable": true, "codeLens": { "implementations": true, @@ -8116,11 +8116,11 @@ fn lsp_configuration_did_change() { } }, "unstable": false - }]); + } }); // one for the workspace - client.handle_configuration_request(request.clone()); + client.handle_configuration_request(&settings); // one for the specifier - client.handle_configuration_request(request); + client.handle_configuration_request(&settings); let list = client.get_completion_list( "file:///a/file.ts", @@ -8192,16 +8192,20 @@ fn lsp_completions_complete_function_calls() { "settings": {} }), ); - let request = json!([{ - "enable": true, - "suggest": { - "completeFunctionCalls": true, + let settings = json!({ + "deno": { + "enable": true, }, - }]); + "typescript": { + "suggest": { + "completeFunctionCalls": true, + }, + }, + }); // one for the workspace - client.handle_configuration_request(request.clone()); + client.handle_configuration_request(&settings); // one for the specifier - client.handle_configuration_request(request); + client.handle_configuration_request(&settings); let list = client.get_completion_list( "file:///a/file.ts", @@ -9304,7 +9308,7 @@ fn lsp_node_modules_dir() { }), ); - let request = json!([{ + let settings = json!({ "deno": { "enable": true, "config": "./deno.json", "codeLens": { @@ -9321,11 +9325,11 @@ fn lsp_node_modules_dir() { "imports": {} }, "unstable": false - }]); + } }); // one for the workspace - client.handle_configuration_request(request.clone()); + client.handle_configuration_request(&settings); // one for the specifier - client.handle_configuration_request(request); + client.handle_configuration_request(&settings); }; refresh_config(&mut client); @@ -9439,7 +9443,7 @@ fn lsp_vendor_dir() { }), ); - let request = json!([{ + let settings = json!({ "deno": { "enable": true, "config": "./deno.json", "codeLens": { @@ -9456,11 +9460,11 @@ fn lsp_vendor_dir() { "imports": {} }, "unstable": false - }]); + } }); // one for the workspace - client.handle_configuration_request(request.clone()); + client.handle_configuration_request(&settings); // one for the specifier - client.handle_configuration_request(request); + client.handle_configuration_request(&settings); }; refresh_config(&mut client); diff --git a/test_util/src/lsp.rs b/test_util/src/lsp.rs index 2af27e8d44..72d27f6d09 100644 --- a/test_util/src/lsp.rs +++ b/test_util/src/lsp.rs @@ -685,9 +685,9 @@ impl LspClient { ) { self.initialize_with_config( do_build, - json!([{ + json!({"deno":{ "enable": true - }]), + }}), ) } @@ -709,18 +709,18 @@ impl LspClient { self.write_notification("initialized", json!({})); self.config = config; if self.supports_workspace_configuration { - self.handle_configuration_request(self.config.clone()); + self.handle_configuration_request(&self.config.clone()); } } pub fn did_open(&mut self, params: Value) -> CollectedDiagnostics { - self.did_open_with_config(params, self.config.clone()) + self.did_open_with_config(params, &self.config.clone()) } pub fn did_open_with_config( &mut self, params: Value, - config: Value, + config: &Value, ) -> CollectedDiagnostics { self.did_open_raw(params); if self.supports_workspace_configuration { @@ -733,9 +733,18 @@ impl LspClient { self.write_notification("textDocument/didOpen", params); } - pub fn handle_configuration_request(&mut self, result: Value) { - let (id, method, _) = self.read_request::(); + pub fn handle_configuration_request(&mut self, settings: &Value) { + let (id, method, args) = self.read_request::(); assert_eq!(method, "workspace/configuration"); + let params = args.as_ref().unwrap().as_object().unwrap(); + let items = params.get("items").unwrap().as_array().unwrap(); + let settings_object = settings.as_object().unwrap(); + let mut result = vec![]; + for item in items { + let item = item.as_object().unwrap(); + let section = item.get("section").unwrap().as_str().unwrap(); + result.push(settings_object.get(section).cloned().unwrap_or_default()); + } self.write_response(id, result); }