diff --git a/cli/lsp/capabilities.rs b/cli/lsp/capabilities.rs index 87d2c31da3..49adc961d9 100644 --- a/cli/lsp/capabilities.rs +++ b/cli/lsp/capabilities.rs @@ -154,7 +154,7 @@ pub fn server_capabilities( workspace: Some(WorkspaceServerCapabilities { workspace_folders: Some(WorkspaceFoldersServerCapabilities { supported: Some(true), - change_notifications: None, + change_notifications: Some(OneOf::Left(true)), }), file_operations: None, }), diff --git a/cli/lsp/config.rs b/cli/lsp/config.rs index d35451e84f..7b42949439 100644 --- a/cli/lsp/config.rs +++ b/cli/lsp/config.rs @@ -1,12 +1,14 @@ // Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. +use super::client::Client; +use super::logging::lsp_log; +use crate::fs_util; use deno_core::error::AnyError; use deno_core::serde::Deserialize; use deno_core::serde::Serialize; use deno_core::serde_json; use deno_core::serde_json::Value; use deno_core::ModuleSpecifier; -use lsp::WorkspaceFolder; use lspower::lsp; use std::collections::BTreeMap; use std::collections::HashMap; @@ -128,6 +130,10 @@ impl Default for ImportCompletionSettings { pub struct SpecifierSettings { /// A flag that indicates if Deno is enabled for this specifier or not. pub enable: bool, + /// A list of paths, using the workspace folder as a base that should be Deno + /// enabled. + #[serde(default)] + pub enable_paths: Vec, /// Code lens specific settings for the resource. #[serde(default)] pub code_lens: CodeLensSpecifierSettings, @@ -141,6 +147,10 @@ pub struct WorkspaceSettings { #[serde(default)] pub enable: bool, + /// A list of paths, using the root_uri as a base that should be Deno enabled. + #[serde(default)] + pub enable_paths: Vec, + /// An option that points to a path string of the path to utilise as the /// cache/DENO_DIR for the language server. pub cache: Option, @@ -198,14 +208,27 @@ impl WorkspaceSettings { #[derive(Debug, Clone, Default)] pub struct ConfigSnapshot { pub client_capabilities: ClientCapabilities, + pub enabled_paths: HashMap>, pub settings: Settings, - pub workspace_folders: Option>, } impl ConfigSnapshot { + /// Determine if the provided specifier is enabled or not. pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool { - if let Some(settings) = self.settings.specifiers.get(specifier) { - settings.1.enable + if !self.enabled_paths.is_empty() { + let specifier_str = specifier.to_string(); + for (workspace, enabled_paths) in self.enabled_paths.iter() { + if specifier_str.starts_with(workspace) { + return enabled_paths + .iter() + .any(|path| specifier_str.starts_with(path)); + } + } + } + if let Some((_, SpecifierSettings { enable, .. })) = + self.settings.specifiers.get(specifier) + { + *enable } else { self.settings.workspace.enable } @@ -228,14 +251,19 @@ pub struct Settings { #[derive(Debug)] pub struct Config { pub client_capabilities: ClientCapabilities, + enabled_paths: HashMap>, + pub root_uri: Option, settings: Settings, - pub workspace_folders: Option>, + pub workspace_folders: Option>, } impl Config { pub fn new() -> Self { Self { client_capabilities: ClientCapabilities::default(), + enabled_paths: Default::default(), + /// Root provided by the initialization parameters. + root_uri: None, settings: Default::default(), workspace_folders: None, } @@ -259,8 +287,8 @@ impl Config { pub fn snapshot(&self) -> Arc { Arc::new(ConfigSnapshot { client_capabilities: self.client_capabilities.clone(), + enabled_paths: self.enabled_paths.clone(), settings: self.settings.clone(), - workspace_folders: self.workspace_folders.clone(), }) } @@ -269,6 +297,16 @@ impl Config { } pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool { + if !self.enabled_paths.is_empty() { + let specifier_str = specifier.to_string(); + for (workspace, enabled_paths) in self.enabled_paths.iter() { + if specifier_str.starts_with(workspace) { + return enabled_paths + .iter() + .any(|path| specifier_str.starts_with(path)); + } + } + } self .settings .specifiers @@ -321,6 +359,66 @@ impl Config { } } + /// Given the configured workspaces or root URI and the their settings, + /// update and resolve any paths that should be enabled + pub async fn update_enabled_paths(&mut self, client: Client) -> bool { + if let Some(workspace_folders) = self.workspace_folders.clone() { + let mut touched = false; + for (workspace, folder) in workspace_folders { + if let Ok(settings) = client.specifier_configuration(&folder.uri).await + { + if self.update_enabled_paths_entry(&workspace, settings.enable_paths) + { + touched = true; + } + } + } + touched + } else if let Some(root_uri) = self.root_uri.clone() { + self.update_enabled_paths_entry( + &root_uri, + self.settings.workspace.enable_paths.clone(), + ) + } else { + false + } + } + + /// Update a specific entry in the enabled paths for a given workspace. + fn update_enabled_paths_entry( + &mut self, + workspace: &ModuleSpecifier, + enabled_paths: Vec, + ) -> bool { + let workspace = fs_util::ensure_directory_specifier(workspace.clone()); + let key = workspace.to_string(); + let mut touched = false; + if !enabled_paths.is_empty() { + if let Ok(workspace_path) = fs_util::specifier_to_file_path(&workspace) { + let mut paths = Vec::new(); + for path in &enabled_paths { + let fs_path = workspace_path.join(path); + match ModuleSpecifier::from_file_path(fs_path) { + Ok(path_uri) => { + paths.push(path_uri.to_string()); + } + Err(_) => { + lsp_log!("Unable to resolve a file path for `deno.enablePath` from \"{}\" for workspace \"{}\".", path, workspace); + } + } + } + if !paths.is_empty() { + touched = true; + self.enabled_paths.insert(key, paths); + } + } + } else { + touched = true; + self.enabled_paths.remove(&key); + } + touched + } + pub fn get_specifiers_with_client_uris(&self) -> Vec { self .settings @@ -330,7 +428,7 @@ impl Config { specifier: s.clone(), client_uri: u.clone(), }) - .collect::>() + .collect() } pub fn set_specifier_settings( @@ -352,33 +450,9 @@ mod tests { use deno_core::resolve_url; use deno_core::serde_json::json; - #[derive(Debug, Default)] - struct MockLanguageServer; - - #[lspower::async_trait] - impl lspower::LanguageServer for MockLanguageServer { - async fn initialize( - &self, - _params: lspower::lsp::InitializeParams, - ) -> lspower::jsonrpc::Result { - Ok(lspower::lsp::InitializeResult { - capabilities: lspower::lsp::ServerCapabilities::default(), - server_info: None, - }) - } - - async fn shutdown(&self) -> lspower::jsonrpc::Result<()> { - Ok(()) - } - } - - fn setup() -> Config { - Config::new() - } - #[test] fn test_config_specifier_enabled() { - let mut config = setup(); + let mut config = Config::new(); let specifier = resolve_url("file:///a.ts").unwrap(); assert!(!config.specifier_enabled(&specifier)); config @@ -389,9 +463,43 @@ mod tests { assert!(config.specifier_enabled(&specifier)); } + #[test] + fn test_config_snapshot_specifier_enabled() { + let mut config = Config::new(); + let specifier = resolve_url("file:///a.ts").unwrap(); + assert!(!config.specifier_enabled(&specifier)); + config + .set_workspace_settings(json!({ + "enable": true + })) + .expect("could not update"); + let config_snapshot = config.snapshot(); + assert!(config_snapshot.specifier_enabled(&specifier)); + } + + #[test] + fn test_config_specifier_enabled_path() { + let mut config = Config::new(); + let specifier_a = resolve_url("file:///project/worker/a.ts").unwrap(); + let specifier_b = resolve_url("file:///project/other/b.ts").unwrap(); + assert!(!config.specifier_enabled(&specifier_a)); + assert!(!config.specifier_enabled(&specifier_b)); + let mut enabled_paths = HashMap::new(); + enabled_paths.insert( + "file:///project/".to_string(), + vec!["file:///project/worker/".to_string()], + ); + config.enabled_paths = enabled_paths; + assert!(config.specifier_enabled(&specifier_a)); + assert!(!config.specifier_enabled(&specifier_b)); + let config_snapshot = config.snapshot(); + assert!(config_snapshot.specifier_enabled(&specifier_a)); + assert!(!config_snapshot.specifier_enabled(&specifier_b)); + } + #[test] fn test_set_workspace_settings_defaults() { - let mut config = setup(); + let mut config = Config::new(); config .set_workspace_settings(json!({})) .expect("could not update"); @@ -399,6 +507,7 @@ mod tests { config.get_workspace_settings(), WorkspaceSettings { enable: false, + enable_paths: Vec::new(), cache: None, certificate_stores: None, config: None, diff --git a/cli/lsp/diagnostics.rs b/cli/lsp/diagnostics.rs index 8801af7225..8a515ef3cd 100644 --- a/cli/lsp/diagnostics.rs +++ b/cli/lsp/diagnostics.rs @@ -845,7 +845,8 @@ async fn generate_deps_diagnostics( break; } let mut diagnostics = Vec::new(); - if config.specifier_enabled(document.specifier()) { + let specifier = document.specifier(); + if config.specifier_enabled(specifier) { for (_, dependency) in document.dependencies() { diagnose_dependency( &mut diagnostics, @@ -866,7 +867,7 @@ async fn generate_deps_diagnostics( } } diagnostics_vec.push(( - document.specifier().clone(), + specifier.clone(), document.maybe_lsp_version(), diagnostics, )); @@ -985,6 +986,7 @@ let c: number = "a"; specifier.clone(), SpecifierSettings { enable: false, + enable_paths: Vec::new(), code_lens: Default::default(), }, ), diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index f9dfce4d5f..5db7011bb2 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -117,8 +117,6 @@ pub(crate) struct Inner { maybe_import_map_uri: Option, /// A collection of measurements which instrument that performance of the LSP. performance: Arc, - /// Root provided by the initialization parameters. - root_uri: Option, /// A memoized version of fixable diagnostic codes retrieved from TypeScript. ts_fixable_diagnostics: Vec, /// An abstraction that handles interactions with TypeScript. @@ -173,7 +171,6 @@ impl Inner { maybe_import_map_uri: None, module_registries, module_registries_location, - root_uri: None, performance, ts_fixable_diagnostics: Default::default(), ts_server, @@ -306,10 +303,10 @@ impl Inner { let maybe_config = workspace_settings.config; if let Some(config_str) = &maybe_config { if !config_str.is_empty() { - lsp_log!("Setting TypeScript configuration from: \"{}\"", config_str); + lsp_log!("Setting Deno configuration from: \"{}\"", config_str); let config_url = if let Ok(url) = Url::from_file_path(config_str) { Ok(url) - } else if let Some(root_uri) = &self.root_uri { + } else if let Some(root_uri) = &self.config.root_uri { root_uri.join(config_str).map_err(|_| { anyhow!("Bad file path for configuration file: \"{}\"", config_str) }) @@ -331,7 +328,7 @@ impl Inner { // It is possible that root_uri is not set, for example when having a single // file open and not a workspace. In those situations we can't // automatically discover the configuration - if let Some(root_uri) = &self.root_uri { + if let Some(root_uri) = &self.config.root_uri { let root_path = fs_util::specifier_to_file_path(root_uri)?; let mut checked = std::collections::HashSet::new(); let maybe_config = @@ -392,7 +389,7 @@ impl Inner { assets: self.assets.snapshot(), cache_metadata: self.cache_metadata.clone(), documents: self.documents.clone(), - root_uri: self.root_uri.clone(), + root_uri: self.config.root_uri.clone(), }) } @@ -405,7 +402,7 @@ impl Inner { lsp_log!("Setting cache path from: \"{}\"", cache_str); let cache_url = if let Ok(url) = Url::from_file_path(cache_str) { Ok(url) - } else if let Some(root_uri) = &self.root_uri { + } else if let Some(root_uri) = &self.config.root_uri { let root_path = fs_util::specifier_to_file_path(root_uri)?; let cache_path = root_path.join(cache_str); Url::from_file_path(cache_path).map_err(|_| { @@ -434,6 +431,7 @@ impl Inner { let module_registries_location = dir.root.join(REGISTRIES_PATH); let workspace_settings = self.config.get_workspace_settings(); let maybe_root_path = self + .config .root_uri .as_ref() .and_then(|uri| fs_util::specifier_to_file_path(uri).ok()); @@ -477,7 +475,7 @@ impl Inner { Some(Url::parse(&import_map_str).map_err(|_| { anyhow!("Bad data url for import map: {}", import_map_str) })?) - } else if let Some(root_uri) = &self.root_uri { + } else if let Some(root_uri) = &self.config.root_uri { let root_path = fs_util::specifier_to_file_path(root_uri)?; let import_map_path = root_path.join(&import_map_str); Some(Url::from_file_path(import_map_path).map_err(|_| { @@ -554,6 +552,7 @@ impl Inner { let mark = self.performance.mark("update_registries", None::<()>); let workspace_settings = self.config.get_workspace_settings(); let maybe_root_path = self + .config .root_uri .as_ref() .and_then(|uri| fs_util::specifier_to_file_path(uri).ok()); @@ -687,7 +686,7 @@ impl Inner { { // sometimes this root uri may not have a trailing slash, so force it to - self.root_uri = params + self.config.root_uri = params .root_uri .map(|s| self.url_map.normalize_url(&s)) .map(fs_util::ensure_directory_specifier); @@ -698,6 +697,12 @@ impl Inner { LspError::internal_error() })?; } + self.config.workspace_folders = params.workspace_folders.map(|folders| { + folders + .into_iter() + .map(|folder| (self.url_map.normalize_url(&folder.uri), folder)) + .collect() + }); self.config.update_capabilities(¶ms.capabilities); } @@ -774,6 +779,7 @@ impl Inner { warn!("Client errored on capabilities.\n{}", err); } } + self.config.update_enabled_paths(self.client.clone()).await; lsp_log!("Server ready."); } @@ -952,6 +958,34 @@ impl Inner { self.performance.measure(mark); } + async fn did_change_workspace_folders( + &mut self, + params: DidChangeWorkspaceFoldersParams, + ) { + let mark = self + .performance + .mark("did_change_workspace_folders", Some(¶ms)); + let mut workspace_folders = params + .event + .added + .into_iter() + .map(|folder| (self.url_map.normalize_url(&folder.uri), folder)) + .collect::>(); + if let Some(current_folders) = &self.config.workspace_folders { + for (specifier, folder) in current_folders { + if !params.event.removed.is_empty() + && params.event.removed.iter().any(|f| f.uri == folder.uri) + { + continue; + } + workspace_folders.push((specifier.clone(), folder.clone())); + } + } + + self.config.workspace_folders = Some(workspace_folders); + self.performance.measure(mark); + } + async fn document_symbol( &mut self, params: DocumentSymbolParams, @@ -1896,6 +1930,7 @@ impl Inner { })?; let maybe_root_path_owned = self + .config .root_uri .as_ref() .and_then(|uri| fs_util::specifier_to_file_path(uri).ok()); @@ -1944,6 +1979,7 @@ impl Inner { })?; let maybe_root_path_owned = self + .config .root_uri .as_ref() .and_then(|uri| fs_util::specifier_to_file_path(uri).ok()); @@ -1999,6 +2035,7 @@ impl Inner { let response = if let Some(one_or_many) = maybe_one_or_many { let maybe_root_path_owned = self + .config .root_uri .as_ref() .and_then(|uri| fs_util::specifier_to_file_path(uri).ok()); @@ -2390,7 +2427,7 @@ impl lspower::LanguageServer for LanguageServer { if document.is_diagnosable() { let specifiers = inner.documents.dependents(&specifier); inner.diagnostics_server.invalidate(&specifiers); - // don't send diagnotics yet if we don't have the specifier settings + // don't send diagnostics yet if we don't have the specifier settings if has_specifier_settings { inner.send_diagnostics_update(); } @@ -2467,7 +2504,8 @@ impl lspower::LanguageServer for LanguageServer { ) }; - // start retreiving all the specifiers' settings outside the lock on its own time + // start retrieving all the specifiers' settings outside the lock on its own + // time if let Some(specifiers) = specifiers { let language_server = self.clone(); let client = client.clone(); @@ -2495,6 +2533,15 @@ impl lspower::LanguageServer for LanguageServer { } } } + let mut ls = language_server.0.lock().await; + if ls.config.update_enabled_paths(client).await { + ls.diagnostics_server.invalidate_all(); + // this will be called in the inner did_change_configuration, but the + // problem then becomes, if there was a change, the snapshot used + // will be an out of date one, so we will call it again here if the + // workspace folders have been touched + ls.send_diagnostics_update(); + } }); } @@ -2533,6 +2580,25 @@ impl lspower::LanguageServer for LanguageServer { self.0.lock().await.did_change_watched_files(params).await } + async fn did_change_workspace_folders( + &self, + params: DidChangeWorkspaceFoldersParams, + ) { + let client = { + let mut inner = self.0.lock().await; + inner.did_change_workspace_folders(params).await; + inner.client.clone() + }; + let language_server = self.clone(); + tokio::spawn(async move { + let mut ls = language_server.0.lock().await; + if ls.config.update_enabled_paths(client).await { + ls.diagnostics_server.invalidate_all(); + ls.send_diagnostics_update(); + } + }); + } + async fn document_symbol( &self, params: DocumentSymbolParams, diff --git a/cli/lsp/repl.rs b/cli/lsp/repl.rs index a208b2266c..1026c7bdae 100644 --- a/cli/lsp/repl.rs +++ b/cli/lsp/repl.rs @@ -273,6 +273,7 @@ fn get_cwd_uri() -> Result { pub fn get_repl_workspace_settings() -> WorkspaceSettings { WorkspaceSettings { enable: true, + enable_paths: Vec::new(), config: None, certificate_stores: None, cache: None, diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs index 4f3e88e7d4..eaeef7a514 100644 --- a/cli/lsp/tsc.rs +++ b/cli/lsp/tsc.rs @@ -882,11 +882,6 @@ impl DocumentSpan { language_server: &language_server::Inner, ) -> Option { let specifier = normalize_specifier(&self.file_name).ok()?; - log::info!( - "to_target file_name: {} specifier: {}", - self.file_name, - specifier - ); let asset_or_doc = language_server.get_maybe_cached_asset_or_document(&specifier)?; let line_index = asset_or_doc.line_index(); diff --git a/cli/tests/integration/lsp_tests.rs b/cli/tests/integration/lsp_tests.rs index 5547dc2140..90fbd608d9 100644 --- a/cli/tests/integration/lsp_tests.rs +++ b/cli/tests/integration/lsp_tests.rs @@ -57,24 +57,22 @@ where .write_notification("textDocument/didOpen", params) .unwrap(); - handle_configuration_request(client); + handle_configuration_request( + client, + json!([{ + "enable": true, + "codeLens": { + "test": true + } + }]), + ); read_diagnostics(client).0 } -fn handle_configuration_request(client: &mut LspClient) { +fn handle_configuration_request(client: &mut LspClient, result: Value) { let (id, method, _) = client.read_request::().unwrap(); assert_eq!(method, "workspace/configuration"); - client - .write_response( - id, - json!([{ - "enable": true, - "codeLens": { - "test": true - } - }]), - ) - .unwrap(); + client.write_response(id, result).unwrap(); } fn read_diagnostics(client: &mut LspClient) -> CollectedDiagnostics { @@ -97,6 +95,17 @@ fn shutdown(client: &mut LspClient) { client.write_notification("exit", json!(null)).unwrap(); } +pub fn ensure_directory_specifier( + mut specifier: ModuleSpecifier, +) -> ModuleSpecifier { + let path = specifier.path(); + if !path.ends_with('/') { + let new_path = format!("{}/", path); + specifier.set_path(&new_path); + } + specifier +} + struct TestSession { client: LspClient, open_file_count: usize, @@ -586,7 +595,15 @@ fn lsp_import_assertions() { }), ) .unwrap(); - handle_configuration_request(&mut client); + handle_configuration_request( + &mut client, + json!([{ + "enable": true, + "codeLens": { + "test": true + } + }]), + ); let diagnostics = CollectedDiagnostics(did_open( &mut client, @@ -981,11 +998,7 @@ fn lsp_hover_disabled() { ) .unwrap(); - let (id, method, _) = client.read_request::().unwrap(); - assert_eq!(method, "workspace/configuration"); - client - .write_response(id, json!([{ "enable": false }])) - .unwrap(); + handle_configuration_request(&mut client, json!([{ "enable": false }])); let (maybe_res, maybe_err) = client .write_request( @@ -1006,6 +1019,205 @@ fn lsp_hover_disabled() { shutdown(&mut client); } +#[test] +fn lsp_workspace_enable_paths() { + let mut params: lsp::InitializeParams = serde_json::from_value(load_fixture( + "initialize_params_workspace_enable_paths.json", + )) + .unwrap(); + // we aren't actually writing anything to the tempdir in this test, but we + // just need a legitimate file path on the host system so that logic that + // tries to convert to and from the fs paths works on all env + let temp_dir = TempDir::new().unwrap(); + + let root_specifier = + ensure_directory_specifier(Url::from_file_path(temp_dir.path()).unwrap()); + + params.root_uri = Some(root_specifier.clone()); + params.workspace_folders = Some(vec![lsp::WorkspaceFolder { + uri: root_specifier.clone(), + name: "project".to_string(), + }]); + + let deno_exe = deno_exe_path(); + let mut client = LspClient::new(&deno_exe).unwrap(); + client + .write_request::<_, _, Value>("initialize", params) + .unwrap(); + + client.write_notification("initialized", json!({})).unwrap(); + + handle_configuration_request( + &mut client, + json!([{ + "enable": false, + "enablePaths": [ + "./worker" + ], + }]), + ); + + did_open( + &mut client, + json!({ + "textDocument": { + "uri": root_specifier.join("./file.ts").unwrap(), + "languageId": "typescript", + "version": 1, + "text": "console.log(Date.now());\n" + } + }), + ); + + did_open( + &mut client, + json!({ + "textDocument": { + "uri": root_specifier.join("./other/file.ts").unwrap(), + "languageId": "typescript", + "version": 1, + "text": "console.log(Date.now());\n" + } + }), + ); + + did_open( + &mut client, + json!({ + "textDocument": { + "uri": root_specifier.join("./worker/file.ts").unwrap(), + "languageId": "typescript", + "version": 1, + "text": "console.log(Date.now());\n" + } + }), + ); + + did_open( + &mut client, + json!({ + "textDocument": { + "uri": root_specifier.join("./worker/subdir/file.ts").unwrap(), + "languageId": "typescript", + "version": 1, + "text": "console.log(Date.now());\n" + } + }), + ); + + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/hover", + json!({ + "textDocument": { + "uri": root_specifier.join("./file.ts").unwrap(), + }, + "position": { + "line": 0, + "character": 19 + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!(maybe_res, Some(json!(null))); + + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/hover", + json!({ + "textDocument": { + "uri": root_specifier.join("./other/file.ts").unwrap(), + }, + "position": { + "line": 0, + "character": 19 + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!(maybe_res, Some(json!(null))); + + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/hover", + json!({ + "textDocument": { + "uri": root_specifier.join("./worker/file.ts").unwrap(), + }, + "position": { + "line": 0, + "character": 19 + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "contents": [ + { + "language": "typescript", + "value": "(method) DateConstructor.now(): number", + }, + "" + ], + "range": { + "start": { + "line": 0, + "character": 17, + }, + "end": { + "line": 0, + "character": 20, + } + } + })) + ); + + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/hover", + json!({ + "textDocument": { + "uri": root_specifier.join("./worker/subdir/file.ts").unwrap(), + }, + "position": { + "line": 0, + "character": 19 + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "contents": [ + { + "language": "typescript", + "value": "(method) DateConstructor.now(): number", + }, + "" + ], + "range": { + "start": { + "line": 0, + "character": 17, + }, + "end": { + "line": 0, + "character": 20, + } + } + })) + ); + + shutdown(&mut client); +} + #[test] fn lsp_hover_unstable_disabled() { let mut client = init("initialize_params.json"); diff --git a/cli/tests/testdata/lsp/initialize_params_workspace_enable_paths.json b/cli/tests/testdata/lsp/initialize_params_workspace_enable_paths.json new file mode 100644 index 0000000000..87581ebd7d --- /dev/null +++ b/cli/tests/testdata/lsp/initialize_params_workspace_enable_paths.json @@ -0,0 +1,77 @@ +{ + "processId": 0, + "clientInfo": { + "name": "test-harness", + "version": "1.0.0" + }, + "rootUri": "file:///project/", + "initializationOptions": { + "enable": false, + "enablePaths": [ + "./worker" + ], + "cache": null, + "certificateStores": null, + "codeLens": { + "implementations": true, + "references": true, + "test": true + }, + "config": "", + "importMap": null, + "lint": true, + "suggest": { + "autoImports": true, + "completeFunctionCalls": false, + "names": true, + "paths": true, + "imports": { + "hosts": {} + } + }, + "tlsCertificate": null, + "unsafelyIgnoreCertificateErrors": null, + "unstable": false + }, + "workspaceFolders": [ + { + "uri": "file:///project/", + "name": "project" + } + ], + "capabilities": { + "textDocument": { + "codeAction": { + "codeActionLiteralSupport": { + "codeActionKind": { + "valueSet": [ + "quickfix", + "refactor" + ] + } + }, + "isPreferredSupport": true, + "dataSupport": true, + "disabledSupport": true, + "resolveSupport": { + "properties": [ + "edit" + ] + } + }, + "foldingRange": { + "lineFoldingOnly": true + }, + "synchronization": { + "dynamicRegistration": true, + "willSave": true, + "willSaveWaitUntil": true, + "didSave": true + } + }, + "workspace": { + "configuration": true, + "workspaceFolders": true + } + } +}