From 27e7bb090e9d771c8f3e4ddba4dd956a4a56855b Mon Sep 17 00:00:00 2001 From: Kitson Kelly Date: Tue, 18 May 2021 06:45:13 +1000 Subject: [PATCH] refactor: share test harness for lsp between bench and integration (#10659) --- Cargo.lock | 2 + cli/bench/lsp.rs | 266 +- cli/lsp/language_server.rs | 2395 ----------------- cli/tests/integration_tests_lsp.rs | 1661 ++++++++++++ ...e_request.json => code_action_params.json} | 27 +- cli/tests/lsp/code_action_params_cache.json | 41 + cli/tests/lsp/code_action_request.json | 44 - cli/tests/lsp/code_action_request_cache.json | 46 - cli/tests/lsp/code_action_resolve_params.json | 27 + ...json => code_action_resolve_response.json} | 0 cli/tests/lsp/code_lens_request.json | 10 - cli/tests/lsp/code_lens_request_asset.json | 10 - cli/tests/lsp/code_lens_resolve_request.json | 21 - .../lsp/code_lens_resolve_request_asset.json | 21 - .../lsp/code_lens_resolve_request_impl.json | 21 - cli/tests/lsp/code_lens_resolve_response.json | 38 + .../lsp/code_lens_resolve_response_impl.json | 38 + cli/tests/lsp/code_lens_response.json | 34 + cli/tests/lsp/code_lens_response_impl.json | 50 + cli/tests/lsp/completion_request.json | 18 - .../lsp/completion_request_optional.json | 18 - .../completion_request_params_optional.json | 13 + .../lsp/completion_request_registry.json | 18 - .../lsp/completion_request_registry_02.json | 18 - .../completion_request_response_empty.json | 38 + cli/tests/lsp/completion_resolve_params.json | 14 + .../completion_resolve_params_optional.json | 15 + .../completion_resolve_params_registry.json | 20 + cli/tests/lsp/completion_resolve_request.json | 19 - .../completion_resolve_request_optional.json | 20 - .../completion_resolve_request_registry.json | 25 - .../lsp/completion_resolve_response.json | 11 + .../completion_resolve_response_registry.json | 20 + cli/tests/lsp/definition_request_asset.json | 14 - .../lsp/did_change_notification_large.json | 25 - .../lsp/did_change_notification_large_02.json | 25 - .../lsp/did_change_notification_large_03.json | 25 - .../lsp/did_change_notification_mbc.json | 25 - cli/tests/lsp/did_open_notification.json | 12 - .../lsp/did_open_notification_asset.json | 12 - .../lsp/did_open_notification_cache.json | 12 - .../lsp/did_open_notification_cl_impl.json | 12 - .../did_open_notification_cl_references.json | 12 - .../did_open_notification_code_action.json | 12 - ...open_notification_completion_optional.json | 12 - ...open_notification_completion_registry.json | 12 - ...n_notification_completion_registry_02.json | 12 - .../did_open_notification_completions.json | 12 - .../lsp/did_open_notification_large.json | 12 - cli/tests/lsp/did_open_notification_mbc.json | 12 - .../lsp/did_open_notification_mbc_fmt.json | 12 - .../lsp/did_open_notification_unstable.json | 12 - cli/tests/lsp/did_open_params_doc_symbol.json | 8 + cli/tests/lsp/did_open_params_large.json | 8 + .../lsp/did_open_params_semantic_tokens.json | 8 + ...document_symbol_did_open_notification.json | 12 - cli/tests/lsp/document_symbol_request.json | 10 - cli/tests/lsp/document_symbol_response.json | 371 +++ cli/tests/lsp/exit_notification.json | 5 - .../folding_range_did_open_notification.json | 12 - cli/tests/lsp/folding_range_request.json | 10 - cli/tests/lsp/formatting_mbc_response.json | 54 + cli/tests/lsp/formatting_request_mbc_fmt.json | 14 - cli/tests/lsp/hover_request.json | 14 - cli/tests/lsp/hover_request_asset.json | 14 - cli/tests/lsp/hover_request_large_01.json | 14 - cli/tests/lsp/hover_request_large_02.json | 14 - cli/tests/lsp/hover_request_large_03.json | 14 - cli/tests/lsp/hover_request_mbc.json | 14 - cli/tests/lsp/incoming_calls_params.json | 28 + cli/tests/lsp/incoming_calls_request.json | 33 - cli/tests/lsp/incoming_calls_response.json | 42 + cli/tests/lsp/initialize_params.json | 56 + cli/tests/lsp/initialize_params_disabled.json | 56 + cli/tests/lsp/initialize_params_registry.json | 58 + cli/tests/lsp/initialize_params_unstable.json | 56 + cli/tests/lsp/initialize_request.json | 61 - .../lsp/initialize_request_disabled.json | 29 - .../lsp/initialize_request_registry.json | 63 - .../lsp/initialize_request_unstable.json | 30 - cli/tests/lsp/initialized_notification.json | 5 - cli/tests/lsp/outgoing_calls_params.json | 28 + cli/tests/lsp/outgoing_calls_request.json | 33 - cli/tests/lsp/outgoing_calls_response.json | 42 + cli/tests/lsp/performance_request.json | 6 - ..._call_hierarchy_did_open_notification.json | 12 - .../lsp/prepare_call_hierarchy_request.json | 14 - .../lsp/prepare_call_hierarchy_response.json | 28 + cli/tests/lsp/references_request_asset.json | 17 - .../lsp/rename_did_open_notification.json | 12 - cli/tests/lsp/rename_request.json | 15 - cli/tests/lsp/rename_response.json | 38 + ...selection_range_did_open_notification.json | 12 - cli/tests/lsp/selection_range_request.json | 16 - cli/tests/lsp/selection_range_response.json | 86 + ...semantic_tokens_did_open_notification.json | 12 - .../lsp/semantic_tokens_full_request.json | 10 - .../lsp/semantic_tokens_range_request.json | 20 - cli/tests/lsp/shutdown_request.json | 6 - ...ignature_help_did_change_notification.json | 25 - .../signature_help_did_open_notification.json | 12 - cli/tests/lsp/signature_help_request_01.json | 19 - cli/tests/lsp/signature_help_request_02.json | 14 - .../lsp/virtual_text_document_request.json | 10 - test_util/Cargo.toml | 2 + test_util/src/lib.rs | 2 + test_util/src/lsp.rs | 270 ++ 107 files changed, 3296 insertions(+), 3879 deletions(-) create mode 100644 cli/tests/integration_tests_lsp.rs rename cli/tests/lsp/{code_action_resolve_request.json => code_action_params.json} (65%) create mode 100644 cli/tests/lsp/code_action_params_cache.json delete mode 100644 cli/tests/lsp/code_action_request.json delete mode 100644 cli/tests/lsp/code_action_request_cache.json create mode 100644 cli/tests/lsp/code_action_resolve_params.json rename cli/tests/lsp/{code_action_resolve_request_response.json => code_action_resolve_response.json} (100%) delete mode 100644 cli/tests/lsp/code_lens_request.json delete mode 100644 cli/tests/lsp/code_lens_request_asset.json delete mode 100644 cli/tests/lsp/code_lens_resolve_request.json delete mode 100644 cli/tests/lsp/code_lens_resolve_request_asset.json delete mode 100644 cli/tests/lsp/code_lens_resolve_request_impl.json create mode 100644 cli/tests/lsp/code_lens_resolve_response.json create mode 100644 cli/tests/lsp/code_lens_resolve_response_impl.json create mode 100644 cli/tests/lsp/code_lens_response.json create mode 100644 cli/tests/lsp/code_lens_response_impl.json delete mode 100644 cli/tests/lsp/completion_request.json delete mode 100644 cli/tests/lsp/completion_request_optional.json create mode 100644 cli/tests/lsp/completion_request_params_optional.json delete mode 100644 cli/tests/lsp/completion_request_registry.json delete mode 100644 cli/tests/lsp/completion_request_registry_02.json create mode 100644 cli/tests/lsp/completion_request_response_empty.json create mode 100644 cli/tests/lsp/completion_resolve_params.json create mode 100644 cli/tests/lsp/completion_resolve_params_optional.json create mode 100644 cli/tests/lsp/completion_resolve_params_registry.json delete mode 100644 cli/tests/lsp/completion_resolve_request.json delete mode 100644 cli/tests/lsp/completion_resolve_request_optional.json delete mode 100644 cli/tests/lsp/completion_resolve_request_registry.json create mode 100644 cli/tests/lsp/completion_resolve_response.json create mode 100644 cli/tests/lsp/completion_resolve_response_registry.json delete mode 100644 cli/tests/lsp/definition_request_asset.json delete mode 100644 cli/tests/lsp/did_change_notification_large.json delete mode 100644 cli/tests/lsp/did_change_notification_large_02.json delete mode 100644 cli/tests/lsp/did_change_notification_large_03.json delete mode 100644 cli/tests/lsp/did_change_notification_mbc.json delete mode 100644 cli/tests/lsp/did_open_notification.json delete mode 100644 cli/tests/lsp/did_open_notification_asset.json delete mode 100644 cli/tests/lsp/did_open_notification_cache.json delete mode 100644 cli/tests/lsp/did_open_notification_cl_impl.json delete mode 100644 cli/tests/lsp/did_open_notification_cl_references.json delete mode 100644 cli/tests/lsp/did_open_notification_code_action.json delete mode 100644 cli/tests/lsp/did_open_notification_completion_optional.json delete mode 100644 cli/tests/lsp/did_open_notification_completion_registry.json delete mode 100644 cli/tests/lsp/did_open_notification_completion_registry_02.json delete mode 100644 cli/tests/lsp/did_open_notification_completions.json delete mode 100644 cli/tests/lsp/did_open_notification_large.json delete mode 100644 cli/tests/lsp/did_open_notification_mbc.json delete mode 100644 cli/tests/lsp/did_open_notification_mbc_fmt.json delete mode 100644 cli/tests/lsp/did_open_notification_unstable.json create mode 100644 cli/tests/lsp/did_open_params_doc_symbol.json create mode 100644 cli/tests/lsp/did_open_params_large.json create mode 100644 cli/tests/lsp/did_open_params_semantic_tokens.json delete mode 100644 cli/tests/lsp/document_symbol_did_open_notification.json delete mode 100644 cli/tests/lsp/document_symbol_request.json create mode 100644 cli/tests/lsp/document_symbol_response.json delete mode 100644 cli/tests/lsp/exit_notification.json delete mode 100644 cli/tests/lsp/folding_range_did_open_notification.json delete mode 100644 cli/tests/lsp/folding_range_request.json create mode 100644 cli/tests/lsp/formatting_mbc_response.json delete mode 100644 cli/tests/lsp/formatting_request_mbc_fmt.json delete mode 100644 cli/tests/lsp/hover_request.json delete mode 100644 cli/tests/lsp/hover_request_asset.json delete mode 100644 cli/tests/lsp/hover_request_large_01.json delete mode 100644 cli/tests/lsp/hover_request_large_02.json delete mode 100644 cli/tests/lsp/hover_request_large_03.json delete mode 100644 cli/tests/lsp/hover_request_mbc.json create mode 100644 cli/tests/lsp/incoming_calls_params.json delete mode 100644 cli/tests/lsp/incoming_calls_request.json create mode 100644 cli/tests/lsp/incoming_calls_response.json create mode 100644 cli/tests/lsp/initialize_params.json create mode 100644 cli/tests/lsp/initialize_params_disabled.json create mode 100644 cli/tests/lsp/initialize_params_registry.json create mode 100644 cli/tests/lsp/initialize_params_unstable.json delete mode 100644 cli/tests/lsp/initialize_request.json delete mode 100644 cli/tests/lsp/initialize_request_disabled.json delete mode 100644 cli/tests/lsp/initialize_request_registry.json delete mode 100644 cli/tests/lsp/initialize_request_unstable.json delete mode 100644 cli/tests/lsp/initialized_notification.json create mode 100644 cli/tests/lsp/outgoing_calls_params.json delete mode 100644 cli/tests/lsp/outgoing_calls_request.json create mode 100644 cli/tests/lsp/outgoing_calls_response.json delete mode 100644 cli/tests/lsp/performance_request.json delete mode 100644 cli/tests/lsp/prepare_call_hierarchy_did_open_notification.json delete mode 100644 cli/tests/lsp/prepare_call_hierarchy_request.json create mode 100644 cli/tests/lsp/prepare_call_hierarchy_response.json delete mode 100644 cli/tests/lsp/references_request_asset.json delete mode 100644 cli/tests/lsp/rename_did_open_notification.json delete mode 100644 cli/tests/lsp/rename_request.json create mode 100644 cli/tests/lsp/rename_response.json delete mode 100644 cli/tests/lsp/selection_range_did_open_notification.json delete mode 100644 cli/tests/lsp/selection_range_request.json create mode 100644 cli/tests/lsp/selection_range_response.json delete mode 100644 cli/tests/lsp/semantic_tokens_did_open_notification.json delete mode 100644 cli/tests/lsp/semantic_tokens_full_request.json delete mode 100644 cli/tests/lsp/semantic_tokens_range_request.json delete mode 100644 cli/tests/lsp/shutdown_request.json delete mode 100644 cli/tests/lsp/signature_help_did_change_notification.json delete mode 100644 cli/tests/lsp/signature_help_did_open_notification.json delete mode 100644 cli/tests/lsp/signature_help_request_01.json delete mode 100644 cli/tests/lsp/signature_help_request_02.json delete mode 100644 cli/tests/lsp/virtual_text_document_request.json create mode 100644 test_util/src/lsp.rs diff --git a/Cargo.lock b/Cargo.lock index af4168fcd6..9ce829a97b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3471,6 +3471,7 @@ dependencies = [ name = "test_util" version = "0.1.0" dependencies = [ + "anyhow", "async-stream", "bytes", "futures", @@ -3480,6 +3481,7 @@ dependencies = [ "pty", "regex", "serde", + "serde_json", "tempfile", "tokio", "tokio-rustls", diff --git a/cli/bench/lsp.rs b/cli/bench/lsp.rs index ed474b9eaa..cabcef1dbf 100644 --- a/cli/bench/lsp.rs +++ b/cli/bench/lsp.rs @@ -1,37 +1,21 @@ // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. -use deno_core::error::generic_error; use deno_core::error::AnyError; -use deno_core::serde::de; use deno_core::serde::Deserialize; -use deno_core::serde::Serialize; use deno_core::serde_json; use deno_core::serde_json::json; use deno_core::serde_json::Value; -use lazy_static::lazy_static; -use regex::Regex; use std::collections::HashMap; -use std::io::BufRead; -use std::io::Read; -use std::io::Write; use std::path::Path; -use std::process::ChildStdin; -use std::process::ChildStdout; -use std::process::Command; -use std::process::Stdio; use std::time::Duration; -use std::time::Instant; +use test_util::lsp::LspClient; +use test_util::lsp::LspResponseError; static FIXTURE_DB_TS: &str = include_str!("fixtures/db.ts"); static FIXTURE_DB_MESSAGES: &[u8] = include_bytes!("fixtures/db_messages.json"); static FIXTURE_INIT_JSON: &[u8] = include_bytes!("fixtures/initialize_params.json"); -lazy_static! { - static ref CONTENT_TYPE_REG: Regex = - Regex::new(r"(?i)^content-length:\s+(\d+)").unwrap(); -} - #[derive(Debug, Deserialize)] enum FixtureType { #[serde(rename = "action")] @@ -53,224 +37,6 @@ struct FixtureMessage { params: Value, } -#[derive(Debug, Deserialize, Serialize)] -struct LspResponseError { - code: i32, - message: String, - data: Option, -} - -#[derive(Debug)] -enum LspMessage { - Notification(String, Option), - Request(u64, String, Option), - Response(u64, Option, Option), -} - -impl<'a> From<&'a [u8]> for LspMessage { - fn from(s: &'a [u8]) -> Self { - let value: Value = serde_json::from_slice(s).unwrap(); - let obj = value.as_object().unwrap(); - if obj.contains_key("id") && obj.contains_key("method") { - let id = obj.get("id").unwrap().as_u64().unwrap(); - let method = obj.get("method").unwrap().as_str().unwrap().to_string(); - Self::Request(id, method, obj.get("params").cloned()) - } else if obj.contains_key("id") { - let id = obj.get("id").unwrap().as_u64().unwrap(); - let maybe_error: Option = obj - .get("error") - .map(|v| serde_json::from_value(v.clone()).unwrap()); - Self::Response(id, obj.get("result").cloned(), maybe_error) - } else { - assert!(obj.contains_key("method")); - let method = obj.get("method").unwrap().as_str().unwrap().to_string(); - Self::Notification(method, obj.get("params").cloned()) - } - } -} - -struct LspClient { - reader: std::io::BufReader, - child: std::process::Child, - request_id: u64, - start: Instant, - writer: std::io::BufWriter, -} - -fn read_message(reader: &mut R) -> Result, AnyError> -where - R: Read + BufRead, -{ - let mut content_length = 0_usize; - loop { - let mut buf = String::new(); - reader.read_line(&mut buf)?; - if let Some(captures) = CONTENT_TYPE_REG.captures(&buf) { - let content_length_match = captures - .get(1) - .ok_or_else(|| generic_error("missing capture"))?; - content_length = content_length_match.as_str().parse::()?; - } - if &buf == "\r\n" { - break; - } - } - - let mut msg_buf = vec![0_u8; content_length]; - reader.read_exact(&mut msg_buf)?; - Ok(msg_buf) -} - -impl Drop for LspClient { - fn drop(&mut self) { - match self.child.try_wait() { - Ok(None) => { - self.child.kill().unwrap(); - let _ = self.child.wait(); - } - Ok(Some(status)) => panic!("deno lsp exited unexpectedly {}", status), - Err(e) => panic!("pebble error: {}", e), - } - } -} - -impl LspClient { - fn new(deno_exe: &Path) -> Result { - let mut child = Command::new(deno_exe) - .arg("lsp") - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::null()) - .spawn()?; - - let stdout = child.stdout.take().unwrap(); - let reader = std::io::BufReader::new(stdout); - - let stdin = child.stdin.take().unwrap(); - let writer = std::io::BufWriter::new(stdin); - - Ok(Self { - child, - reader, - request_id: 1, - start: Instant::now(), - writer, - }) - } - - fn duration(&self) -> Duration { - self.start.elapsed() - } - - fn read(&mut self) -> Result { - let msg_buf = read_message(&mut self.reader)?; - let msg = LspMessage::from(msg_buf.as_slice()); - Ok(msg) - } - - fn read_notification(&mut self) -> Result<(String, Option), AnyError> - where - R: de::DeserializeOwned, - { - loop { - if let LspMessage::Notification(method, maybe_params) = self.read()? { - if let Some(p) = maybe_params { - let params = serde_json::from_value(p)?; - return Ok((method, Some(params))); - } else { - return Ok((method, None)); - } - } - } - } - - #[allow(unused)] - fn read_request(&mut self) -> Result<(u64, String, Option), AnyError> - where - R: de::DeserializeOwned, - { - loop { - if let LspMessage::Request(id, method, maybe_params) = self.read()? { - if let Some(p) = maybe_params { - let params = serde_json::from_value(p)?; - return Ok((id, method, Some(params))); - } else { - return Ok((id, method, None)); - } - } - } - } - - fn write(&mut self, value: Value) -> Result<(), AnyError> { - let value_str = value.to_string(); - let msg = format!( - "Content-Length: {}\r\n\r\n{}", - value_str.as_bytes().len(), - value_str - ); - self.writer.write_all(msg.as_bytes())?; - self.writer.flush()?; - Ok(()) - } - - fn write_request( - &mut self, - method: S, - params: V, - ) -> Result<(Option, Option), AnyError> - where - S: AsRef, - V: Serialize, - { - let value = json!({ - "jsonrpc": "2.0", - "id": self.request_id, - "method": method.as_ref(), - "params": params, - }); - self.write(value)?; - - loop { - if let LspMessage::Response(id, result, error) = self.read()? { - assert_eq!(id, self.request_id); - self.request_id += 1; - return Ok((result, error)); - } - } - } - - #[allow(unused)] - fn write_response(&mut self, id: u64, result: V) -> Result<(), AnyError> - where - V: Serialize, - { - let value = json!({ - "jsonrpc": "2.0", - "id": id, - "result": result - }); - self.write(value) - } - - fn write_notification( - &mut self, - method: S, - params: V, - ) -> Result<(), AnyError> - where - S: AsRef, - V: Serialize, - { - let value = json!({ - "jsonrpc": "2.0", - "method": method.as_ref(), - "params": params, - }); - self.write(value)?; - Ok(()) - } -} - /// A benchmark that opens a 8000+ line TypeScript document, adds a function to /// the end of the document and does a level of hovering and gets quick fix /// code actions. @@ -320,19 +86,29 @@ fn bench_big_file_edits(deno_exe: &Path) -> Result { for msg in messages { match msg.fixture_type { FixtureType::Action => { - client.write_request("textDocument/codeAction", msg.params)?; + client.write_request::<_, _, Value>( + "textDocument/codeAction", + msg.params, + )?; } FixtureType::Change => { client.write_notification("textDocument/didChange", msg.params)?; } FixtureType::Completion => { - client.write_request("textDocument/completion", msg.params)?; + client.write_request::<_, _, Value>( + "textDocument/completion", + msg.params, + )?; } FixtureType::Highlight => { - client.write_request("textDocument/documentHighlight", msg.params)?; + client.write_request::<_, _, Value>( + "textDocument/documentHighlight", + msg.params, + )?; } FixtureType::Hover => { - client.write_request("textDocument/hover", msg.params)?; + client + .write_request::<_, _, Value>("textDocument/hover", msg.params)?; } } } @@ -427,13 +203,3 @@ pub(crate) fn benchmarks( Ok(exec_times) } - -#[cfg(test)] -mod tests { - #[test] - fn test_read_message() { - let msg = b"content-length: 11\r\n\r\nhello world"; - let reader = std::io::Cursor::new(msg); - assert_eq!(read_message(reader).unwrap(), b"hello world"); - } -} diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index 1be65d0fe4..9f5a7c84ad 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -2608,2398 +2608,3 @@ impl Inner { Ok(contents) } } - -#[cfg(test)] -mod tests { - use super::*; - use crate::lsp::performance::PerformanceAverage; - use lspower::jsonrpc; - use lspower::ExitedError; - use lspower::LspService; - use std::fs; - use std::task::Poll; - use std::time::Instant; - use tempfile::TempDir; - use tower_test::mock::Spawn; - - enum LspResponse - where - V: FnOnce(Value), - { - None, - Delay(u64), - RequestAny, - Request(u64, Value), - RequestAssert(V), - RequestFixture(u64, String), - } - - enum LspFixture { - None, - Path(&'static str), - Value(Value), - } - - type LspTestHarnessRequest = (LspFixture, LspResponse); - - struct LspTestHarness { - requests: Vec, - service: Spawn, - } - - impl LspTestHarness { - pub fn new(requests: Vec) -> Self { - let (service, _) = LspService::new(LanguageServer::new); - let service = Spawn::new(service); - Self { requests, service } - } - - async fn run(&mut self) { - for (value_or_str, expected) in self.requests.iter() { - assert_eq!(self.service.poll_ready(), Poll::Ready(Ok(()))); - let fixtures_path = test_util::root_path().join("cli/tests/lsp"); - assert!(fixtures_path.is_dir()); - let response: Result, ExitedError> = - match value_or_str { - LspFixture::None => Ok(None), - LspFixture::Path(req_path_str) => { - let req_path = fixtures_path.join(req_path_str); - let req_str = fs::read_to_string(req_path).unwrap(); - let req: jsonrpc::Incoming = - serde_json::from_str(&req_str).unwrap(); - self.service.call(req).await - } - LspFixture::Value(value) => { - let req: jsonrpc::Incoming = - serde_json::from_value(value.clone()).unwrap(); - self.service.call(req).await - } - }; - match response { - Ok(result) => match expected { - LspResponse::None => assert_eq!(result, None), - LspResponse::Delay(millis) => { - tokio::time::sleep(tokio::time::Duration::from_millis(*millis)) - .await - } - LspResponse::RequestAny => match result { - Some(jsonrpc::Outgoing::Response(_)) => (), - _ => panic!("unexpected result: {:?}", result), - }, - LspResponse::Request(id, value) => match result { - Some(jsonrpc::Outgoing::Response(resp)) => assert_eq!( - resp, - jsonrpc::Response::ok(jsonrpc::Id::Number(*id), value.clone()) - ), - _ => panic!("unexpected result: {:?}", result), - }, - LspResponse::RequestAssert(assert) => match result { - Some(jsonrpc::Outgoing::Response(resp)) => assert(json!(resp)), - _ => panic!("unexpected result: {:?}", result), - }, - LspResponse::RequestFixture(id, res_path_str) => { - let res_path = fixtures_path.join(res_path_str); - let res_str = fs::read_to_string(res_path).unwrap(); - match result { - Some(jsonrpc::Outgoing::Response(resp)) => assert_eq!( - resp, - jsonrpc::Response::ok( - jsonrpc::Id::Number(*id), - serde_json::from_str(&res_str).unwrap() - ) - ), - _ => panic!("unexpected result: {:?}", result), - } - } - }, - Err(err) => panic!("Error result: {}", err), - } - } - } - } - - #[tokio::test] - async fn test_startup_shutdown() { - let mut harness = LspTestHarness::new(vec![ - ( - LspFixture::Path("initialize_request.json"), - LspResponse::RequestAny, - ), - ( - LspFixture::Path("initialized_notification.json"), - LspResponse::None, - ), - ( - LspFixture::Path("shutdown_request.json"), - LspResponse::Request(3, json!(null)), - ), - ( - LspFixture::Path("exit_notification.json"), - LspResponse::None, - ), - ]); - harness.run().await; - } - - #[tokio::test] - async fn test_hover() { - let mut harness = LspTestHarness::new(vec![ - ( - LspFixture::Path("initialize_request.json"), - LspResponse::RequestAny, - ), - ( - LspFixture::Path("initialized_notification.json"), - LspResponse::None, - ), - ( - LspFixture::Path("did_open_notification.json"), - LspResponse::None, - ), - ( - LspFixture::Path("hover_request.json"), - LspResponse::Request( - 2, - json!({ - "contents": [ - { - "language": "typescript", - "value": "const Deno.args: string[]" - }, - "Returns the script arguments to the program. If for example we run a\nprogram:\n\ndeno run --allow-read https://deno.land/std/examples/cat.ts /etc/passwd\n\nThen `Deno.args` will contain:\n\n[ \"/etc/passwd\" ]" - ], - "range": { - "start": { - "line": 0, - "character": 17 - }, - "end": { - "line": 0, - "character": 21 - } - } - }), - ), - ), - ( - LspFixture::Path("shutdown_request.json"), - LspResponse::Request(3, json!(null)), - ), - ( - LspFixture::Path("exit_notification.json"), - LspResponse::None, - ), - ]); - harness.run().await; - } - - #[tokio::test] - async fn test_hover_asset() { - let mut harness = LspTestHarness::new(vec![ - ( - LspFixture::Path("initialize_request.json"), - LspResponse::RequestAny, - ), - ( - LspFixture::Path("initialized_notification.json"), - LspResponse::None, - ), - ( - LspFixture::Path("did_open_notification_asset.json"), - LspResponse::None, - ), - ( - LspFixture::Path("definition_request_asset.json"), - LspResponse::RequestAny, - ), - ( - LspFixture::Path("virtual_text_document_request.json"), - LspResponse::RequestAny, - ), - ( - LspFixture::Path("hover_request_asset.json"), - LspResponse::Request( - 5, - json!({ - "contents": [ - { - "language": "typescript", - "value": "interface Date", - }, - "Enables basic storage and retrieval of dates and times." - ], - "range": { - "start": { - "line": 109, - "character": 10, - }, - "end": { - "line": 109, - "character": 14, - } - } - }), - ), - ), - ( - LspFixture::Path("shutdown_request.json"), - LspResponse::Request(3, json!(null)), - ), - ( - LspFixture::Path("exit_notification.json"), - LspResponse::None, - ), - ]); - harness.run().await; - } - - #[tokio::test] - async fn test_hover_disabled() { - let mut harness = LspTestHarness::new(vec![ - ( - LspFixture::Path("initialize_request_disabled.json"), - LspResponse::RequestAny, - ), - ( - LspFixture::Path("initialized_notification.json"), - LspResponse::None, - ), - ( - LspFixture::Path("did_open_notification.json"), - LspResponse::None, - ), - ( - LspFixture::Path("hover_request.json"), - LspResponse::Request(2, json!(null)), - ), - ( - LspFixture::Path("shutdown_request.json"), - LspResponse::Request(3, json!(null)), - ), - ( - LspFixture::Path("exit_notification.json"), - LspResponse::None, - ), - ]); - harness.run().await; - } - - #[tokio::test] - async fn test_hover_unstable_disabled() { - let mut harness = LspTestHarness::new(vec![ - ( - LspFixture::Path("initialize_request.json"), - LspResponse::RequestAny, - ), - ( - LspFixture::Path("initialized_notification.json"), - LspResponse::None, - ), - ( - LspFixture::Path("did_open_notification_unstable.json"), - LspResponse::None, - ), - ( - LspFixture::Path("hover_request.json"), - LspResponse::Request( - 2, - json!({ - "contents": [ - { - "language": "typescript", - "value": "any" - } - ], - "range": { - "start": { - "line": 0, - "character": 17 - }, - "end": { - "line": 0, - "character": 27 - } - } - }), - ), - ), - ( - LspFixture::Path("shutdown_request.json"), - LspResponse::Request(3, json!(null)), - ), - ( - LspFixture::Path("exit_notification.json"), - LspResponse::None, - ), - ]); - harness.run().await; - } - - #[tokio::test] - async fn test_hover_unstable_enabled() { - let mut harness = LspTestHarness::new(vec![ - ( - LspFixture::Path("initialize_request_unstable.json"), - LspResponse::RequestAny, - ), - ( - LspFixture::Path("initialized_notification.json"), - LspResponse::None, - ), - ( - LspFixture::Path("did_open_notification_unstable.json"), - LspResponse::None, - ), - ( - LspFixture::Path("hover_request.json"), - LspResponse::Request( - 2, - json!({ - "contents": [ - { - "language": "typescript", - "value": "function Deno.openPlugin(filename: string): number" - }, - "**UNSTABLE**: new API, yet to be vetted.\n\nOpen and initialize a plugin.\n\n```ts\nconst rid = Deno.openPlugin(\"./path/to/some/plugin.so\");\nconst opId = Deno.core.ops()[\"some_op\"];\nconst response = Deno.core.dispatch(opId, new Uint8Array([1,2,3,4]));\nconsole.log(`Response from plugin ${response}`);\n```\n\nRequires `allow-plugin` permission.\n\nThe plugin system is not stable and will change in the future, hence the\nlack of docs. For now take a look at the example\nhttps://github.com/denoland/deno/tree/main/test_plugin" - ], - "range": { - "start": { - "line": 0, - "character": 17 - }, - "end": { - "line": 0, - "character": 27 - } - } - }), - ), - ), - ( - LspFixture::Path("shutdown_request.json"), - LspResponse::Request(3, json!(null)), - ), - ( - LspFixture::Path("exit_notification.json"), - LspResponse::None, - ), - ]); - harness.run().await; - } - - #[tokio::test] - async fn test_hover_change_mbc() { - let mut harness = LspTestHarness::new(vec![ - ( - LspFixture::Path("initialize_request.json"), - LspResponse::RequestAny, - ), - ( - LspFixture::Path("initialized_notification.json"), - LspResponse::None, - ), - ( - LspFixture::Path("did_open_notification_mbc.json"), - LspResponse::None, - ), - ( - LspFixture::Path("did_change_notification_mbc.json"), - LspResponse::None, - ), - ( - LspFixture::Path("hover_request_mbc.json"), - LspResponse::Request( - 2, - json!({ - "contents": [ - { - "language": "typescript", - "value": "const b: \"🦕😃\"", - }, - "", - ], - "range": { - "start": { - "line": 2, - "character": 15, - }, - "end": { - "line": 2, - "character": 16, - }, - } - }), - ), - ), - ( - LspFixture::Path("shutdown_request.json"), - LspResponse::Request(3, json!(null)), - ), - ( - LspFixture::Path("exit_notification.json"), - LspResponse::None, - ), - ]); - harness.run().await; - } - - #[derive(Deserialize)] - struct HoverResponse { - pub result: Option, - } - - #[tokio::test] - async fn test_hover_closed_document() { - let temp_dir = TempDir::new() - .expect("could not create temp dir") - .into_path(); - let a_path = temp_dir.join("a.ts"); - fs::write(a_path, r#"export const a = "a";"#) - .expect("could not write file"); - let b_path = temp_dir.join("b.ts"); - fs::write(&b_path, r#"export * from "./a.ts";"#) - .expect("could not write file"); - let b_specifier = - Url::from_file_path(b_path).expect("could not convert path"); - let c_path = temp_dir.join("c.ts"); - fs::write(&c_path, "import { a } from \"./b.ts\";\nconsole.log(a);\n") - .expect("could not write file"); - let c_specifier = - Url::from_file_path(c_path).expect("could not convert path"); - - let mut harness = LspTestHarness::new(vec![ - ( - LspFixture::Path("initialize_request.json"), - LspResponse::RequestAny, - ), - ( - LspFixture::Path("initialized_notification.json"), - LspResponse::None, - ), - ( - LspFixture::Value(json!({ - "jsonrpc": "2.0", - "method": "textDocument/didOpen", - "params": { - "textDocument": { - "uri": b_specifier, - "languageId": "typescript", - "version": 1, - "text": r#"export * from "./a.ts";"# - } - } - })), - LspResponse::None, - ), - ( - LspFixture::Value(json!({ - "jsonrpc": "2.0", - "method": "textDocument/didOpen", - "params": { - "textDocument": { - "uri": c_specifier, - "languageId": "typescript", - "version": 1, - "text": "import { a } from \"./b.ts\";\nconsole.log(a);\n", - } - } - })), - LspResponse::None, - ), - ( - LspFixture::Value(json!({ - "jsonrpc": "2.0", - "id": 2, - "method": "textDocument/hover", - "params": { - "textDocument": { - "uri": c_specifier, - }, - "position": { - "line": 0, - "character": 10 - } - } - })), - LspResponse::RequestAssert(|value| { - let resp: HoverResponse = serde_json::from_value(value).unwrap(); - if let Some(hover) = resp.result { - assert_eq!( - hover, - Hover { - contents: HoverContents::Array(vec![ - MarkedString::LanguageString(LanguageString { - language: "typescript".to_string(), - value: "(alias) const a: \"a\"\nimport a".to_string() - }), - MarkedString::String("".to_string()), - ]), - range: Some(Range { - start: Position { - line: 0, - character: 9, - }, - end: Position { - line: 0, - character: 10, - } - }), - } - ); - } else { - panic!("no response"); - } - }), - ), - ( - LspFixture::Value(json!({ - "jsonrpc": "2.0", - "method": "textDocument/didClose", - "params": { - "textDocument": { - "uri": b_specifier, - } - } - })), - LspResponse::None, - ), - ( - LspFixture::Value(json!({ - "jsonrpc": "2.0", - "id": 4, - "method": "textDocument/hover", - "params": { - "textDocument": { - "uri": c_specifier, - }, - "position": { - "line": 0, - "character": 10 - } - } - })), - LspResponse::RequestAssert(|value| { - let resp: HoverResponse = serde_json::from_value(value).unwrap(); - if let Some(hover) = resp.result { - assert_eq!( - hover, - Hover { - contents: HoverContents::Array(vec![ - MarkedString::LanguageString(LanguageString { - language: "typescript".to_string(), - value: "(alias) const a: \"a\"\nimport a".to_string() - }), - MarkedString::String("".to_string()), - ]), - range: Some(Range { - start: Position { - line: 0, - character: 9, - }, - end: Position { - line: 0, - character: 10, - } - }), - } - ); - } else { - panic!("no response"); - } - }), - ), - ( - LspFixture::Path("shutdown_request.json"), - LspResponse::Request(3, json!(null)), - ), - ( - LspFixture::Path("exit_notification.json"), - LspResponse::None, - ), - ]); - harness.run().await; - } - - #[tokio::test] - async fn test_call_hierarchy() { - let mut harness = LspTestHarness::new(vec![ - ( - LspFixture::Path("initialize_request.json"), - LspResponse::RequestAny, - ), - ( - LspFixture::Path("initialized_notification.json"), - LspResponse::None, - ), - ( - LspFixture::Path("prepare_call_hierarchy_did_open_notification.json"), - LspResponse::None, - ), - ( - LspFixture::Path("prepare_call_hierarchy_request.json"), - LspResponse::Request( - 2, - json!([ - { - "name": "baz", - "kind": 6, - "detail": "Bar", - "uri": "file:///a/file.ts", - "range": { - "start": { - "line": 5, - "character": 2 - }, - "end": { - "line": 7, - "character": 3 - } - }, - "selectionRange": { - "start": { - "line": 5, - "character": 2 - }, - "end": { - "line": 5, - "character": 5 - } - } - } - ]), - ), - ), - ( - LspFixture::Path("incoming_calls_request.json"), - LspResponse::Request( - 4, - json!([ - { - "from": { - "name": "main", - "kind": 12, - "detail": "", - "uri": "file:///a/file.ts", - "range": { - "start": { - "line": 10, - "character": 0 - }, - "end": { - "line": 13, - "character": 1 - } - }, - "selectionRange": { - "start": { - "line": 10, - "character": 9 - }, - "end": { - "line": 10, - "character": 13 - } - } - }, - "fromRanges": [ - { - "start": { - "line": 12, - "character": 6 - }, - "end": { - "line": 12, - "character": 9 - } - } - ] - } - ]), - ), - ), - ( - LspFixture::Path("outgoing_calls_request.json"), - LspResponse::Request( - 5, - json!([ - { - "to": { - "name": "foo", - "kind": 12, - "detail": "", - "uri": "file:///a/file.ts", - "range": { - "start": { - "line": 0, - "character": 0 - }, - "end": { - "line": 2, - "character": 1 - } - }, - "selectionRange": { - "start": { - "line": 0, - "character": 9 - }, - "end": { - "line": 0, - "character": 12 - } - } - }, - "fromRanges": [ - { - "start": { - "line": 6, - "character": 11 - }, - "end": { - "line": 6, - "character": 14 - } - } - ] - } - ]), - ), - ), - ( - LspFixture::Path("shutdown_request.json"), - LspResponse::Request(3, json!(null)), - ), - ( - LspFixture::Path("exit_notification.json"), - LspResponse::None, - ), - ]); - harness.run().await; - } - - #[tokio::test] - async fn test_format_mbc() { - let mut harness = LspTestHarness::new(vec![ - ( - LspFixture::Path("initialize_request.json"), - LspResponse::RequestAny, - ), - ( - LspFixture::Path("initialized_notification.json"), - LspResponse::None, - ), - ( - LspFixture::Path("did_open_notification_mbc_fmt.json"), - LspResponse::None, - ), - ( - LspFixture::Path("formatting_request_mbc_fmt.json"), - LspResponse::Request( - 2, - json!([ - { - "range": { - "start": { - "line": 0, - "character": 12 - }, - "end": { - "line": 0, - "character": 13, - } - }, - "newText": "\"" - }, - { - "range": { - "start": { - "line": 0, - "character": 21 - }, - "end": { - "line": 0, - "character": 22 - } - }, - "newText": "\";" - }, - { - "range": { - "start": { - "line": 1, - "character": 12, - }, - "end": { - "line": 1, - "character": 13, - } - }, - "newText": "\"" - }, - { - "range": { - "start": { - "line": 1, - "character": 23, - }, - "end": { - "line": 1, - "character": 25, - } - }, - "newText": "\");" - } - ]), - ), - ), - ( - LspFixture::Path("shutdown_request.json"), - LspResponse::Request(3, json!(null)), - ), - ( - LspFixture::Path("exit_notification.json"), - LspResponse::None, - ), - ]); - harness.run().await; - } - - #[tokio::test] - #[ignore] // TODO(ry) Re-enable. Flaky on ubuntu-latest-xl. - async fn test_large_doc_change() { - let mut harness = LspTestHarness::new(vec![ - ( - LspFixture::Path("initialize_request.json"), - LspResponse::RequestAny, - ), - ( - LspFixture::Path("initialized_notification.json"), - LspResponse::None, - ), - ( - LspFixture::Path("did_open_notification_large.json"), - LspResponse::None, - ), - ( - LspFixture::Path("did_change_notification_large.json"), - LspResponse::None, - ), - ( - LspFixture::Path("did_change_notification_large_02.json"), - LspResponse::None, - ), - ( - LspFixture::Path("did_change_notification_large_03.json"), - LspResponse::None, - ), - ( - LspFixture::Path("hover_request_large_01.json"), - LspResponse::RequestAny, - ), - ( - LspFixture::Path("hover_request_large_02.json"), - LspResponse::RequestAny, - ), - ( - LspFixture::Path("hover_request_large_03.json"), - LspResponse::RequestAny, - ), - ( - LspFixture::Path("shutdown_request.json"), - LspResponse::Request(3, json!(null)), - ), - ( - LspFixture::Path("exit_notification.json"), - LspResponse::None, - ), - ]); - let time = Instant::now(); - harness.run().await; - assert!( - time.elapsed().as_millis() <= 10000, - "the execution time exceeded 10000ms" - ); - } - - #[tokio::test] - async fn test_document_symbol() { - let mut harness = LspTestHarness::new(vec![ - ( - LspFixture::Path("initialize_request.json"), - LspResponse::RequestAny, - ), - ( - LspFixture::Path("initialized_notification.json"), - LspResponse::None, - ), - ( - LspFixture::Path("document_symbol_did_open_notification.json"), - LspResponse::None, - ), - ( - LspFixture::Path("document_symbol_request.json"), - LspResponse::Request( - 2, - json!([ - { - "name": "bar", - "kind": 13, - "range": { - "start": { - "line": 17, - "character": 4 - }, - "end": { - "line": 17, - "character": 26 - } - }, - "selectionRange": { - "start": { - "line": 17, - "character": 4 - }, - "end": { - "line": 17, - "character": 7 - } - } - }, - { - "name": "Bar", - "kind": 5, - "range": { - "start": { - "line": 4, - "character": 0 - }, - "end": { - "line": 13, - "character": 1 - } - }, - "selectionRange": { - "start": { - "line": 4, - "character": 6 - }, - "end": { - "line": 4, - "character": 9 - } - }, - "children": [ - { - "name": "constructor", - "kind": 9, - "range": { - "start": { - "line": 5, - "character": 2 - }, - "end": { - "line": 5, - "character": 35 - } - }, - "selectionRange": { - "start": { - "line": 5, - "character": 2 - }, - "end": { - "line": 5, - "character": 35 - } - } - }, - { - "name": "baz", - "kind": 6, - "tags": [ - 1 - ], - "range": { - "start": { - "line": 8, - "character": 2 - }, - "end": { - "line": 8, - "character": 25 - } - }, - "selectionRange": { - "start": { - "line": 8, - "character": 2 - }, - "end": { - "line": 8, - "character": 5 - } - } - }, - { - "name": "foo", - "kind": 6, - "range": { - "start": { - "line": 6, - "character": 2 - }, - "end": { - "line": 6, - "character": 24 - } - }, - "selectionRange": { - "start": { - "line": 6, - "character": 2 - }, - "end": { - "line": 6, - "character": 5 - } - } - }, - { - "name": "getStaticBar", - "kind": 6, - "range": { - "start": { - "line": 12, - "character": 2 - }, - "end": { - "line": 12, - "character": 57 - } - }, - "selectionRange": { - "start": { - "line": 12, - "character": 17 - }, - "end": { - "line": 12, - "character": 29 - } - } - }, - { - "name": "staticBar", - "kind": 7, - "range": { - "start": { - "line": 11, - "character": 2 - }, - "end": { - "line": 11, - "character": 32 - } - }, - "selectionRange": { - "start": { - "line": 11, - "character": 9 - }, - "end": { - "line": 11, - "character": 18 - } - } - }, - { - "name": "value", - "kind": 7, - "range": { - "start": { - "line": 9, - "character": 2 - }, - "end": { - "line": 9, - "character": 35 - } - }, - "selectionRange": { - "start": { - "line": 9, - "character": 6 - }, - "end": { - "line": 9, - "character": 11 - } - } - }, - { - "name": "value", - "kind": 7, - "range": { - "start": { - "line": 10, - "character": 2 - }, - "end": { - "line": 10, - "character": 42 - } - }, - "selectionRange": { - "start": { - "line": 10, - "character": 6 - }, - "end": { - "line": 10, - "character": 11 - } - } - }, - { - "name": "x", - "kind": 7, - "range": { - "start": { - "line": 5, - "character": 14 - }, - "end": { - "line": 5, - "character": 30 - } - }, - "selectionRange": { - "start": { - "line": 5, - "character": 21 - }, - "end": { - "line": 5, - "character": 22 - } - } - } - ] - }, - { - "name": "IFoo", - "kind": 11, - "range": { - "start": { - "line": 0, - "character": 0 - }, - "end": { - "line": 2, - "character": 1 - } - }, - "selectionRange": { - "start": { - "line": 0, - "character": 10 - }, - "end": { - "line": 0, - "character": 14 - } - }, - "children": [ - { - "name": "foo", - "kind": 6, - "range": { - "start": { - "line": 1, - "character": 2 - }, - "end": { - "line": 1, - "character": 17 - } - }, - "selectionRange": { - "start": { - "line": 1, - "character": 2 - }, - "end": { - "line": 1, - "character": 5 - } - } - } - ] - }, - { - "name": "Values", - "kind": 10, - "range": { - "start": { - "line": 15, - "character": 0 - }, - "end": { - "line": 15, - "character": 30 - } - }, - "selectionRange": { - "start": { - "line": 15, - "character": 5 - }, - "end": { - "line": 15, - "character": 11 - } - }, - "children": [ - { - "name": "value1", - "kind": 13, - "range": { - "start": { - "line": 15, - "character": 14 - }, - "end": { - "line": 15, - "character": 20 - } - }, - "selectionRange": { - "start": { - "line": 15, - "character": 14 - }, - "end": { - "line": 15, - "character": 20 - } - } - }, - { - "name": "value2", - "kind": 13, - "range": { - "start": { - "line": 15, - "character": 22 - }, - "end": { - "line": 15, - "character": 28 - } - }, - "selectionRange": { - "start": { - "line": 15, - "character": 22 - }, - "end": { - "line": 15, - "character": 28 - } - } - } - ] - } - ]), - ), - ), - ( - LspFixture::Path("shutdown_request.json"), - LspResponse::Request(3, json!(null)), - ), - ( - LspFixture::Path("exit_notification.json"), - LspResponse::None, - ), - ]); - harness.run().await; - } - - #[tokio::test] - async fn test_folding_range() { - let mut harness = LspTestHarness::new(vec![ - ( - LspFixture::Path("initialize_request.json"), - LspResponse::RequestAny, - ), - ( - LspFixture::Path("initialized_notification.json"), - LspResponse::None, - ), - ( - LspFixture::Path("folding_range_did_open_notification.json"), - LspResponse::None, - ), - ( - LspFixture::Path("folding_range_request.json"), - LspResponse::Request( - 2, - json!([ - { - "startLine": 0, - "endLine": 12, - "kind": "region" - }, - { - "startLine": 1, - "endLine": 3, - "kind": "comment" - }, - { - "startLine": 4, - "endLine": 10 - }, - { - "startLine": 5, - "endLine": 9 - }, - { - "startLine": 6, - "endLine": 7 - } - ]), - ), - ), - ( - LspFixture::Path("shutdown_request.json"), - LspResponse::Request(3, json!(null)), - ), - ( - LspFixture::Path("exit_notification.json"), - LspResponse::None, - ), - ]); - harness.run().await; - } - - #[tokio::test] - async fn test_rename() { - let mut harness = LspTestHarness::new(vec![ - ( - LspFixture::Path("initialize_request.json"), - LspResponse::RequestAny, - ), - ( - LspFixture::Path("initialized_notification.json"), - LspResponse::None, - ), - ( - LspFixture::Path("rename_did_open_notification.json"), - LspResponse::None, - ), - ( - LspFixture::Path("rename_request.json"), - LspResponse::Request( - 2, - json!({ - "documentChanges": [{ - "textDocument": { - "uri": "file:///a/file.ts", - "version": 1, - }, - "edits": [{ - "range": { - "start": { - "line": 0, - "character": 4 - }, - "end": { - "line": 0, - "character": 12 - } - }, - "newText": "variable_modified" - }, { - "range": { - "start": { - "line": 1, - "character": 12 - }, - "end": { - "line": 1, - "character": 20 - } - }, - "newText": "variable_modified" - }] - }] - }), - ), - ), - ( - LspFixture::Path("shutdown_request.json"), - LspResponse::Request(3, json!(null)), - ), - ( - LspFixture::Path("exit_notification.json"), - LspResponse::None, - ), - ]); - harness.run().await; - } - - #[tokio::test] - async fn test_selection_range() { - let mut harness = LspTestHarness::new(vec![ - ( - LspFixture::Path("initialize_request.json"), - LspResponse::RequestAny, - ), - ( - LspFixture::Path("initialized_notification.json"), - LspResponse::None, - ), - ( - LspFixture::Path("selection_range_did_open_notification.json"), - LspResponse::None, - ), - ( - LspFixture::Path("selection_range_request.json"), - LspResponse::Request( - 2, - json!([{ - "range": { - "start": { - "line": 2, - "character": 8 - }, - "end": { - "line": 2, - "character": 9 - } - }, - "parent": { - "range": { - "start": { - "line": 2, - "character": 8 - }, - "end": { - "line": 2, - "character": 15 - } - }, - "parent": { - "range": { - "start": { - "line": 2, - "character": 4 - }, - "end": { - "line": 4, - "character": 5 - } - }, - "parent": { - "range": { - "start": { - "line": 1, - "character": 13 - }, - "end": { - "line": 6, - "character": 2 - } - }, - "parent": { - "range": { - "start": { - "line": 1, - "character": 2 - }, - "end": { - "line": 6, - "character": 3 - } - }, - "parent": { - "range": { - "start": { - "line": 0, - "character": 11 - }, - "end": { - "line": 7, - "character": 0 - } - }, - "parent": { - "range": { - "start": { - "line": 0, - "character": 0 - }, - "end": { - "line": 7, - "character": 1 - } - } - } - } - } - } - } - } - }]), - ), - ), - ( - LspFixture::Path("shutdown_request.json"), - LspResponse::Request(3, json!(null)), - ), - ( - LspFixture::Path("exit_notification.json"), - LspResponse::None, - ), - ]); - harness.run().await; - } - - #[tokio::test] - #[rustfmt::skip] - async fn test_semantic_tokens() { - let mut harness = LspTestHarness::new(vec![ - (LspFixture::Path("initialize_request.json"), LspResponse::RequestAny), - (LspFixture::Path("initialized_notification.json"), LspResponse::None), - ( - LspFixture::Path("semantic_tokens_did_open_notification.json"), - LspResponse::None, - ), - ( - LspFixture::Path("semantic_tokens_full_request.json"), - LspResponse::Request( - 2, - json!({ - "data": [0, 5, 6, 1, 1, 0, 9, 6, 8, 9, 0, 8, 6, 8, 9, 2, 15 ,3, 10 ,5, 0, 4, 1, 6, 1, 0, 12 ,7, 2, 16 ,1, 8, 1, 7, 41 ,0, 4, 1, 6, 0, 0, 2, 5, 11 ,16 ,1, 9, 1, 7, 40 ,3, 10 ,4, 2, 1, 1, 11 ,1, 9, 9, 1, 2, 3, 11 ,1, 3, 6, 3, 0, 1, 0, 15 ,4, 2, 0, 1, 30 ,1, 6, 9, 1, 2, 3, 11 ,1, 1, 9, 9, 9, 3, 0, 16 ,3, 0, 0, 1, 17 ,12 ,11 ,3, 0, 24 ,3, 0, 0, 0, 4, 9, 9, 2] - }), - ), - ), - ( - LspFixture::Path("semantic_tokens_range_request.json"), - LspResponse::Request( - 4, - json!({ - "data": [0, 5, 6, 1, 1, 0, 9, 6, 8, 9, 0, 8, 6, 8, 9, 2, 15 ,3, 10 ,5, 0, 4, 1, 6, 1, 0, 12 ,7, 2, 16 ,1, 8, 1, 7, 41 ,0, 4, 1, 6, 0, 0, 2, 5, 11 ,16 ,1, 9, 1, 7, 40] - }), - ), - ), - ( - LspFixture::Path("shutdown_request.json"), - LspResponse::Request(3, json!(null)), - ), - (LspFixture::Path("exit_notification.json"), LspResponse::None), - ]); - harness.run().await; - } - - #[tokio::test] - async fn test_code_lens_request() { - let mut harness = LspTestHarness::new(vec![ - ( - LspFixture::Path("initialize_request.json"), - LspResponse::RequestAny, - ), - ( - LspFixture::Path("initialized_notification.json"), - LspResponse::None, - ), - ( - LspFixture::Path("did_open_notification_cl_references.json"), - LspResponse::None, - ), - ( - LspFixture::Path("code_lens_request.json"), - LspResponse::Request( - 2, - json!([ - { - "range": { - "start": { - "line": 0, - "character": 6, - }, - "end": { - "line": 0, - "character": 7, - } - }, - "data": { - "specifier": "file:///a/file.ts", - "source": "references", - }, - }, - { - "range": { - "start": { - "line": 1, - "character": 2, - }, - "end": { - "line": 1, - "character": 3, - } - }, - "data": { - "specifier": "file:///a/file.ts", - "source": "references", - } - } - ]), - ), - ), - ( - LspFixture::Path("code_lens_resolve_request.json"), - LspResponse::Request( - 4, - json!({ - "range": { - "start": { - "line": 0, - "character": 6, - }, - "end": { - "line": 0, - "character": 7, - } - }, - "command": { - "title": "1 reference", - "command": "deno.showReferences", - "arguments": [ - "file:///a/file.ts", - { - "line": 0, - "character": 6, - }, - [ - { - "uri": "file:///a/file.ts", - "range": { - "start": { - "line": 12, - "character": 14, - }, - "end": { - "line": 12, - "character": 15, - } - } - } - ], - ] - } - }), - ), - ), - ( - LspFixture::Path("shutdown_request.json"), - LspResponse::Request(3, json!(null)), - ), - ( - LspFixture::Path("exit_notification.json"), - LspResponse::None, - ), - ]); - harness.run().await; - } - - #[tokio::test] - async fn test_signature_help() { - let mut harness = LspTestHarness::new(vec![ - ( - LspFixture::Path("initialize_request.json"), - LspResponse::RequestAny, - ), - ( - LspFixture::Path("initialized_notification.json"), - LspResponse::None, - ), - ( - LspFixture::Path("signature_help_did_open_notification.json"), - LspResponse::None, - ), - ( - LspFixture::Path("signature_help_request_01.json"), - LspResponse::Request( - 1, - json!({ - "signatures": [ - { - "label": "add(a: number, b: number): number", - "documentation": "Adds two numbers.", - "parameters": [ - { - "label": "a: number", - "documentation": "This is a first number." - }, - { - "label": "b: number", - "documentation": "This is a second number." - } - ] - } - ], - "activeSignature": 0, - "activeParameter": 0 - }), - ), - ), - ( - LspFixture::Path("signature_help_did_change_notification.json"), - LspResponse::None, - ), - ( - LspFixture::Path("signature_help_request_02.json"), - LspResponse::Request( - 2, - json!({ - "signatures": [ - { - "label": "add(a: number, b: number): number", - "documentation": "Adds two numbers.", - "parameters": [ - { - "label": "a: number", - "documentation": "This is a first number." - }, - { - "label": "b: number", - "documentation": "This is a second number." - } - ] - } - ], - "activeSignature": 0, - "activeParameter": 1 - }), - ), - ), - ( - LspFixture::Path("shutdown_request.json"), - LspResponse::Request(3, json!(null)), - ), - ( - LspFixture::Path("exit_notification.json"), - LspResponse::None, - ), - ]); - harness.run().await; - } - - #[tokio::test] - async fn test_code_lens_impl_request() { - let mut harness = LspTestHarness::new(vec![ - ( - LspFixture::Path("initialize_request.json"), - LspResponse::RequestAny, - ), - ( - LspFixture::Path("initialized_notification.json"), - LspResponse::None, - ), - ( - LspFixture::Path("did_open_notification_cl_impl.json"), - LspResponse::None, - ), - ( - LspFixture::Path("code_lens_request.json"), - LspResponse::Request( - 2, - json!([ - { - "range": { - "start": { - "line": 0, - "character": 10, - }, - "end": { - "line": 0, - "character": 11, - } - }, - "data": { - "specifier": "file:///a/file.ts", - "source": "implementations", - }, - }, - { - "range": { - "start": { - "line": 0, - "character": 10, - }, - "end": { - "line": 0, - "character": 11, - } - }, - "data": { - "specifier": "file:///a/file.ts", - "source": "references", - }, - }, - { - "range": { - "start": { - "line": 4, - "character": 6, - }, - "end": { - "line": 4, - "character": 7, - } - }, - "data": { - "specifier": "file:///a/file.ts", - "source": "references", - }, - }, - ]), - ), - ), - ( - LspFixture::Path("code_lens_resolve_request_impl.json"), - LspResponse::Request( - 4, - json!({ - "range": { - "start": { - "line": 0, - "character": 10, - }, - "end": { - "line": 0, - "character": 11, - } - }, - "command": { - "title": "1 implementation", - "command": "deno.showReferences", - "arguments": [ - "file:///a/file.ts", - { - "line": 0, - "character": 10, - }, - [ - { - "uri": "file:///a/file.ts", - "range": { - "start": { - "line": 4, - "character": 6, - }, - "end": { - "line": 4, - "character": 7, - } - } - } - ], - ] - } - }), - ), - ), - ( - LspFixture::Path("shutdown_request.json"), - LspResponse::Request(3, json!(null)), - ), - ( - LspFixture::Path("exit_notification.json"), - LspResponse::None, - ), - ]); - harness.run().await; - } - - #[derive(Deserialize)] - struct CodeLensResponse { - pub result: Option>, - } - - #[derive(Deserialize)] - struct CodeLensResolveResponse { - pub result: CodeLens, - } - - #[tokio::test] - async fn test_code_lens_non_doc_nav_tree() { - let mut harness = LspTestHarness::new(vec![ - ( - LspFixture::Path("initialize_request.json"), - LspResponse::RequestAny, - ), - ( - LspFixture::Path("initialized_notification.json"), - LspResponse::None, - ), - ( - LspFixture::Path("did_open_notification_asset.json"), - LspResponse::None, - ), - ( - LspFixture::Path("references_request_asset.json"), - LspResponse::RequestAny, - ), - ( - LspFixture::Path("virtual_text_document_request.json"), - LspResponse::RequestAny, - ), - ( - LspFixture::Path("code_lens_request_asset.json"), - LspResponse::RequestAssert(|value| { - let resp: CodeLensResponse = serde_json::from_value(value).unwrap(); - let lenses = resp.result.unwrap(); - assert!(lenses.len() > 50); - }), - ), - ( - LspFixture::Path("code_lens_resolve_request_asset.json"), - LspResponse::RequestAssert(|value| { - let resp: CodeLensResolveResponse = - serde_json::from_value(value).unwrap(); - assert!(resp.result.command.is_some()); - }), - ), - ( - LspFixture::Path("shutdown_request.json"), - LspResponse::Request(3, json!(null)), - ), - ( - LspFixture::Path("exit_notification.json"), - LspResponse::None, - ), - ]); - harness.run().await; - } - - #[tokio::test] - async fn test_code_actions() { - let mut harness = LspTestHarness::new(vec![ - ( - LspFixture::Path("initialize_request.json"), - LspResponse::RequestAny, - ), - ( - LspFixture::Path("initialized_notification.json"), - LspResponse::None, - ), - ( - LspFixture::Path("did_open_notification_code_action.json"), - LspResponse::None, - ), - (LspFixture::None, LspResponse::Delay(20000)), - ( - LspFixture::Path("code_action_request.json"), - LspResponse::RequestFixture(2, "code_action_response.json".to_string()), - ), - ( - LspFixture::Path("code_action_resolve_request.json"), - LspResponse::RequestFixture( - 4, - "code_action_resolve_request_response.json".to_string(), - ), - ), - ( - LspFixture::Path("shutdown_request.json"), - LspResponse::Request(3, json!(null)), - ), - ( - LspFixture::Path("exit_notification.json"), - LspResponse::None, - ), - ]); - harness.run().await; - } - - #[tokio::test] - async fn test_code_actions_deno_cache() { - let mut harness = LspTestHarness::new(vec![ - ( - LspFixture::Path("initialize_request.json"), - LspResponse::RequestAny, - ), - ( - LspFixture::Path("initialized_notification.json"), - LspResponse::None, - ), - ( - LspFixture::Path("did_open_notification_cache.json"), - LspResponse::None, - ), - ( - LspFixture::Path("code_action_request_cache.json"), - LspResponse::RequestFixture( - 2, - "code_action_response_cache.json".to_string(), - ), - ), - ( - LspFixture::Path("shutdown_request.json"), - LspResponse::Request(3, json!(null)), - ), - ( - LspFixture::Path("exit_notification.json"), - LspResponse::None, - ), - ]); - harness.run().await; - } - - #[derive(Deserialize)] - struct CompletionResult { - pub result: Option, - } - - #[tokio::test] - async fn test_completions() { - let mut harness = LspTestHarness::new(vec![ - ( - LspFixture::Path("initialize_request.json"), - LspResponse::RequestAny, - ), - ( - LspFixture::Path("initialized_notification.json"), - LspResponse::None, - ), - ( - LspFixture::Path("did_open_notification_completions.json"), - LspResponse::None, - ), - ( - LspFixture::Path("completion_request.json"), - LspResponse::RequestAssert(|value| { - let response: CompletionResult = - serde_json::from_value(value).unwrap(); - let result = response.result.unwrap(); - match result { - CompletionResponse::List(list) => { - // there should be at least 90 completions for `Deno.` - assert!(list.items.len() > 90); - } - _ => panic!("unexpected result"), - } - }), - ), - ( - LspFixture::Path("completion_resolve_request.json"), - LspResponse::Request( - 4, - json!({ - "label": "build", - "kind": 6, - "detail": "const Deno.build: {\n target: string;\n arch: \"x86_64\" | \"aarch64\";\n os: \"darwin\" | \"linux\" | \"windows\";\n vendor: string;\n env?: string | undefined;\n}", - "documentation": { - "kind": "markdown", - "value": "Build related information." - }, - "sortText": "1", - "insertTextFormat": 1, - }), - ), - ), - ( - LspFixture::Path("shutdown_request.json"), - LspResponse::Request(3, json!(null)), - ), - ( - LspFixture::Path("exit_notification.json"), - LspResponse::None, - ), - ]); - harness.run().await; - } - - #[tokio::test] - async fn test_completions_optional() { - let mut harness = LspTestHarness::new(vec![ - ( - LspFixture::Path("initialize_request.json"), - LspResponse::RequestAny, - ), - ( - LspFixture::Path("initialized_notification.json"), - LspResponse::None, - ), - ( - LspFixture::Path("did_open_notification_completion_optional.json"), - LspResponse::None, - ), - ( - LspFixture::Path("completion_request_optional.json"), - LspResponse::Request( - 2, - json!({ - "isIncomplete": false, - "items": [ - { - "label": "b?", - "kind": 5, - "sortText": "1", - "filterText": "b", - "insertText": "b", - "data": { - "tsc": { - "specifier": "file:///a/file.ts", - "position": 79, - "name": "b", - "useCodeSnippet": false - } - } - } - ] - }), - ), - ), - ( - LspFixture::Path("completion_resolve_request_optional.json"), - LspResponse::Request( - 4, - json!({ - "label": "b?", - "kind": 5, - "detail": "(property) A.b?: string | undefined", - "documentation": { - "kind": "markdown", - "value": "" - }, - "sortText": "1", - "filterText": "b", - "insertText": "b" - }), - ), - ), - ( - LspFixture::Path("shutdown_request.json"), - LspResponse::Request(3, json!(null)), - ), - ( - LspFixture::Path("exit_notification.json"), - LspResponse::None, - ), - ]); - harness.run().await; - } - - #[tokio::test] - async fn test_completions_registry() { - let _g = test_util::http_server(); - let mut harness = LspTestHarness::new(vec![ - ( - LspFixture::Path("initialize_request_registry.json"), - LspResponse::RequestAny, - ), - ( - LspFixture::Path("initialized_notification.json"), - LspResponse::None, - ), - ( - LspFixture::Path("did_open_notification_completion_registry.json"), - LspResponse::None, - ), - ( - LspFixture::Path("completion_request_registry.json"), - LspResponse::RequestAssert(|value| { - let response: CompletionResult = - serde_json::from_value(value).unwrap(); - let result = response.result.unwrap(); - if let CompletionResponse::List(list) = result { - assert_eq!(list.items.len(), 3); - } else { - panic!("unexpected result"); - } - }), - ), - ( - LspFixture::Path("completion_resolve_request_registry.json"), - LspResponse::Request( - 4, - json!({ - "label": "v2.0.0", - "kind": 19, - "detail": "(version)", - "sortText": "0000000003", - "filterText": "http://localhost:4545/x/a@v2.0.0", - "textEdit": { - "range": { - "start": { - "line": 0, - "character": 20 - }, - "end": { - "line": 0, - "character": 46 - } - }, - "newText": "http://localhost:4545/x/a@v2.0.0" - } - }), - ), - ), - ( - LspFixture::Path("shutdown_request.json"), - LspResponse::Request(3, json!(null)), - ), - ( - LspFixture::Path("exit_notification.json"), - LspResponse::None, - ), - ]); - harness.run().await; - } - - #[tokio::test] - async fn test_completion_registry_empty_specifier() { - let _g = test_util::http_server(); - let mut harness = LspTestHarness::new(vec![ - ( - LspFixture::Path("initialize_request_registry.json"), - LspResponse::RequestAny, - ), - ( - LspFixture::Path("initialized_notification.json"), - LspResponse::None, - ), - ( - LspFixture::Path("did_open_notification_completion_registry_02.json"), - LspResponse::None, - ), - ( - LspFixture::Path("completion_request_registry_02.json"), - LspResponse::Request( - 2, - json!({ - "isIncomplete": false, - "items": [ - { - "label": ".", - "kind": 19, - "detail": "(local)", - "sortText": "1", - "insertText": "." - }, - { - "label": "..", - "kind": 19, - "detail": "(local)", - "sortText": "1", - "insertText": ".." - }, - { - "label": "http://localhost:4545", - "kind": 19, - "detail": "(registry)", - "sortText": "2", - "textEdit": { - "range": { - "start": { - "line": 0, - "character": 20 - }, - "end": { - "line": 0, - "character": 20 - } - }, - "newText": "http://localhost:4545" - } - } - ] - }), - ), - ), - ( - LspFixture::Path("shutdown_request.json"), - LspResponse::Request(3, json!(null)), - ), - ( - LspFixture::Path("exit_notification.json"), - LspResponse::None, - ), - ]); - harness.run().await; - } - - #[derive(Deserialize)] - struct PerformanceAverages { - averages: Vec, - } - #[derive(Deserialize)] - struct PerformanceResponse { - result: PerformanceAverages, - } - - #[tokio::test] - async fn test_deno_performance_request() { - let mut harness = LspTestHarness::new(vec![ - ( - LspFixture::Path("initialize_request.json"), - LspResponse::RequestAny, - ), - ( - LspFixture::Path("initialized_notification.json"), - LspResponse::None, - ), - ( - LspFixture::Path("did_open_notification.json"), - LspResponse::None, - ), - ( - LspFixture::Path("hover_request.json"), - LspResponse::Request( - 2, - json!({ - "contents": [ - { - "language": "typescript", - "value": "const Deno.args: string[]" - }, - "Returns the script arguments to the program. If for example we run a\nprogram:\n\ndeno run --allow-read https://deno.land/std/examples/cat.ts /etc/passwd\n\nThen `Deno.args` will contain:\n\n[ \"/etc/passwd\" ]" - ], - "range": { - "start": { - "line": 0, - "character": 17 - }, - "end": { - "line": 0, - "character": 21 - } - } - }), - ), - ), - ( - LspFixture::Path("performance_request.json"), - LspResponse::RequestAssert(|value| { - let resp: PerformanceResponse = - serde_json::from_value(value).unwrap(); - // the len can be variable since some of the parts of the language - // server run in separate threads and may not add to performance by - // the time the results are checked. - assert!(resp.result.averages.len() >= 6); - }), - ), - ( - LspFixture::Path("shutdown_request.json"), - LspResponse::Request(3, json!(null)), - ), - ( - LspFixture::Path("exit_notification.json"), - LspResponse::None, - ), - ]); - harness.run().await; - } -} diff --git a/cli/tests/integration_tests_lsp.rs b/cli/tests/integration_tests_lsp.rs new file mode 100644 index 0000000000..6b0f6b7920 --- /dev/null +++ b/cli/tests/integration_tests_lsp.rs @@ -0,0 +1,1661 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +use deno_core::serde::Deserialize; +use deno_core::serde::Serialize; +use deno_core::serde_json; +use deno_core::serde_json::json; +use deno_core::serde_json::Value; +use deno_core::url::Url; +use lspower::lsp; +use std::fs; +use tempfile::TempDir; +use test_util::deno_exe_path; +use test_util::http_server; +use test_util::lsp::LspClient; +use test_util::root_path; + +fn load_fixture(path: &str) -> Value { + let fixtures_path = root_path().join("cli/tests/lsp"); + let path = fixtures_path.join(path); + let fixture_str = fs::read_to_string(path).unwrap(); + serde_json::from_str(&fixture_str).unwrap() +} + +fn init(init_path: &str) -> LspClient { + let deno_exe = deno_exe_path(); + let mut client = LspClient::new(&deno_exe).unwrap(); + client + .write_request::<_, _, Value>("initialize", load_fixture(init_path)) + .unwrap(); + client.write_notification("initialized", json!({})).unwrap(); + client +} + +fn did_open(client: &mut LspClient, params: V) +where + V: Serialize, +{ + client + .write_notification("textDocument/didOpen", params) + .unwrap(); + let (method, _) = client.read_notification::().unwrap(); + assert_eq!(method, "textDocument/publishDiagnostics"); + let (method, _) = client.read_notification::().unwrap(); + assert_eq!(method, "textDocument/publishDiagnostics"); + let (method, _) = client.read_notification::().unwrap(); + assert_eq!(method, "textDocument/publishDiagnostics"); +} + +fn shutdown(client: &mut LspClient) { + client + .write_request::<_, _, Value>("shutdown", json!(null)) + .unwrap(); + client.write_notification("exit", json!(null)).unwrap(); +} + +#[test] +fn lsp_startup_shutdown() { + let mut client = init("initialize_params.json"); + shutdown(&mut client); +} + +#[test] +fn lsp_hover() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "console.log(Deno.args);\n" + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/hover", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "position": { + "line": 0, + "character": 19 + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "contents": [ + { + "language": "typescript", + "value": "const Deno.args: string[]" + }, + "Returns the script arguments to the program. If for example we run a\nprogram:\n\ndeno run --allow-read https://deno.land/std/examples/cat.ts /etc/passwd\n\nThen `Deno.args` will contain:\n\n[ \"/etc/passwd\" ]" + ], + "range": { + "start": { + "line": 0, + "character": 17 + }, + "end": { + "line": 0, + "character": 21 + } + } + })) + ); + shutdown(&mut client); +} + +#[test] +fn lsp_hover_asset() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "console.log(Date.now());\n" + } + }), + ); + let (_, maybe_error) = client + .write_request::<_, _, Value>( + "textDocument/definition", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "position": { + "line": 0, + "character": 14 + } + }), + ) + .unwrap(); + assert!(maybe_error.is_none()); + let (_, maybe_error) = client + .write_request::<_, _, Value>( + "deno/virtualTextDocument", + json!({ + "textDocument": { + "uri": "deno:/asset//lib.deno.shared_globals.d.ts" + } + }), + ) + .unwrap(); + assert!(maybe_error.is_none()); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/hover", + json!({ + "textDocument": { + "uri": "deno:/asset//lib.es2015.symbol.wellknown.d.ts" + }, + "position": { + "line": 109, + "character": 13 + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "contents": [ + { + "language": "typescript", + "value": "interface Date", + }, + "Enables basic storage and retrieval of dates and times." + ], + "range": { + "start": { + "line": 109, + "character": 10, + }, + "end": { + "line": 109, + "character": 14, + } + } + })) + ); + shutdown(&mut client); +} + +#[test] +fn lsp_hover_disabled() { + let mut client = init("initialize_params_disabled.json"); + client + .write_notification( + "textDocument/didOpen", + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "console.log(Date.now());\n" + } + }), + ) + .unwrap(); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/hover", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "position": { + "line": 0, + "character": 19 + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!(maybe_res, Some(json!(null))); + shutdown(&mut client); +} + +#[test] +fn lsp_hover_unstable_disabled() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "console.log(Deno.openPlugin);\n" + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/hover", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "position": { + "line": 0, + "character": 19 + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "contents": [ + { + "language": "typescript", + "value": "any" + } + ], + "range": { + "start": { + "line": 0, + "character": 17 + }, + "end": { + "line": 0, + "character": 27 + } + } + })) + ); + shutdown(&mut client); +} + +#[test] +fn lsp_hover_unstable_enabled() { + let mut client = init("initialize_params_unstable.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "console.log(Deno.openPlugin);\n" + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/hover", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "position": { + "line": 0, + "character": 19 + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "contents":[ + { + "language":"typescript", + "value":"function Deno.openPlugin(filename: string): number" + }, + "**UNSTABLE**: new API, yet to be vetted.\n\nOpen and initialize a plugin.\n\n```ts\nconst rid = Deno.openPlugin(\"./path/to/some/plugin.so\");\nconst opId = Deno.core.ops()[\"some_op\"];\nconst response = Deno.core.dispatch(opId, new Uint8Array([1,2,3,4]));\nconsole.log(`Response from plugin ${response}`);\n```\n\nRequires `allow-plugin` permission.\n\nThe plugin system is not stable and will change in the future, hence the\nlack of docs. For now take a look at the example\nhttps://github.com/denoland/deno/tree/main/test_plugin" + ], + "range":{ + "start":{ + "line":0, + "character":17 + }, + "end":{ + "line":0, + "character":27 + } + } + })) + ); + shutdown(&mut client); +} + +#[test] +fn lsp_hover_change_mbc() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "const a = `编写软件很难`;\nconst b = `👍🦕😃`;\nconsole.log(a, b);\n" + } + }), + ); + client + .write_notification( + "textDocument/didChange", + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "version": 2 + }, + "contentChanges": [ + { + "range": { + "start": { + "line": 1, + "character": 11 + }, + "end": { + "line": 1, + "character": 13 + } + }, + "text": "" + } + ] + }), + ) + .unwrap(); + let (method, _) = client.read_notification::().unwrap(); + assert_eq!(method, "textDocument/publishDiagnostics"); + let (method, _) = client.read_notification::().unwrap(); + assert_eq!(method, "textDocument/publishDiagnostics"); + let (method, _) = client.read_notification::().unwrap(); + assert_eq!(method, "textDocument/publishDiagnostics"); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/hover", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "position": { + "line": 2, + "character": 14 + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "contents": [ + { + "language": "typescript", + "value": "const b: \"😃\"", + }, + "", + ], + "range": { + "start": { + "line": 2, + "character": 13, + }, + "end": { + "line": 2, + "character": 14, + }, + } + })) + ); + shutdown(&mut client); +} + +#[test] +fn lsp_hover_closed_document() { + let temp_dir = TempDir::new() + .expect("could not create temp dir") + .into_path(); + let a_path = temp_dir.join("a.ts"); + fs::write(a_path, r#"export const a = "a";"#).expect("could not write file"); + let b_path = temp_dir.join("b.ts"); + fs::write(&b_path, r#"export * from "./a.ts";"#) + .expect("could not write file"); + let b_specifier = + Url::from_file_path(b_path).expect("could not convert path"); + let c_path = temp_dir.join("c.ts"); + fs::write(&c_path, "import { a } from \"./b.ts\";\nconsole.log(a);\n") + .expect("could not write file"); + let c_specifier = + Url::from_file_path(c_path).expect("could not convert path"); + + let mut client = init("initialize_params.json"); + client + .write_notification( + "textDocument/didOpen", + json!({ + "textDocument": { + "uri": b_specifier, + "languageId": "typescript", + "version": 1, + "text": r#"export * from "./a.ts";"# + } + }), + ) + .unwrap(); + client + .write_notification( + "textDocument/didOpen", + json!({ + "textDocument": { + "uri": c_specifier, + "languageId": "typescript", + "version": 1, + "text": "import { a } from \"./b.ts\";\nconsole.log(a);\n", + } + }), + ) + .unwrap(); + let (method, _) = client.read_notification::().unwrap(); + assert_eq!(method, "textDocument/publishDiagnostics"); + let (method, _) = client.read_notification::().unwrap(); + assert_eq!(method, "textDocument/publishDiagnostics"); + let (method, _) = client.read_notification::().unwrap(); + assert_eq!(method, "textDocument/publishDiagnostics"); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/hover", + json!({ + "textDocument": { + "uri": c_specifier, + }, + "position": { + "line": 0, + "character": 10 + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "contents": [ + { + "language": "typescript", + "value": "(alias) const a: \"a\"\nimport a" + }, + "" + ], + "range": { + "start": { + "line": 0, + "character": 9 + }, + "end": { + "line": 0, + "character": 10 + } + } + })) + ); + client + .write_notification( + "textDocument/didClose", + json!({ + "textDocument": { + "uri": b_specifier, + } + }), + ) + .unwrap(); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/hover", + json!({ + "textDocument": { + "uri": c_specifier, + }, + "position": { + "line": 0, + "character": 10 + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "contents": [ + { + "language": "typescript", + "value": "(alias) const a: \"a\"\nimport a" + }, + "" + ], + "range": { + "start": { + "line": 0, + "character": 9 + }, + "end": { + "line": 0, + "character": 10 + } + } + })) + ); + shutdown(&mut client); +} + +#[test] +fn lsp_call_hierarchy() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "function foo() {\n return false;\n}\n\nclass Bar {\n baz() {\n return foo();\n }\n}\n\nfunction main() {\n const bar = new Bar();\n bar.baz();\n}\n\nmain();" + } + }), + ); + let (maybe_res, maybe_error) = client + .write_request( + "textDocument/prepareCallHierarchy", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "position": { + "line": 5, + "character": 3 + } + }), + ) + .unwrap(); + assert!(maybe_error.is_none()); + assert_eq!( + maybe_res, + Some(load_fixture("prepare_call_hierarchy_response.json")) + ); + let (maybe_res, maybe_error) = client + .write_request( + "callHierarchy/incomingCalls", + load_fixture("incoming_calls_params.json"), + ) + .unwrap(); + assert!(maybe_error.is_none()); + assert_eq!( + maybe_res, + Some(load_fixture("incoming_calls_response.json")) + ); + let (maybe_res, maybe_error) = client + .write_request( + "callHierarchy/outgoingCalls", + load_fixture("outgoing_calls_params.json"), + ) + .unwrap(); + assert!(maybe_error.is_none()); + assert_eq!( + maybe_res, + Some(load_fixture("outgoing_calls_response.json")) + ); + shutdown(&mut client); +} + +#[test] +fn lsp_format_mbc() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "const bar = '👍🇺🇸😃'\nconsole.log('hello deno')\n" + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/formatting", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "options": { + "tabSize": 2, + "insertSpaces": true + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!(load_fixture("formatting_mbc_response.json"))) + ); + shutdown(&mut client); +} + +#[test] +fn lsp_large_doc_changes() { + let mut client = init("initialize_params.json"); + did_open(&mut client, load_fixture("did_open_params_large.json")); + client + .write_notification( + "textDocument/didChange", + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "version": 2 + }, + "contentChanges": [ + { + "range": { + "start": { + "line": 444, + "character": 11 + }, + "end": { + "line": 444, + "character": 14 + } + }, + "text": "+++" + } + ] + }), + ) + .unwrap(); + client + .write_notification( + "textDocument/didChange", + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "version": 2 + }, + "contentChanges": [ + { + "range": { + "start": { + "line": 445, + "character": 4 + }, + "end": { + "line": 445, + "character": 4 + } + }, + "text": "// " + } + ] + }), + ) + .unwrap(); + client + .write_notification( + "textDocument/didChagne", + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "version": 2 + }, + "contentChanges": [ + { + "range": { + "start": { + "line": 477, + "character": 4 + }, + "end": { + "line": 477, + "character": 9 + } + }, + "text": "error" + } + ] + }), + ) + .unwrap(); + let (maybe_res, maybe_err) = client + .write_request::<_, _, Value>( + "textDocument/hover", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "position": { + "line": 421, + "character": 30 + } + }), + ) + .unwrap(); + assert!(maybe_res.is_some()); + assert!(maybe_err.is_none()); + let (maybe_res, maybe_err) = client + .write_request::<_, _, Value>( + "textDocument/hover", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "position": { + "line": 444, + "character": 6 + } + }), + ) + .unwrap(); + assert!(maybe_res.is_some()); + assert!(maybe_err.is_none()); + let (maybe_res, maybe_err) = client + .write_request::<_, _, Value>( + "textDocument/hover", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "position": { + "line": 461, + "character": 34 + } + }), + ) + .unwrap(); + assert!(maybe_res.is_some()); + assert!(maybe_err.is_none()); + shutdown(&mut client); + + assert!(client.duration().as_millis() <= 15000); +} + +#[test] +fn lsp_document_symbol() { + let mut client = init("initialize_params.json"); + did_open(&mut client, load_fixture("did_open_params_doc_symbol.json")); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/documentSymbol", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(load_fixture("document_symbol_response.json")) + ); + shutdown(&mut client); +} + +#[test] +fn lsp_folding_range() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "// #region 1\n/*\n * Some comment\n */\nclass Foo {\n bar(a, b) {\n if (a === b) {\n return true;\n }\n return false;\n }\n}\n// #endregion" + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/foldingRange", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!([ + { + "startLine": 0, + "endLine": 12, + "kind": "region" + }, + { + "startLine": 1, + "endLine": 3, + "kind": "comment" + }, + { + "startLine": 4, + "endLine": 10 + }, + { + "startLine": 5, + "endLine": 9 + }, + { + "startLine": 6, + "endLine": 7 + } + ])) + ); + shutdown(&mut client); +} + +#[test] +fn lsp_rename() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "let variable = 'a';\nconsole.log(variable);" + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/rename", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "position": { + "line": 0, + "character": 4 + }, + "newName": "variable_modified" + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!(maybe_res, Some(load_fixture("rename_response.json"))); + shutdown(&mut client); +} + +#[test] +fn lsp_selection_range() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "class Foo {\n bar(a, b) {\n if (a === b) {\n return true;\n }\n return false;\n }\n}" + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/selectionRange", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "positions": [ + { + "line": 2, + "character": 8 + } + ] + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(load_fixture("selection_range_response.json")) + ); + shutdown(&mut client); +} + +#[test] +fn lsp_semantic_tokens() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + load_fixture("did_open_params_semantic_tokens.json"), + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/semanticTokens/full", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "data": [ + 0, 5, 6, 1, 1, 0, 9, 6, 8, 9, 0, 8, 6, 8, 9, 2, 15, 3, 10, 5, 0, 4, 1, + 6, 1, 0, 12, 7, 2, 16, 1, 8, 1, 7, 41, 0, 4, 1, 6, 0, 0, 2, 5, 11, 16, + 1, 9, 1, 7, 40, 3, 10, 4, 2, 1, 1, 11, 1, 9, 9, 1, 2, 3, 11, 1, 3, 6, 3, + 0, 1, 0, 15, 4, 2, 0, 1, 30, 1, 6, 9, 1, 2, 3, 11,1, 1, 9, 9, 9, 3, 0, + 16, 3, 0, 0, 1, 17, 12, 11, 3, 0, 24, 3, 0, 0, 0, 4, 9, 9, 2 + ] + })) + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/semanticTokens/range", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "range": { + "start": { + "line": 0, + "character": 0 + }, + "end": { + "line": 6, + "character": 0 + } + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "data": [ + 0, 5, 6, 1, 1, 0, 9, 6, 8, 9, 0, 8, 6, 8, 9, 2, 15, 3, 10, 5, 0, 4, 1, + 6, 1, 0, 12, 7, 2, 16, 1, 8, 1, 7, 41, 0, 4, 1, 6, 0, 0, 2, 5, 11, 16, + 1, 9, 1, 7, 40 + ] + })) + ); + shutdown(&mut client); +} + +#[test] +fn lsp_code_lens() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "class A {\n a = \"a\";\n\n b() {\n console.log(this.a);\n }\n\n c() {\n this.a = \"c\";\n }\n}\n\nconst a = new A();\na.b();\n" + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/codeLens", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!(maybe_res, Some(load_fixture("code_lens_response.json"))); + let (maybe_res, maybe_err) = client + .write_request( + "codeLens/resolve", + json!({ + "range": { + "start": { + "line": 0, + "character": 6 + }, + "end": { + "line": 0, + "character": 7 + } + }, + "data": { + "specifier": "file:///a/file.ts", + "source": "references" + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(load_fixture("code_lens_resolve_response.json")) + ); + shutdown(&mut client); +} + +#[test] +fn lsp_code_lens_impl() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "interface A {\n b(): void;\n}\n\nclass B implements A {\n b() {\n console.log(\"b\");\n }\n}\n" + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/codeLens", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(load_fixture("code_lens_response_impl.json")) + ); + let (maybe_res, maybe_err) = client + .write_request( + "codeLens/resolve", + json!({ + "range": { + "start": { + "line": 0, + "character": 10 + }, + "end": { + "line": 0, + "character": 11 + } + }, + "data": { + "specifier": "file:///a/file.ts", + "source": "implementations" + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(load_fixture("code_lens_resolve_response_impl.json")) + ); + shutdown(&mut client); +} + +#[test] +fn lsp_code_lens_non_doc_nav_tree() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "console.log(Date.now());\n" + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request::<_, _, Value>( + "textDocument/references", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "position": { + "line": 0, + "character": 3 + }, + "context": { + "includeDeclaration": true + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert!(maybe_res.is_some()); + let (maybe_res, maybe_err) = client + .write_request::<_, _, Value>( + "deno/virtualTextDocument", + json!({ + "textDocument": { + "uri": "deno:/asset//lib.deno.shared_globals.d.ts" + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert!(maybe_res.is_some()); + let (maybe_res, maybe_err) = client + .write_request::<_, _, Vec>( + "textDocument/codeLens", + json!({ + "textDocument": { + "uri": "deno:/asset//lib.deno.shared_globals.d.ts" + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert!(maybe_res.is_some()); + let res = maybe_res.unwrap(); + assert!(res.len() > 50); + let (maybe_res, maybe_err) = client + .write_request::<_, _, lsp::CodeLens>( + "codeLens/resolve", + json!({ + "range": { + "start": { + "line": 416, + "character": 12 + }, + "end": { + "line": 416, + "character": 19 + } + }, + "data": { + "specifier": "asset:///lib.deno.shared_globals.d.ts", + "source": "references" + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert!(maybe_res.is_some()); + shutdown(&mut client); +} + +#[test] +fn lsp_signature_help() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "/**\n * Adds two numbers.\n * @param a This is a first number.\n * @param b This is a second number.\n */\nfunction add(a: number, b: number) {\n return a + b;\n}\n\nadd(" + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/signatureHelp", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "position": { + "character": 4, + "line": 9 + }, + "context": { + "triggerKind": 2, + "triggerCharacter": "(", + "isRetrigger": false + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "signatures": [ + { + "label": "add(a: number, b: number): number", + "documentation": "Adds two numbers.", + "parameters": [ + { + "label": "a: number", + "documentation": "This is a first number." + }, + { + "label": "b: number", + "documentation": "This is a second number." + } + ] + } + ], + "activeSignature": 0, + "activeParameter": 0 + })) + ); + client + .write_notification( + "textDocument/didChange", + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "version": 2 + }, + "contentChanges": [ + { + "range": { + "start": { + "line": 9, + "character": 4 + }, + "end": { + "line": 9, + "character": 4 + } + }, + "text": "123, " + } + ] + }), + ) + .unwrap(); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/signatureHelp", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "position": { + "character": 8, + "line": 9 + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "signatures": [ + { + "label": "add(a: number, b: number): number", + "documentation": "Adds two numbers.", + "parameters": [ + { + "label": "a: number", + "documentation": "This is a first number." + }, + { + "label": "b: number", + "documentation": "This is a second number." + } + ] + } + ], + "activeSignature": 0, + "activeParameter": 1 + })) + ); + shutdown(&mut client); +} + +#[test] +fn lsp_code_actions() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "export function a(): void {\n await Promise.resolve(\"a\");\n}\n\nexport function b(): void {\n await Promise.resolve(\"b\");\n}\n" + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/codeAction", + load_fixture("code_action_params.json"), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!(maybe_res, Some(load_fixture("code_action_response.json"))); + let (maybe_res, maybe_err) = client + .write_request( + "codeAction/resolve", + load_fixture("code_action_resolve_params.json"), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(load_fixture("code_action_resolve_response.json")) + ); + shutdown(&mut client); +} + +#[test] +fn lsp_code_actions_deno_cache() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "import * as a from \"https://deno.land/x/a/mod.ts\";\n\nconsole.log(a);\n" + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/codeAction", + load_fixture("code_action_params_cache.json"), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(load_fixture("code_action_response_cache.json")) + ); + shutdown(&mut client); +} + +#[test] +fn lsp_completions() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "Deno." + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/completion", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "position": { + "line": 0, + "character": 5 + }, + "context": { + "triggerKind": 2, + "triggerCharacter": "." + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + if let Some(lsp::CompletionResponse::List(list)) = maybe_res { + assert!(!list.is_incomplete); + assert!(list.items.len() > 90); + } else { + panic!("unexpected response"); + } + let (maybe_res, maybe_err) = client + .write_request( + "completionItem/resolve", + load_fixture("completion_resolve_params.json"), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(load_fixture("completion_resolve_response.json")) + ); + shutdown(&mut client); +} + +#[test] +fn lsp_completions_optional() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "interface A {\n b?: string;\n}\n\nconst o: A = {};\n\nfunction c(s: string) {}\n\nc(o.)" + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/completion", + load_fixture("completion_request_params_optional.json"), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "isIncomplete": false, + "items": [ + { + "label": "b?", + "kind": 5, + "sortText": "1", + "filterText": "b", + "insertText": "b", + "data": { + "tsc": { + "specifier": "file:///a/file.ts", + "position": 79, + "name": "b", + "useCodeSnippet": false + } + } + } + ] + })) + ); + let (maybe_res, maybe_err) = client + .write_request( + "completionItem/resolve", + load_fixture("completion_resolve_params_optional.json"), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "label": "b?", + "kind": 5, + "detail": "(property) A.b?: string | undefined", + "documentation": { + "kind": "markdown", + "value": "" + }, + "sortText": "1", + "filterText": "b", + "insertText": "b" + })) + ); + shutdown(&mut client); +} + +#[test] +fn lsp_completions_registry() { + let _g = http_server(); + let mut client = init("initialize_params_registry.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "import * as a from \"http://localhost:4545/x/a@\"" + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/completion", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "position": { + "line": 0, + "character": 46 + }, + "context": { + "triggerKind": 2, + "triggerCharacter": "@" + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + if let Some(lsp::CompletionResponse::List(list)) = maybe_res { + assert!(!list.is_incomplete); + assert_eq!(list.items.len(), 3); + } else { + panic!("unexpected response"); + } + let (maybe_res, maybe_err) = client + .write_request( + "completionItem/resolve", + load_fixture("completion_resolve_params_registry.json"), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(load_fixture("completion_resolve_response_registry.json")) + ); + shutdown(&mut client); +} + +#[test] +fn lsp_completions_registry_empty() { + let _g = http_server(); + let mut client = init("initialize_params_registry.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "import * as a from \"\"" + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/completion", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "position": { + "line": 0, + "character": 20 + }, + "context": { + "triggerKind": 2, + "triggerCharacter": "\"" + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(load_fixture("completion_request_response_empty.json")) + ); + shutdown(&mut client); +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PerformanceAverage { + pub name: String, + pub count: u32, + pub average_duration: u32, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct PerformanceAverages { + averages: Vec, +} + +#[test] +fn lsp_performance() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "console.log(Deno.args);\n" + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request::<_, _, Value>( + "textDocument/hover", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "position": { + "line": 0, + "character": 19 + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert!(maybe_res.is_some()); + let (maybe_res, maybe_err) = client + .write_request::<_, _, PerformanceAverages>("deno/performance", json!({})) + .unwrap(); + assert!(maybe_err.is_none()); + if let Some(res) = maybe_res { + assert!(res.averages.len() >= 6); + } else { + panic!("unexpected result"); + } + shutdown(&mut client); +} diff --git a/cli/tests/lsp/code_action_resolve_request.json b/cli/tests/lsp/code_action_params.json similarity index 65% rename from cli/tests/lsp/code_action_resolve_request.json rename to cli/tests/lsp/code_action_params.json index 48a2eea3bc..d026d61f68 100644 --- a/cli/tests/lsp/code_action_resolve_request.json +++ b/cli/tests/lsp/code_action_params.json @@ -1,10 +1,18 @@ { - "jsonrpc": "2.0", - "id": 4, - "method": "codeAction/resolve", - "params": { - "title": "Add all missing 'async' modifiers", - "kind": "quickfix", + "textDocument": { + "uri": "file:///a/file.ts" + }, + "range": { + "start": { + "line": 1, + "character": 2 + }, + "end": { + "line": 1, + "character": 7 + } + }, + "context": { "diagnostics": [ { "range": { @@ -24,9 +32,8 @@ "relatedInformation": [] } ], - "data": { - "specifier": "file:///a/file.ts", - "fixId": "fixAwaitInSyncFunction" - } + "only": [ + "quickfix" + ] } } diff --git a/cli/tests/lsp/code_action_params_cache.json b/cli/tests/lsp/code_action_params_cache.json new file mode 100644 index 0000000000..61ae555a3c --- /dev/null +++ b/cli/tests/lsp/code_action_params_cache.json @@ -0,0 +1,41 @@ +{ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "range": { + "start": { + "line": 0, + "character": 19 + }, + "end": { + "line": 0, + "character": 49 + } + }, + "context": { + "diagnostics": [ + { + "range": { + "start": { + "line": 0, + "character": 19 + }, + "end": { + "line": 0, + "character": 49 + } + }, + "severity": 1, + "code": "no-cache", + "source": "deno", + "message": "Unable to load the remote module: \"https://deno.land/x/a/mod.ts\".", + "data": { + "specifier": "https://deno.land/x/a/mod.ts" + } + } + ], + "only": [ + "quickfix" + ] + } +} diff --git a/cli/tests/lsp/code_action_request.json b/cli/tests/lsp/code_action_request.json deleted file mode 100644 index af6cbee8bd..0000000000 --- a/cli/tests/lsp/code_action_request.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": 2, - "method": "textDocument/codeAction", - "params": { - "textDocument": { - "uri": "file:///a/file.ts" - }, - "range": { - "start": { - "line": 1, - "character": 2 - }, - "end": { - "line": 1, - "character": 7 - } - }, - "context": { - "diagnostics": [ - { - "range": { - "start": { - "line": 1, - "character": 2 - }, - "end": { - "line": 1, - "character": 7 - } - }, - "severity": 1, - "code": 1308, - "source": "deno-ts", - "message": "'await' expressions are only allowed within async functions and at the top levels of modules.", - "relatedInformation": [] - } - ], - "only": [ - "quickfix" - ] - } - } -} diff --git a/cli/tests/lsp/code_action_request_cache.json b/cli/tests/lsp/code_action_request_cache.json deleted file mode 100644 index 8e296be327..0000000000 --- a/cli/tests/lsp/code_action_request_cache.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": 2, - "method": "textDocument/codeAction", - "params": { - "textDocument": { - "uri": "file:///a/file.ts" - }, - "range": { - "start": { - "line": 0, - "character": 19 - }, - "end": { - "line": 0, - "character": 49 - } - }, - "context": { - "diagnostics": [ - { - "range": { - "start": { - "line": 0, - "character": 19 - }, - "end": { - "line": 0, - "character": 49 - } - }, - "severity": 1, - "code": "no-cache", - "source": "deno", - "message": "Unable to load the remote module: \"https://deno.land/x/a/mod.ts\".", - "data": { - "specifier": "https://deno.land/x/a/mod.ts" - } - } - ], - "only": [ - "quickfix" - ] - } - } -} diff --git a/cli/tests/lsp/code_action_resolve_params.json b/cli/tests/lsp/code_action_resolve_params.json new file mode 100644 index 0000000000..50c1f9a43b --- /dev/null +++ b/cli/tests/lsp/code_action_resolve_params.json @@ -0,0 +1,27 @@ +{ + "title": "Add all missing 'async' modifiers", + "kind": "quickfix", + "diagnostics": [ + { + "range": { + "start": { + "line": 1, + "character": 2 + }, + "end": { + "line": 1, + "character": 7 + } + }, + "severity": 1, + "code": 1308, + "source": "deno-ts", + "message": "'await' expressions are only allowed within async functions and at the top levels of modules.", + "relatedInformation": [] + } + ], + "data": { + "specifier": "file:///a/file.ts", + "fixId": "fixAwaitInSyncFunction" + } +} diff --git a/cli/tests/lsp/code_action_resolve_request_response.json b/cli/tests/lsp/code_action_resolve_response.json similarity index 100% rename from cli/tests/lsp/code_action_resolve_request_response.json rename to cli/tests/lsp/code_action_resolve_response.json diff --git a/cli/tests/lsp/code_lens_request.json b/cli/tests/lsp/code_lens_request.json deleted file mode 100644 index a876153bf7..0000000000 --- a/cli/tests/lsp/code_lens_request.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": 2, - "method": "textDocument/codeLens", - "params": { - "textDocument": { - "uri": "file:///a/file.ts" - } - } -} diff --git a/cli/tests/lsp/code_lens_request_asset.json b/cli/tests/lsp/code_lens_request_asset.json deleted file mode 100644 index 6aa246ce5c..0000000000 --- a/cli/tests/lsp/code_lens_request_asset.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": 4, - "method": "textDocument/codeLens", - "params": { - "textDocument": { - "uri": "deno:/asset//lib.deno.shared_globals.d.ts" - } - } -} diff --git a/cli/tests/lsp/code_lens_resolve_request.json b/cli/tests/lsp/code_lens_resolve_request.json deleted file mode 100644 index 150603cd4e..0000000000 --- a/cli/tests/lsp/code_lens_resolve_request.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": 4, - "method": "codeLens/resolve", - "params": { - "range": { - "start": { - "line": 0, - "character": 6 - }, - "end": { - "line": 0, - "character": 7 - } - }, - "data": { - "specifier": "file:///a/file.ts", - "source": "references" - } - } -} diff --git a/cli/tests/lsp/code_lens_resolve_request_asset.json b/cli/tests/lsp/code_lens_resolve_request_asset.json deleted file mode 100644 index 027af96b61..0000000000 --- a/cli/tests/lsp/code_lens_resolve_request_asset.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": 5, - "method": "codeLens/resolve", - "params": { - "range": { - "start": { - "line": 416, - "character": 12 - }, - "end": { - "line": 416, - "character": 19 - } - }, - "data": { - "specifier": "asset:///lib.deno.shared_globals.d.ts", - "source": "references" - } - } -} diff --git a/cli/tests/lsp/code_lens_resolve_request_impl.json b/cli/tests/lsp/code_lens_resolve_request_impl.json deleted file mode 100644 index e44d19675e..0000000000 --- a/cli/tests/lsp/code_lens_resolve_request_impl.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": 4, - "method": "codeLens/resolve", - "params": { - "range": { - "start": { - "line": 0, - "character": 10 - }, - "end": { - "line": 0, - "character": 11 - } - }, - "data": { - "specifier": "file:///a/file.ts", - "source": "implementations" - } - } -} diff --git a/cli/tests/lsp/code_lens_resolve_response.json b/cli/tests/lsp/code_lens_resolve_response.json new file mode 100644 index 0000000000..1400eb4e6f --- /dev/null +++ b/cli/tests/lsp/code_lens_resolve_response.json @@ -0,0 +1,38 @@ +{ + "range": { + "start": { + "line": 0, + "character": 6 + }, + "end": { + "line": 0, + "character": 7 + } + }, + "command": { + "title": "1 reference", + "command": "deno.showReferences", + "arguments": [ + "file:///a/file.ts", + { + "line": 0, + "character": 6 + }, + [ + { + "uri": "file:///a/file.ts", + "range": { + "start": { + "line": 12, + "character": 14 + }, + "end": { + "line": 12, + "character": 15 + } + } + } + ] + ] + } +} diff --git a/cli/tests/lsp/code_lens_resolve_response_impl.json b/cli/tests/lsp/code_lens_resolve_response_impl.json new file mode 100644 index 0000000000..cabf2f8334 --- /dev/null +++ b/cli/tests/lsp/code_lens_resolve_response_impl.json @@ -0,0 +1,38 @@ +{ + "range": { + "start": { + "line": 0, + "character": 10 + }, + "end": { + "line": 0, + "character": 11 + } + }, + "command": { + "title": "1 implementation", + "command": "deno.showReferences", + "arguments": [ + "file:///a/file.ts", + { + "line": 0, + "character": 10 + }, + [ + { + "uri": "file:///a/file.ts", + "range": { + "start": { + "line": 4, + "character": 6 + }, + "end": { + "line": 4, + "character": 7 + } + } + } + ] + ] + } +} diff --git a/cli/tests/lsp/code_lens_response.json b/cli/tests/lsp/code_lens_response.json new file mode 100644 index 0000000000..e3a87e4be6 --- /dev/null +++ b/cli/tests/lsp/code_lens_response.json @@ -0,0 +1,34 @@ +[ + { + "range": { + "start": { + "line": 0, + "character": 6 + }, + "end": { + "line": 0, + "character": 7 + } + }, + "data": { + "specifier": "file:///a/file.ts", + "source": "references" + } + }, + { + "range": { + "start": { + "line": 1, + "character": 2 + }, + "end": { + "line": 1, + "character": 3 + } + }, + "data": { + "specifier": "file:///a/file.ts", + "source": "references" + } + } +] diff --git a/cli/tests/lsp/code_lens_response_impl.json b/cli/tests/lsp/code_lens_response_impl.json new file mode 100644 index 0000000000..b0073a23f7 --- /dev/null +++ b/cli/tests/lsp/code_lens_response_impl.json @@ -0,0 +1,50 @@ +[ + { + "range": { + "start": { + "line": 0, + "character": 10 + }, + "end": { + "line": 0, + "character": 11 + } + }, + "data": { + "specifier": "file:///a/file.ts", + "source": "implementations" + } + }, + { + "range": { + "start": { + "line": 0, + "character": 10 + }, + "end": { + "line": 0, + "character": 11 + } + }, + "data": { + "specifier": "file:///a/file.ts", + "source": "references" + } + }, + { + "range": { + "start": { + "line": 4, + "character": 6 + }, + "end": { + "line": 4, + "character": 7 + } + }, + "data": { + "specifier": "file:///a/file.ts", + "source": "references" + } + } +] diff --git a/cli/tests/lsp/completion_request.json b/cli/tests/lsp/completion_request.json deleted file mode 100644 index 81bf719a9e..0000000000 --- a/cli/tests/lsp/completion_request.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": 2, - "method": "textDocument/completion", - "params": { - "textDocument": { - "uri": "file:///a/file.ts" - }, - "position": { - "line": 0, - "character": 5 - }, - "context": { - "triggerKind": 2, - "triggerCharacter": "." - } - } -} diff --git a/cli/tests/lsp/completion_request_optional.json b/cli/tests/lsp/completion_request_optional.json deleted file mode 100644 index 5e86c33ff6..0000000000 --- a/cli/tests/lsp/completion_request_optional.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": 2, - "method": "textDocument/completion", - "params": { - "textDocument": { - "uri": "file:///a/file.ts" - }, - "position": { - "line": 8, - "character": 4 - }, - "context": { - "triggerKind": 2, - "triggerCharacter": "." - } - } -} diff --git a/cli/tests/lsp/completion_request_params_optional.json b/cli/tests/lsp/completion_request_params_optional.json new file mode 100644 index 0000000000..1f3c079c73 --- /dev/null +++ b/cli/tests/lsp/completion_request_params_optional.json @@ -0,0 +1,13 @@ +{ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "position": { + "line": 8, + "character": 4 + }, + "context": { + "triggerKind": 2, + "triggerCharacter": "." + } +} diff --git a/cli/tests/lsp/completion_request_registry.json b/cli/tests/lsp/completion_request_registry.json deleted file mode 100644 index 2165fbdab6..0000000000 --- a/cli/tests/lsp/completion_request_registry.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": 2, - "method": "textDocument/completion", - "params": { - "textDocument": { - "uri": "file:///a/file.ts" - }, - "position": { - "line": 0, - "character": 46 - }, - "context": { - "triggerKind": 2, - "triggerCharacter": "@" - } - } -} diff --git a/cli/tests/lsp/completion_request_registry_02.json b/cli/tests/lsp/completion_request_registry_02.json deleted file mode 100644 index 21c3bc4753..0000000000 --- a/cli/tests/lsp/completion_request_registry_02.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": 2, - "method": "textDocument/completion", - "params": { - "textDocument": { - "uri": "file:///a/file.ts" - }, - "position": { - "line": 0, - "character": 20 - }, - "context": { - "triggerKind": 2, - "triggerCharacter": "\"" - } - } -} diff --git a/cli/tests/lsp/completion_request_response_empty.json b/cli/tests/lsp/completion_request_response_empty.json new file mode 100644 index 0000000000..272dfb4756 --- /dev/null +++ b/cli/tests/lsp/completion_request_response_empty.json @@ -0,0 +1,38 @@ +{ + "isIncomplete": false, + "items": [ + { + "label": ".", + "kind": 19, + "detail": "(local)", + "sortText": "1", + "insertText": "." + }, + { + "label": "..", + "kind": 19, + "detail": "(local)", + "sortText": "1", + "insertText": ".." + }, + { + "label": "http://localhost:4545", + "kind": 19, + "detail": "(registry)", + "sortText": "2", + "textEdit": { + "range": { + "start": { + "line": 0, + "character": 20 + }, + "end": { + "line": 0, + "character": 20 + } + }, + "newText": "http://localhost:4545" + } + } + ] +} diff --git a/cli/tests/lsp/completion_resolve_params.json b/cli/tests/lsp/completion_resolve_params.json new file mode 100644 index 0000000000..26231036da --- /dev/null +++ b/cli/tests/lsp/completion_resolve_params.json @@ -0,0 +1,14 @@ +{ + "label": "build", + "kind": 6, + "sortText": "1", + "insertTextFormat": 1, + "data": { + "tsc": { + "specifier": "file:///a/file.ts", + "position": 5, + "name": "build", + "useCodeSnippet": false + } + } +} diff --git a/cli/tests/lsp/completion_resolve_params_optional.json b/cli/tests/lsp/completion_resolve_params_optional.json new file mode 100644 index 0000000000..cb99bf960c --- /dev/null +++ b/cli/tests/lsp/completion_resolve_params_optional.json @@ -0,0 +1,15 @@ +{ + "label": "b?", + "kind": 5, + "sortText": "1", + "filterText": "b", + "insertText": "b", + "data": { + "tsc": { + "specifier": "file:///a/file.ts", + "position": 79, + "name": "b", + "useCodeSnippet": false + } + } +} diff --git a/cli/tests/lsp/completion_resolve_params_registry.json b/cli/tests/lsp/completion_resolve_params_registry.json new file mode 100644 index 0000000000..99a4a048e2 --- /dev/null +++ b/cli/tests/lsp/completion_resolve_params_registry.json @@ -0,0 +1,20 @@ +{ + "label": "v2.0.0", + "kind": 19, + "detail": "(version)", + "sortText": "0000000003", + "filterText": "http://localhost:4545/x/a@v2.0.0", + "textEdit": { + "range": { + "start": { + "line": 0, + "character": 20 + }, + "end": { + "line": 0, + "character": 46 + } + }, + "newText": "http://localhost:4545/x/a@v2.0.0" + } +} diff --git a/cli/tests/lsp/completion_resolve_request.json b/cli/tests/lsp/completion_resolve_request.json deleted file mode 100644 index 7b78fb440e..0000000000 --- a/cli/tests/lsp/completion_resolve_request.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": 4, - "method": "completionItem/resolve", - "params": { - "label": "build", - "kind": 6, - "sortText": "1", - "insertTextFormat": 1, - "data": { - "tsc": { - "specifier": "file:///a/file.ts", - "position": 5, - "name": "build", - "useCodeSnippet": false - } - } - } -} diff --git a/cli/tests/lsp/completion_resolve_request_optional.json b/cli/tests/lsp/completion_resolve_request_optional.json deleted file mode 100644 index ffa60b9197..0000000000 --- a/cli/tests/lsp/completion_resolve_request_optional.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": 4, - "method": "completionItem/resolve", - "params": { - "label": "b?", - "kind": 5, - "sortText": "1", - "filterText": "b", - "insertText": "b", - "data": { - "tsc": { - "specifier": "file:///a/file.ts", - "position": 79, - "name": "b", - "useCodeSnippet": false - } - } - } -} diff --git a/cli/tests/lsp/completion_resolve_request_registry.json b/cli/tests/lsp/completion_resolve_request_registry.json deleted file mode 100644 index bae19d0609..0000000000 --- a/cli/tests/lsp/completion_resolve_request_registry.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": 4, - "method": "completionItem/resolve", - "params": { - "label": "v2.0.0", - "kind": 19, - "detail": "(version)", - "sortText": "0000000003", - "filterText": "http://localhost:4545/x/a@v2.0.0", - "textEdit": { - "range": { - "start": { - "line": 0, - "character": 20 - }, - "end": { - "line": 0, - "character": 46 - } - }, - "newText": "http://localhost:4545/x/a@v2.0.0" - } - } -} diff --git a/cli/tests/lsp/completion_resolve_response.json b/cli/tests/lsp/completion_resolve_response.json new file mode 100644 index 0000000000..0edbc14ef2 --- /dev/null +++ b/cli/tests/lsp/completion_resolve_response.json @@ -0,0 +1,11 @@ +{ + "label": "build", + "kind": 6, + "detail": "const Deno.build: {\n target: string;\n arch: \"x86_64\" | \"aarch64\";\n os: \"darwin\" | \"linux\" | \"windows\";\n vendor: string;\n env?: string | undefined;\n}", + "documentation": { + "kind": "markdown", + "value": "Build related information." + }, + "sortText": "1", + "insertTextFormat": 1 +} diff --git a/cli/tests/lsp/completion_resolve_response_registry.json b/cli/tests/lsp/completion_resolve_response_registry.json new file mode 100644 index 0000000000..99a4a048e2 --- /dev/null +++ b/cli/tests/lsp/completion_resolve_response_registry.json @@ -0,0 +1,20 @@ +{ + "label": "v2.0.0", + "kind": 19, + "detail": "(version)", + "sortText": "0000000003", + "filterText": "http://localhost:4545/x/a@v2.0.0", + "textEdit": { + "range": { + "start": { + "line": 0, + "character": 20 + }, + "end": { + "line": 0, + "character": 46 + } + }, + "newText": "http://localhost:4545/x/a@v2.0.0" + } +} diff --git a/cli/tests/lsp/definition_request_asset.json b/cli/tests/lsp/definition_request_asset.json deleted file mode 100644 index 5e117cf147..0000000000 --- a/cli/tests/lsp/definition_request_asset.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": 4, - "method": "textDocument/definition", - "params": { - "textDocument": { - "uri": "file:///a/file.ts" - }, - "position": { - "line": 0, - "character": 14 - } - } -} diff --git a/cli/tests/lsp/did_change_notification_large.json b/cli/tests/lsp/did_change_notification_large.json deleted file mode 100644 index c4999a7c3e..0000000000 --- a/cli/tests/lsp/did_change_notification_large.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "jsonrpc": "2.0", - "method": "textDocument/didChange", - "params": { - "textDocument": { - "uri": "file:///a/file.ts", - "version": 2 - }, - "contentChanges": [ - { - "range": { - "start": { - "line": 444, - "character": 11 - }, - "end": { - "line": 444, - "character": 14 - } - }, - "text": "+++" - } - ] - } -} diff --git a/cli/tests/lsp/did_change_notification_large_02.json b/cli/tests/lsp/did_change_notification_large_02.json deleted file mode 100644 index e8744d620e..0000000000 --- a/cli/tests/lsp/did_change_notification_large_02.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "jsonrpc": "2.0", - "method": "textDocument/didChange", - "params": { - "textDocument": { - "uri": "file:///a/file.ts", - "version": 2 - }, - "contentChanges": [ - { - "range": { - "start": { - "line": 445, - "character": 4 - }, - "end": { - "line": 445, - "character": 4 - } - }, - "text": "// " - } - ] - } -} diff --git a/cli/tests/lsp/did_change_notification_large_03.json b/cli/tests/lsp/did_change_notification_large_03.json deleted file mode 100644 index f39234510f..0000000000 --- a/cli/tests/lsp/did_change_notification_large_03.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "jsonrpc": "2.0", - "method": "textDocument/didChange", - "params": { - "textDocument": { - "uri": "file:///a/file.ts", - "version": 2 - }, - "contentChanges": [ - { - "range": { - "start": { - "line": 477, - "character": 4 - }, - "end": { - "line": 477, - "character": 9 - } - }, - "text": "error" - } - ] - } -} diff --git a/cli/tests/lsp/did_change_notification_mbc.json b/cli/tests/lsp/did_change_notification_mbc.json deleted file mode 100644 index fed742d396..0000000000 --- a/cli/tests/lsp/did_change_notification_mbc.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "jsonrpc": "2.0", - "method": "textDocument/didChange", - "params": { - "textDocument": { - "uri": "file:///a/file.ts", - "version": 2 - }, - "contentChanges": [ - { - "range": { - "start": { - "line": 1, - "character": 11 - }, - "end": { - "line": 1, - "character": 13 - } - }, - "text": "" - } - ] - } -} diff --git a/cli/tests/lsp/did_open_notification.json b/cli/tests/lsp/did_open_notification.json deleted file mode 100644 index 04f12a7b3a..0000000000 --- a/cli/tests/lsp/did_open_notification.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "jsonrpc": "2.0", - "method": "textDocument/didOpen", - "params": { - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "console.log(Deno.args);\n" - } - } -} diff --git a/cli/tests/lsp/did_open_notification_asset.json b/cli/tests/lsp/did_open_notification_asset.json deleted file mode 100644 index 413353f298..0000000000 --- a/cli/tests/lsp/did_open_notification_asset.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "jsonrpc": "2.0", - "method": "textDocument/didOpen", - "params": { - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "console.log(Date.now());\n" - } - } -} diff --git a/cli/tests/lsp/did_open_notification_cache.json b/cli/tests/lsp/did_open_notification_cache.json deleted file mode 100644 index 6f21ee5cd9..0000000000 --- a/cli/tests/lsp/did_open_notification_cache.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "jsonrpc": "2.0", - "method": "textDocument/didOpen", - "params": { - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "import * as a from \"https://deno.land/x/a/mod.ts\";\n\nconsole.log(a);\n" - } - } -} diff --git a/cli/tests/lsp/did_open_notification_cl_impl.json b/cli/tests/lsp/did_open_notification_cl_impl.json deleted file mode 100644 index eabcd7ccc8..0000000000 --- a/cli/tests/lsp/did_open_notification_cl_impl.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "jsonrpc": "2.0", - "method": "textDocument/didOpen", - "params": { - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "interface A {\n b(): void;\n}\n\nclass B implements A {\n b() {\n console.log(\"b\");\n }\n}\n" - } - } -} diff --git a/cli/tests/lsp/did_open_notification_cl_references.json b/cli/tests/lsp/did_open_notification_cl_references.json deleted file mode 100644 index 546ba5674c..0000000000 --- a/cli/tests/lsp/did_open_notification_cl_references.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "jsonrpc": "2.0", - "method": "textDocument/didOpen", - "params": { - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "class A {\n a = \"a\";\n\n b() {\n console.log(this.a);\n }\n\n c() {\n this.a = \"c\";\n }\n}\n\nconst a = new A();\na.b();\n" - } - } -} diff --git a/cli/tests/lsp/did_open_notification_code_action.json b/cli/tests/lsp/did_open_notification_code_action.json deleted file mode 100644 index 57559cf3c4..0000000000 --- a/cli/tests/lsp/did_open_notification_code_action.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "jsonrpc": "2.0", - "method": "textDocument/didOpen", - "params": { - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "export function a(): void {\n await Promise.resolve(\"a\");\n}\n\nexport function b(): void {\n await Promise.resolve(\"b\");\n}\n" - } - } -} diff --git a/cli/tests/lsp/did_open_notification_completion_optional.json b/cli/tests/lsp/did_open_notification_completion_optional.json deleted file mode 100644 index 5e919c80ab..0000000000 --- a/cli/tests/lsp/did_open_notification_completion_optional.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "jsonrpc": "2.0", - "method": "textDocument/didOpen", - "params": { - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "interface A {\n b?: string;\n}\n\nconst o: A = {};\n\nfunction c(s: string) {}\n\nc(o.)" - } - } -} diff --git a/cli/tests/lsp/did_open_notification_completion_registry.json b/cli/tests/lsp/did_open_notification_completion_registry.json deleted file mode 100644 index cb8ad25927..0000000000 --- a/cli/tests/lsp/did_open_notification_completion_registry.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "jsonrpc": "2.0", - "method": "textDocument/didOpen", - "params": { - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "import * as a from \"http://localhost:4545/x/a@\"" - } - } -} diff --git a/cli/tests/lsp/did_open_notification_completion_registry_02.json b/cli/tests/lsp/did_open_notification_completion_registry_02.json deleted file mode 100644 index a53882b560..0000000000 --- a/cli/tests/lsp/did_open_notification_completion_registry_02.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "jsonrpc": "2.0", - "method": "textDocument/didOpen", - "params": { - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "import * as a from \"\"" - } - } -} diff --git a/cli/tests/lsp/did_open_notification_completions.json b/cli/tests/lsp/did_open_notification_completions.json deleted file mode 100644 index edcdc93734..0000000000 --- a/cli/tests/lsp/did_open_notification_completions.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "jsonrpc": "2.0", - "method": "textDocument/didOpen", - "params": { - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "Deno." - } - } -} diff --git a/cli/tests/lsp/did_open_notification_large.json b/cli/tests/lsp/did_open_notification_large.json deleted file mode 100644 index 4a467891c2..0000000000 --- a/cli/tests/lsp/did_open_notification_large.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "jsonrpc": "2.0", - "method": "textDocument/didOpen", - "params": { - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "javascript", - "version": 1, - "text": "// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.\n\n// @ts-check\n/// \n// deno-lint-ignore-file no-undef\n\n// This module is the entry point for \"compiler\" isolate, ie. the one\n// that is created when Deno needs to type check TypeScript, and in some\n// instances convert TypeScript to JavaScript.\n\n// Removes the `__proto__` for security reasons. This intentionally makes\n// Deno non compliant with ECMA-262 Annex B.2.2.1\ndelete Object.prototype.__proto__;\n\n((window) => {\n /** @type {DenoCore} */\n const core = window.Deno.core;\n\n let logDebug = false;\n let logSource = \"JS\";\n\n function setLogDebug(debug, source) {\n logDebug = debug;\n if (source) {\n logSource = source;\n }\n }\n\n function debug(...args) {\n if (logDebug) {\n const stringifiedArgs = args.map((arg) =>\n typeof arg === \"string\" ? arg : JSON.stringify(arg)\n ).join(\" \");\n // adding a non-zero integer value to the end of the debug string causes\n // the message to be printed to stderr instead of stdout, which is better\n // aligned to the behaviour of debug messages\n core.print(`DEBUG ${logSource} - ${stringifiedArgs}\\n`, 1);\n }\n }\n\n function error(...args) {\n const stringifiedArgs = args.map((arg) =>\n typeof arg === \"string\" || arg instanceof Error\n ? String(arg)\n : JSON.stringify(arg)\n ).join(\" \");\n core.print(`ERROR ${logSource} = ${stringifiedArgs}\\n`, 1);\n }\n\n class AssertionError extends Error {\n constructor(msg) {\n super(msg);\n this.name = \"AssertionError\";\n }\n }\n\n function assert(cond, msg = \"Assertion failed.\") {\n if (!cond) {\n throw new AssertionError(msg);\n }\n }\n\n /** @type {Map} */\n const sourceFileCache = new Map();\n\n /** @param {ts.DiagnosticRelatedInformation} diagnostic */\n function fromRelatedInformation({\n start,\n length,\n file,\n messageText: msgText,\n ...ri\n }) {\n let messageText;\n let messageChain;\n if (typeof msgText === \"object\") {\n messageChain = msgText;\n } else {\n messageText = msgText;\n }\n if (start !== undefined && length !== undefined && file) {\n const startPos = file.getLineAndCharacterOfPosition(start);\n const sourceLine = file.getFullText().split(\"\\n\")[startPos.line];\n const fileName = file.fileName;\n return {\n start: startPos,\n end: file.getLineAndCharacterOfPosition(start + length),\n fileName,\n messageChain,\n messageText,\n sourceLine,\n ...ri,\n };\n } else {\n return {\n messageChain,\n messageText,\n ...ri,\n };\n }\n }\n\n /** @param {ts.Diagnostic[]} diagnostics */\n function fromTypeScriptDiagnostic(diagnostics) {\n return diagnostics.map(({ relatedInformation: ri, source, ...diag }) => {\n /** @type {any} */\n const value = fromRelatedInformation(diag);\n value.relatedInformation = ri\n ? ri.map(fromRelatedInformation)\n : undefined;\n value.source = source;\n return value;\n });\n }\n\n // Using incremental compile APIs requires that all\n // paths must be either relative or absolute. Since\n // analysis in Rust operates on fully resolved URLs,\n // it makes sense to use the same scheme here.\n const ASSETS = \"asset:///\";\n const CACHE = \"cache:///\";\n\n /** Diagnostics that are intentionally ignored when compiling TypeScript in\n * Deno, as they provide misleading or incorrect information. */\n const IGNORED_DIAGNOSTICS = [\n // TS1208: All files must be modules when the '--isolatedModules' flag is\n // provided. We can ignore because we guarantee that all files are\n // modules.\n 1208,\n // TS1375: 'await' expressions are only allowed at the top level of a file\n // when that file is a module, but this file has no imports or exports.\n // Consider adding an empty 'export {}' to make this file a module.\n 1375,\n // TS1103: 'for-await-of' statement is only allowed within an async function\n // or async generator.\n 1103,\n // TS2306: File 'file:///Users/rld/src/deno/cli/tests/subdir/amd_like.js' is\n // not a module.\n 2306,\n // TS2691: An import path cannot end with a '.ts' extension. Consider\n // importing 'bad-module' instead.\n 2691,\n // TS2792: Cannot find module. Did you mean to set the 'moduleResolution'\n // option to 'node', or to add aliases to the 'paths' option?\n 2792,\n // TS5009: Cannot find the common subdirectory path for the input files.\n 5009,\n // TS5055: Cannot write file\n // 'http://localhost:4545/cli/tests/subdir/mt_application_x_javascript.j4.js'\n // because it would overwrite input file.\n 5055,\n // TypeScript is overly opinionated that only CommonJS modules kinds can\n // support JSON imports. Allegedly this was fixed in\n // Microsoft/TypeScript#26825 but that doesn't seem to be working here,\n // so we will ignore complaints about this compiler setting.\n 5070,\n // TS7016: Could not find a declaration file for module '...'. '...'\n // implicitly has an 'any' type. This is due to `allowJs` being off by\n // default but importing of a JavaScript module.\n 7016,\n ];\n\n const SNAPSHOT_COMPILE_OPTIONS = {\n esModuleInterop: true,\n jsx: ts.JsxEmit.React,\n module: ts.ModuleKind.ESNext,\n noEmit: true,\n strict: true,\n target: ts.ScriptTarget.ESNext,\n };\n\n class ScriptSnapshot {\n /** @type {string} */\n specifier;\n /** @type {string} */\n version;\n /**\n * @param {string} specifier\n * @param {string} version \n */\n constructor(specifier, version) {\n this.specifier = specifier;\n this.version = version;\n }\n /**\n * @param {number} start \n * @param {number} end \n * @returns {string}\n */\n getText(start, end) {\n const { specifier, version } = this;\n debug(\n `snapshot.getText(${start}, ${end}) specifier: ${specifier} version: ${version}`,\n );\n return core.jsonOpSync(\"op_get_text\", { specifier, version, start, end });\n }\n /**\n * @returns {number}\n */\n getLength() {\n const { specifier, version } = this;\n debug(`snapshot.getLength() specifier: ${specifier} version: ${version}`);\n return core.jsonOpSync(\"op_get_length\", { specifier, version });\n }\n /**\n * @param {ScriptSnapshot} oldSnapshot\n * @returns {ts.TextChangeRange | undefined}\n */\n getChangeRange(oldSnapshot) {\n const { specifier, version } = this;\n const { version: oldVersion } = oldSnapshot;\n const oldLength = oldSnapshot.getLength();\n debug(\n `snapshot.getLength() specifier: ${specifier} oldVersion: ${oldVersion} version: ${version}`,\n );\n return core.jsonOpSync(\n \"op_get_change_range\",\n { specifier, oldLength, oldVersion, version },\n );\n }\n dispose() {\n const { specifier, version } = this;\n debug(`snapshot.dispose() specifier: ${specifier} version: ${version}`);\n core.jsonOpSync(\"op_dispose\", { specifier, version });\n }\n }\n\n /** @type {ts.CompilerOptions} */\n let compilationSettings = {};\n\n /** @type {ts.LanguageService} */\n let languageService;\n\n /** An object literal of the incremental compiler host, which provides the\n * specific \"bindings\" to the Deno environment that tsc needs to work.\n *\n * @type {ts.CompilerHost & ts.LanguageServiceHost} */\n const host = {\n fileExists(fileName) {\n debug(`host.fileExists(\"${fileName}\")`);\n return false;\n },\n readFile(specifier) {\n debug(`host.readFile(\"${specifier}\")`);\n return core.jsonOpSync(\"op_load\", { specifier }).data;\n },\n getSourceFile(\n specifier,\n languageVersion,\n _onError,\n _shouldCreateNewSourceFile,\n ) {\n debug(\n `host.getSourceFile(\"${specifier}\", ${\n ts.ScriptTarget[languageVersion]\n })`,\n );\n let sourceFile = sourceFileCache.get(specifier);\n if (sourceFile) {\n return sourceFile;\n }\n\n /** @type {{ data: string; hash?: string; scriptKind: ts.ScriptKind }} */\n const { data, hash, scriptKind } = core.jsonOpSync(\n \"op_load\",\n { specifier },\n );\n assert(\n data != null,\n `\"data\" is unexpectedly null for \"${specifier}\".`,\n );\n sourceFile = ts.createSourceFile(\n specifier,\n data,\n languageVersion,\n false,\n scriptKind,\n );\n sourceFile.moduleName = specifier;\n sourceFile.version = hash;\n sourceFileCache.set(specifier, sourceFile);\n return sourceFile;\n },\n getDefaultLibFileName() {\n return `${ASSETS}/lib.esnext.d.ts`;\n },\n getDefaultLibLocation() {\n return ASSETS;\n },\n writeFile(fileName, data, _writeByteOrderMark, _onError, sourceFiles) {\n debug(`host.writeFile(\"${fileName}\")`);\n let maybeSpecifiers;\n if (sourceFiles) {\n maybeSpecifiers = sourceFiles.map((sf) => sf.moduleName);\n }\n return core.jsonOpSync(\n \"op_emit\",\n { maybeSpecifiers, fileName, data },\n );\n },\n getCurrentDirectory() {\n return CACHE;\n },\n getCanonicalFileName(fileName) {\n return fileName;\n },\n useCaseSensitiveFileNames() {\n return true;\n },\n getNewLine() {\n return \"\\n\";\n },\n resolveModuleNames(specifiers, base) {\n debug(`host.resolveModuleNames()`);\n debug(` base: ${base}`);\n debug(` specifiers: ${specifiers.join(\", \")}`);\n /** @type {Array<[string, ts.Extension] | undefined>} */\n const resolved = core.jsonOpSync(\"op_resolve\", {\n specifiers,\n base,\n });\n if (resolved) {\n const result = resolved.map((item) => {\n if (item) {\n const [resolvedFileName, extension] = item;\n return {\n resolvedFileName,\n extension,\n isExternalLibraryImport: false,\n };\n }\n return undefined;\n });\n result.length = specifiers.length;\n return result;\n } else {\n return new Array(specifiers.length);\n }\n },\n createHash(data) {\n return core.jsonOpSync(\"op_create_hash\", { data }).hash;\n },\n\n // LanguageServiceHost\n getCompilationSettings() {\n debug(\"host.getCompilationSettings()\");\n return compilationSettings;\n },\n getScriptFileNames() {\n debug(\"host.getScriptFileNames()\");\n return core.jsonOpSync(\"op_script_names\", undefined);\n },\n getScriptVersion(specifier) {\n debug(`host.getScriptVersion(\"${specifier}\")`);\n const sourceFile = sourceFileCache.get(specifier);\n if (sourceFile) {\n return sourceFile.version ?? \"1\";\n }\n return core.jsonOpSync(\"op_script_version\", { specifier });\n },\n getScriptSnapshot(specifier) {\n debug(`host.getScriptSnapshot(\"${specifier}\")`);\n const sourceFile = sourceFileCache.get(specifier);\n if (sourceFile) {\n return {\n getText(start, end) {\n return sourceFile.text.substring(start, end);\n },\n getLength() {\n return sourceFile.text.length;\n },\n getChangeRange() {\n return undefined;\n },\n };\n }\n /** @type {string | undefined} */\n const version = core.jsonOpSync(\"op_script_version\", { specifier });\n if (version != null) {\n return new ScriptSnapshot(specifier, version);\n }\n return undefined;\n },\n };\n\n /** @type {Array<[string, number]>} */\n const stats = [];\n let statsStart = 0;\n\n function performanceStart() {\n stats.length = 0;\n statsStart = Date.now();\n ts.performance.enable();\n }\n\n /**\n * @param {{ program: ts.Program | ts.EmitAndSemanticDiagnosticsBuilderProgram, fileCount?: number }} options \n */\n function performanceProgram({ program, fileCount }) {\n if (program) {\n if (\"getProgram\" in program) {\n program = program.getProgram();\n }\n stats.push([\"Files\", program.getSourceFiles().length]);\n stats.push([\"Nodes\", program.getNodeCount()]);\n stats.push([\"Identifiers\", program.getIdentifierCount()]);\n stats.push([\"Symbols\", program.getSymbolCount()]);\n stats.push([\"Types\", program.getTypeCount()]);\n stats.push([\"Instantiations\", program.getInstantiationCount()]);\n } else if (fileCount != null) {\n stats.push([\"Files\", fileCount]);\n }\n const programTime = ts.performance.getDuration(\"Program\");\n const bindTime = ts.performance.getDuration(\"Bind\");\n const checkTime = ts.performance.getDuration(\"Check\");\n const emitTime = ts.performance.getDuration(\"Emit\");\n stats.push([\"Parse time\", programTime]);\n stats.push([\"Bind time\", bindTime]);\n stats.push([\"Check time\", checkTime]);\n stats.push([\"Emit time\", emitTime]);\n stats.push(\n [\"Total TS time\", programTime + bindTime + checkTime + emitTime],\n );\n }\n\n function performanceEnd() {\n const duration = Date.now() - statsStart;\n stats.push([\"Compile time\", duration]);\n return stats;\n }\n\n /**\n * @typedef {object} Request\n * @property {Record} config\n * @property {boolean} debug\n * @property {string[]} rootNames\n */\n\n /** The API that is called by Rust when executing a request.\n * @param {Request} request\n */\n function exec({ config, debug: debugFlag, rootNames }) {\n setLogDebug(debugFlag, \"TS\");\n performanceStart();\n debug(\">>> exec start\", { rootNames });\n debug(config);\n\n const { options, errors: configFileParsingDiagnostics } = ts\n .convertCompilerOptionsFromJson(config, \"\");\n // The `allowNonTsExtensions` is a \"hidden\" compiler option used in VSCode\n // which is not allowed to be passed in JSON, we need it to allow special\n // URLs which Deno supports. So we need to either ignore the diagnostic, or\n // inject it ourselves.\n Object.assign(options, { allowNonTsExtensions: true });\n const program = ts.createIncrementalProgram({\n rootNames,\n options,\n host,\n configFileParsingDiagnostics,\n });\n\n const { diagnostics: emitDiagnostics } = program.emit();\n\n const diagnostics = [\n ...program.getConfigFileParsingDiagnostics(),\n ...program.getSyntacticDiagnostics(),\n ...program.getOptionsDiagnostics(),\n ...program.getGlobalDiagnostics(),\n ...program.getSemanticDiagnostics(),\n ...emitDiagnostics,\n ].filter(({ code }) => !IGNORED_DIAGNOSTICS.includes(code));\n performanceProgram({ program });\n\n core.jsonOpSync(\"op_respond\", {\n diagnostics: fromTypeScriptDiagnostic(diagnostics),\n stats: performanceEnd(),\n });\n debug(\"<<< exec stop\");\n }\n\n /**\n * @param {number} id \n * @param {any} data \n */\n function respond(id, data = null) {\n core.jsonOpSync(\"op_respond\", { id, data });\n }\n\n /**\n * @param {LanguageServerRequest} request \n */\n function serverRequest({ id, ...request }) {\n debug(`serverRequest()`, { id, ...request });\n switch (request.method) {\n case \"configure\": {\n const { options, errors } = ts\n .convertCompilerOptionsFromJson(request.compilerOptions, \"\");\n Object.assign(options, { allowNonTsExtensions: true });\n if (errors.length) {\n debug(ts.formatDiagnostics(errors, host));\n }\n compilationSettings = options;\n return respond(id, true);\n }\n case \"getAsset\": {\n const sourceFile = host.getSourceFile(\n request.specifier,\n ts.ScriptTarget.ESNext,\n );\n return respond(id, sourceFile && sourceFile.text);\n }\n case \"getDiagnostics\": {\n try {\n /** @type {Record} */\n const diagnosticMap = {};\n for (const specifier of request.specifiers) {\n diagnosticMap[specifier] = fromTypeScriptDiagnostic([\n ...languageService.getSemanticDiagnostics(specifier),\n ...languageService.getSuggestionDiagnostics(specifier),\n ...languageService.getSyntacticDiagnostics(specifier),\n ].filter(({ code }) => !IGNORED_DIAGNOSTICS.includes(code)));\n }\n return respond(id, diagnosticMap);\n } catch (e) {\n if (\"stack\" in e) {\n error(e.stack);\n } else {\n error(e);\n }\n return respond(id, {});\n }\n }\n case \"getQuickInfo\": {\n return respond(\n id,\n languageService.getQuickInfoAtPosition(\n request.specifier,\n request.position,\n ),\n );\n }\n case \"getCompletions\": {\n return respond(\n id,\n languageService.getCompletionsAtPosition(\n request.specifier,\n request.position,\n request.preferences,\n ),\n );\n }\n case \"getDocumentHighlights\": {\n return respond(\n id,\n languageService.getDocumentHighlights(\n request.specifier,\n request.position,\n request.filesToSearch,\n ),\n );\n }\n case \"getReferences\": {\n return respond(\n id,\n languageService.getReferencesAtPosition(\n request.specifier,\n request.position,\n ),\n );\n }\n case \"getDefinition\": {\n return respond(\n id,\n languageService.getDefinitionAndBoundSpan(\n request.specifier,\n request.position,\n ),\n );\n }\n case \"getImplementation\": {\n return respond(\n id,\n languageService.getImplementationAtPosition(\n request.specifier,\n request.position,\n ),\n );\n }\n case \"findRenameLocations\": {\n return respond(\n id,\n languageService.findRenameLocations(\n request.specifier,\n request.position,\n request.findInStrings,\n request.findInComments,\n request.providePrefixAndSuffixTextForRename,\n ),\n );\n }\n default:\n throw new TypeError(\n // @ts-ignore exhausted case statement sets type to never\n `Invalid request method for request: \"${request.method}\" (${id})`,\n );\n }\n }\n\n /** @param {{ debug: boolean; }} init */\n function serverInit({ debug: debugFlag }) {\n if (hasStarted) {\n throw new Error(\"The language server has already been initialized.\");\n }\n hasStarted = true;\n languageService = ts.createLanguageService(host);\n core.ops();\n setLogDebug(debugFlag, \"TSLS\");\n debug(\"serverInit()\");\n }\n\n let hasStarted = false;\n\n /** Startup the runtime environment, setting various flags.\n * @param {{ debugFlag?: boolean; legacyFlag?: boolean; }} msg\n */\n function startup({ debugFlag = false }) {\n if (hasStarted) {\n throw new Error(\"The compiler runtime already started.\");\n }\n hasStarted = true;\n core.ops();\n setLogDebug(!!debugFlag, \"TS\");\n }\n\n // Setup the compiler runtime during the build process.\n core.ops();\n core.registerErrorClass(\"Error\", Error);\n\n // A build time only op that provides some setup information that is used to\n // ensure the snapshot is setup properly.\n /** @type {{ buildSpecifier: string; libs: string[] }} */\n const { buildSpecifier, libs } = core.jsonOpSync(\"op_build_info\", {});\n for (const lib of libs) {\n const specifier = `lib.${lib}.d.ts`;\n // we are using internal APIs here to \"inject\" our custom libraries into\n // tsc, so things like `\"lib\": [ \"deno.ns\" ]` are supported.\n if (!ts.libs.includes(lib)) {\n ts.libs.push(lib);\n ts.libMap.set(lib, `lib.${lib}.d.ts`);\n }\n // we are caching in memory common type libraries that will be re-used by\n // tsc on when the snapshot is restored\n assert(\n host.getSourceFile(`${ASSETS}${specifier}`, ts.ScriptTarget.ESNext),\n );\n }\n // this helps ensure as much as possible is in memory that is re-usable\n // before the snapshotting is done, which helps unsure fast \"startup\" for\n // subsequent uses of tsc in Deno.\n const TS_SNAPSHOT_PROGRAM = ts.createProgram({\n rootNames: [buildSpecifier],\n options: SNAPSHOT_COMPILE_OPTIONS,\n host,\n });\n ts.getPreEmitDiagnostics(TS_SNAPSHOT_PROGRAM);\n\n // exposes the two functions that are called by `tsc::exec()` when type\n // checking TypeScript.\n globalThis.startup = startup;\n globalThis.exec = exec;\n\n // exposes the functions that are called when the compiler is used as a\n // language service.\n globalThis.serverInit = serverInit;\n globalThis.serverRequest = serverRequest;\n})(this);\n" - } - } -} diff --git a/cli/tests/lsp/did_open_notification_mbc.json b/cli/tests/lsp/did_open_notification_mbc.json deleted file mode 100644 index d7dd9444e7..0000000000 --- a/cli/tests/lsp/did_open_notification_mbc.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "jsonrpc": "2.0", - "method": "textDocument/didOpen", - "params": { - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "const a = `编写软件很难`;\nconst b = `👍🦕😃`;\nconsole.log(a, b);\n" - } - } -} diff --git a/cli/tests/lsp/did_open_notification_mbc_fmt.json b/cli/tests/lsp/did_open_notification_mbc_fmt.json deleted file mode 100644 index 528dad25dd..0000000000 --- a/cli/tests/lsp/did_open_notification_mbc_fmt.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "jsonrpc": "2.0", - "method": "textDocument/didOpen", - "params": { - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "const bar = '👍🇺🇸😃'\nconsole.log('hello deno')\n" - } - } -} diff --git a/cli/tests/lsp/did_open_notification_unstable.json b/cli/tests/lsp/did_open_notification_unstable.json deleted file mode 100644 index bb7a1f6792..0000000000 --- a/cli/tests/lsp/did_open_notification_unstable.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "jsonrpc": "2.0", - "method": "textDocument/didOpen", - "params": { - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "console.log(Deno.openPlugin);\n" - } - } -} diff --git a/cli/tests/lsp/did_open_params_doc_symbol.json b/cli/tests/lsp/did_open_params_doc_symbol.json new file mode 100644 index 0000000000..c748771919 --- /dev/null +++ b/cli/tests/lsp/did_open_params_doc_symbol.json @@ -0,0 +1,8 @@ +{ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "interface IFoo {\n foo(): boolean;\n}\n\nclass Bar implements IFoo {\n constructor(public x: number) { }\n foo() { return true; }\n /** @deprecated */\n baz() { return false; }\n get value(): number { return 0; }\n set value(newVavlue: number) { return; }\n static staticBar = new Bar(0);\n private static getStaticBar() { return Bar.staticBar; }\n}\n\nenum Values { value1, value2 }\n\nvar bar: IFoo = new Bar(3);" + } +} diff --git a/cli/tests/lsp/did_open_params_large.json b/cli/tests/lsp/did_open_params_large.json new file mode 100644 index 0000000000..b78d6e799e --- /dev/null +++ b/cli/tests/lsp/did_open_params_large.json @@ -0,0 +1,8 @@ +{ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "javascript", + "version": 1, + "text": "// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.\n\n// @ts-check\n/// \n// deno-lint-ignore-file no-undef\n\n// This module is the entry point for \"compiler\" isolate, ie. the one\n// that is created when Deno needs to type check TypeScript, and in some\n// instances convert TypeScript to JavaScript.\n\n// Removes the `__proto__` for security reasons. This intentionally makes\n// Deno non compliant with ECMA-262 Annex B.2.2.1\ndelete Object.prototype.__proto__;\n\n((window) => {\n /** @type {DenoCore} */\n const core = window.Deno.core;\n\n let logDebug = false;\n let logSource = \"JS\";\n\n function setLogDebug(debug, source) {\n logDebug = debug;\n if (source) {\n logSource = source;\n }\n }\n\n function debug(...args) {\n if (logDebug) {\n const stringifiedArgs = args.map((arg) =>\n typeof arg === \"string\" ? arg : JSON.stringify(arg)\n ).join(\" \");\n // adding a non-zero integer value to the end of the debug string causes\n // the message to be printed to stderr instead of stdout, which is better\n // aligned to the behaviour of debug messages\n core.print(`DEBUG ${logSource} - ${stringifiedArgs}\\n`, 1);\n }\n }\n\n function error(...args) {\n const stringifiedArgs = args.map((arg) =>\n typeof arg === \"string\" || arg instanceof Error\n ? String(arg)\n : JSON.stringify(arg)\n ).join(\" \");\n core.print(`ERROR ${logSource} = ${stringifiedArgs}\\n`, 1);\n }\n\n class AssertionError extends Error {\n constructor(msg) {\n super(msg);\n this.name = \"AssertionError\";\n }\n }\n\n function assert(cond, msg = \"Assertion failed.\") {\n if (!cond) {\n throw new AssertionError(msg);\n }\n }\n\n /** @type {Map} */\n const sourceFileCache = new Map();\n\n /** @param {ts.DiagnosticRelatedInformation} diagnostic */\n function fromRelatedInformation({\n start,\n length,\n file,\n messageText: msgText,\n ...ri\n }) {\n let messageText;\n let messageChain;\n if (typeof msgText === \"object\") {\n messageChain = msgText;\n } else {\n messageText = msgText;\n }\n if (start !== undefined && length !== undefined && file) {\n const startPos = file.getLineAndCharacterOfPosition(start);\n const sourceLine = file.getFullText().split(\"\\n\")[startPos.line];\n const fileName = file.fileName;\n return {\n start: startPos,\n end: file.getLineAndCharacterOfPosition(start + length),\n fileName,\n messageChain,\n messageText,\n sourceLine,\n ...ri,\n };\n } else {\n return {\n messageChain,\n messageText,\n ...ri,\n };\n }\n }\n\n /** @param {ts.Diagnostic[]} diagnostics */\n function fromTypeScriptDiagnostic(diagnostics) {\n return diagnostics.map(({ relatedInformation: ri, source, ...diag }) => {\n /** @type {any} */\n const value = fromRelatedInformation(diag);\n value.relatedInformation = ri\n ? ri.map(fromRelatedInformation)\n : undefined;\n value.source = source;\n return value;\n });\n }\n\n // Using incremental compile APIs requires that all\n // paths must be either relative or absolute. Since\n // analysis in Rust operates on fully resolved URLs,\n // it makes sense to use the same scheme here.\n const ASSETS = \"asset:///\";\n const CACHE = \"cache:///\";\n\n /** Diagnostics that are intentionally ignored when compiling TypeScript in\n * Deno, as they provide misleading or incorrect information. */\n const IGNORED_DIAGNOSTICS = [\n // TS1208: All files must be modules when the '--isolatedModules' flag is\n // provided. We can ignore because we guarantee that all files are\n // modules.\n 1208,\n // TS1375: 'await' expressions are only allowed at the top level of a file\n // when that file is a module, but this file has no imports or exports.\n // Consider adding an empty 'export {}' to make this file a module.\n 1375,\n // TS1103: 'for-await-of' statement is only allowed within an async function\n // or async generator.\n 1103,\n // TS2306: File 'file:///Users/rld/src/deno/cli/tests/subdir/amd_like.js' is\n // not a module.\n 2306,\n // TS2691: An import path cannot end with a '.ts' extension. Consider\n // importing 'bad-module' instead.\n 2691,\n // TS2792: Cannot find module. Did you mean to set the 'moduleResolution'\n // option to 'node', or to add aliases to the 'paths' option?\n 2792,\n // TS5009: Cannot find the common subdirectory path for the input files.\n 5009,\n // TS5055: Cannot write file\n // 'http://localhost:4545/cli/tests/subdir/mt_application_x_javascript.j4.js'\n // because it would overwrite input file.\n 5055,\n // TypeScript is overly opinionated that only CommonJS modules kinds can\n // support JSON imports. Allegedly this was fixed in\n // Microsoft/TypeScript#26825 but that doesn't seem to be working here,\n // so we will ignore complaints about this compiler setting.\n 5070,\n // TS7016: Could not find a declaration file for module '...'. '...'\n // implicitly has an 'any' type. This is due to `allowJs` being off by\n // default but importing of a JavaScript module.\n 7016,\n ];\n\n const SNAPSHOT_COMPILE_OPTIONS = {\n esModuleInterop: true,\n jsx: ts.JsxEmit.React,\n module: ts.ModuleKind.ESNext,\n noEmit: true,\n strict: true,\n target: ts.ScriptTarget.ESNext,\n };\n\n class ScriptSnapshot {\n /** @type {string} */\n specifier;\n /** @type {string} */\n version;\n /**\n * @param {string} specifier\n * @param {string} version \n */\n constructor(specifier, version) {\n this.specifier = specifier;\n this.version = version;\n }\n /**\n * @param {number} start \n * @param {number} end \n * @returns {string}\n */\n getText(start, end) {\n const { specifier, version } = this;\n debug(\n `snapshot.getText(${start}, ${end}) specifier: ${specifier} version: ${version}`,\n );\n return core.jsonOpSync(\"op_get_text\", { specifier, version, start, end });\n }\n /**\n * @returns {number}\n */\n getLength() {\n const { specifier, version } = this;\n debug(`snapshot.getLength() specifier: ${specifier} version: ${version}`);\n return core.jsonOpSync(\"op_get_length\", { specifier, version });\n }\n /**\n * @param {ScriptSnapshot} oldSnapshot\n * @returns {ts.TextChangeRange | undefined}\n */\n getChangeRange(oldSnapshot) {\n const { specifier, version } = this;\n const { version: oldVersion } = oldSnapshot;\n const oldLength = oldSnapshot.getLength();\n debug(\n `snapshot.getLength() specifier: ${specifier} oldVersion: ${oldVersion} version: ${version}`,\n );\n return core.jsonOpSync(\n \"op_get_change_range\",\n { specifier, oldLength, oldVersion, version },\n );\n }\n dispose() {\n const { specifier, version } = this;\n debug(`snapshot.dispose() specifier: ${specifier} version: ${version}`);\n core.jsonOpSync(\"op_dispose\", { specifier, version });\n }\n }\n\n /** @type {ts.CompilerOptions} */\n let compilationSettings = {};\n\n /** @type {ts.LanguageService} */\n let languageService;\n\n /** An object literal of the incremental compiler host, which provides the\n * specific \"bindings\" to the Deno environment that tsc needs to work.\n *\n * @type {ts.CompilerHost & ts.LanguageServiceHost} */\n const host = {\n fileExists(fileName) {\n debug(`host.fileExists(\"${fileName}\")`);\n return false;\n },\n readFile(specifier) {\n debug(`host.readFile(\"${specifier}\")`);\n return core.jsonOpSync(\"op_load\", { specifier }).data;\n },\n getSourceFile(\n specifier,\n languageVersion,\n _onError,\n _shouldCreateNewSourceFile,\n ) {\n debug(\n `host.getSourceFile(\"${specifier}\", ${\n ts.ScriptTarget[languageVersion]\n })`,\n );\n let sourceFile = sourceFileCache.get(specifier);\n if (sourceFile) {\n return sourceFile;\n }\n\n /** @type {{ data: string; hash?: string; scriptKind: ts.ScriptKind }} */\n const { data, hash, scriptKind } = core.jsonOpSync(\n \"op_load\",\n { specifier },\n );\n assert(\n data != null,\n `\"data\" is unexpectedly null for \"${specifier}\".`,\n );\n sourceFile = ts.createSourceFile(\n specifier,\n data,\n languageVersion,\n false,\n scriptKind,\n );\n sourceFile.moduleName = specifier;\n sourceFile.version = hash;\n sourceFileCache.set(specifier, sourceFile);\n return sourceFile;\n },\n getDefaultLibFileName() {\n return `${ASSETS}/lib.esnext.d.ts`;\n },\n getDefaultLibLocation() {\n return ASSETS;\n },\n writeFile(fileName, data, _writeByteOrderMark, _onError, sourceFiles) {\n debug(`host.writeFile(\"${fileName}\")`);\n let maybeSpecifiers;\n if (sourceFiles) {\n maybeSpecifiers = sourceFiles.map((sf) => sf.moduleName);\n }\n return core.jsonOpSync(\n \"op_emit\",\n { maybeSpecifiers, fileName, data },\n );\n },\n getCurrentDirectory() {\n return CACHE;\n },\n getCanonicalFileName(fileName) {\n return fileName;\n },\n useCaseSensitiveFileNames() {\n return true;\n },\n getNewLine() {\n return \"\\n\";\n },\n resolveModuleNames(specifiers, base) {\n debug(`host.resolveModuleNames()`);\n debug(` base: ${base}`);\n debug(` specifiers: ${specifiers.join(\", \")}`);\n /** @type {Array<[string, ts.Extension] | undefined>} */\n const resolved = core.jsonOpSync(\"op_resolve\", {\n specifiers,\n base,\n });\n if (resolved) {\n const result = resolved.map((item) => {\n if (item) {\n const [resolvedFileName, extension] = item;\n return {\n resolvedFileName,\n extension,\n isExternalLibraryImport: false,\n };\n }\n return undefined;\n });\n result.length = specifiers.length;\n return result;\n } else {\n return new Array(specifiers.length);\n }\n },\n createHash(data) {\n return core.jsonOpSync(\"op_create_hash\", { data }).hash;\n },\n\n // LanguageServiceHost\n getCompilationSettings() {\n debug(\"host.getCompilationSettings()\");\n return compilationSettings;\n },\n getScriptFileNames() {\n debug(\"host.getScriptFileNames()\");\n return core.jsonOpSync(\"op_script_names\", undefined);\n },\n getScriptVersion(specifier) {\n debug(`host.getScriptVersion(\"${specifier}\")`);\n const sourceFile = sourceFileCache.get(specifier);\n if (sourceFile) {\n return sourceFile.version ?? \"1\";\n }\n return core.jsonOpSync(\"op_script_version\", { specifier });\n },\n getScriptSnapshot(specifier) {\n debug(`host.getScriptSnapshot(\"${specifier}\")`);\n const sourceFile = sourceFileCache.get(specifier);\n if (sourceFile) {\n return {\n getText(start, end) {\n return sourceFile.text.substring(start, end);\n },\n getLength() {\n return sourceFile.text.length;\n },\n getChangeRange() {\n return undefined;\n },\n };\n }\n /** @type {string | undefined} */\n const version = core.jsonOpSync(\"op_script_version\", { specifier });\n if (version != null) {\n return new ScriptSnapshot(specifier, version);\n }\n return undefined;\n },\n };\n\n /** @type {Array<[string, number]>} */\n const stats = [];\n let statsStart = 0;\n\n function performanceStart() {\n stats.length = 0;\n statsStart = Date.now();\n ts.performance.enable();\n }\n\n /**\n * @param {{ program: ts.Program | ts.EmitAndSemanticDiagnosticsBuilderProgram, fileCount?: number }} options \n */\n function performanceProgram({ program, fileCount }) {\n if (program) {\n if (\"getProgram\" in program) {\n program = program.getProgram();\n }\n stats.push([\"Files\", program.getSourceFiles().length]);\n stats.push([\"Nodes\", program.getNodeCount()]);\n stats.push([\"Identifiers\", program.getIdentifierCount()]);\n stats.push([\"Symbols\", program.getSymbolCount()]);\n stats.push([\"Types\", program.getTypeCount()]);\n stats.push([\"Instantiations\", program.getInstantiationCount()]);\n } else if (fileCount != null) {\n stats.push([\"Files\", fileCount]);\n }\n const programTime = ts.performance.getDuration(\"Program\");\n const bindTime = ts.performance.getDuration(\"Bind\");\n const checkTime = ts.performance.getDuration(\"Check\");\n const emitTime = ts.performance.getDuration(\"Emit\");\n stats.push([\"Parse time\", programTime]);\n stats.push([\"Bind time\", bindTime]);\n stats.push([\"Check time\", checkTime]);\n stats.push([\"Emit time\", emitTime]);\n stats.push(\n [\"Total TS time\", programTime + bindTime + checkTime + emitTime],\n );\n }\n\n function performanceEnd() {\n const duration = Date.now() - statsStart;\n stats.push([\"Compile time\", duration]);\n return stats;\n }\n\n /**\n * @typedef {object} Request\n * @property {Record} config\n * @property {boolean} debug\n * @property {string[]} rootNames\n */\n\n /** The API that is called by Rust when executing a request.\n * @param {Request} request\n */\n function exec({ config, debug: debugFlag, rootNames }) {\n setLogDebug(debugFlag, \"TS\");\n performanceStart();\n debug(\">>> exec start\", { rootNames });\n debug(config);\n\n const { options, errors: configFileParsingDiagnostics } = ts\n .convertCompilerOptionsFromJson(config, \"\");\n // The `allowNonTsExtensions` is a \"hidden\" compiler option used in VSCode\n // which is not allowed to be passed in JSON, we need it to allow special\n // URLs which Deno supports. So we need to either ignore the diagnostic, or\n // inject it ourselves.\n Object.assign(options, { allowNonTsExtensions: true });\n const program = ts.createIncrementalProgram({\n rootNames,\n options,\n host,\n configFileParsingDiagnostics,\n });\n\n const { diagnostics: emitDiagnostics } = program.emit();\n\n const diagnostics = [\n ...program.getConfigFileParsingDiagnostics(),\n ...program.getSyntacticDiagnostics(),\n ...program.getOptionsDiagnostics(),\n ...program.getGlobalDiagnostics(),\n ...program.getSemanticDiagnostics(),\n ...emitDiagnostics,\n ].filter(({ code }) => !IGNORED_DIAGNOSTICS.includes(code));\n performanceProgram({ program });\n\n core.jsonOpSync(\"op_respond\", {\n diagnostics: fromTypeScriptDiagnostic(diagnostics),\n stats: performanceEnd(),\n });\n debug(\"<<< exec stop\");\n }\n\n /**\n * @param {number} id \n * @param {any} data \n */\n function respond(id, data = null) {\n core.jsonOpSync(\"op_respond\", { id, data });\n }\n\n /**\n * @param {LanguageServerRequest} request \n */\n function serverRequest({ id, ...request }) {\n debug(`serverRequest()`, { id, ...request });\n switch (request.method) {\n case \"configure\": {\n const { options, errors } = ts\n .convertCompilerOptionsFromJson(request.compilerOptions, \"\");\n Object.assign(options, { allowNonTsExtensions: true });\n if (errors.length) {\n debug(ts.formatDiagnostics(errors, host));\n }\n compilationSettings = options;\n return respond(id, true);\n }\n case \"getAsset\": {\n const sourceFile = host.getSourceFile(\n request.specifier,\n ts.ScriptTarget.ESNext,\n );\n return respond(id, sourceFile && sourceFile.text);\n }\n case \"getDiagnostics\": {\n try {\n /** @type {Record} */\n const diagnosticMap = {};\n for (const specifier of request.specifiers) {\n diagnosticMap[specifier] = fromTypeScriptDiagnostic([\n ...languageService.getSemanticDiagnostics(specifier),\n ...languageService.getSuggestionDiagnostics(specifier),\n ...languageService.getSyntacticDiagnostics(specifier),\n ].filter(({ code }) => !IGNORED_DIAGNOSTICS.includes(code)));\n }\n return respond(id, diagnosticMap);\n } catch (e) {\n if (\"stack\" in e) {\n error(e.stack);\n } else {\n error(e);\n }\n return respond(id, {});\n }\n }\n case \"getQuickInfo\": {\n return respond(\n id,\n languageService.getQuickInfoAtPosition(\n request.specifier,\n request.position,\n ),\n );\n }\n case \"getCompletions\": {\n return respond(\n id,\n languageService.getCompletionsAtPosition(\n request.specifier,\n request.position,\n request.preferences,\n ),\n );\n }\n case \"getDocumentHighlights\": {\n return respond(\n id,\n languageService.getDocumentHighlights(\n request.specifier,\n request.position,\n request.filesToSearch,\n ),\n );\n }\n case \"getReferences\": {\n return respond(\n id,\n languageService.getReferencesAtPosition(\n request.specifier,\n request.position,\n ),\n );\n }\n case \"getDefinition\": {\n return respond(\n id,\n languageService.getDefinitionAndBoundSpan(\n request.specifier,\n request.position,\n ),\n );\n }\n case \"getImplementation\": {\n return respond(\n id,\n languageService.getImplementationAtPosition(\n request.specifier,\n request.position,\n ),\n );\n }\n case \"findRenameLocations\": {\n return respond(\n id,\n languageService.findRenameLocations(\n request.specifier,\n request.position,\n request.findInStrings,\n request.findInComments,\n request.providePrefixAndSuffixTextForRename,\n ),\n );\n }\n default:\n throw new TypeError(\n // @ts-ignore exhausted case statement sets type to never\n `Invalid request method for request: \"${request.method}\" (${id})`,\n );\n }\n }\n\n /** @param {{ debug: boolean; }} init */\n function serverInit({ debug: debugFlag }) {\n if (hasStarted) {\n throw new Error(\"The language server has already been initialized.\");\n }\n hasStarted = true;\n languageService = ts.createLanguageService(host);\n core.ops();\n setLogDebug(debugFlag, \"TSLS\");\n debug(\"serverInit()\");\n }\n\n let hasStarted = false;\n\n /** Startup the runtime environment, setting various flags.\n * @param {{ debugFlag?: boolean; legacyFlag?: boolean; }} msg\n */\n function startup({ debugFlag = false }) {\n if (hasStarted) {\n throw new Error(\"The compiler runtime already started.\");\n }\n hasStarted = true;\n core.ops();\n setLogDebug(!!debugFlag, \"TS\");\n }\n\n // Setup the compiler runtime during the build process.\n core.ops();\n core.registerErrorClass(\"Error\", Error);\n\n // A build time only op that provides some setup information that is used to\n // ensure the snapshot is setup properly.\n /** @type {{ buildSpecifier: string; libs: string[] }} */\n const { buildSpecifier, libs } = core.jsonOpSync(\"op_build_info\", {});\n for (const lib of libs) {\n const specifier = `lib.${lib}.d.ts`;\n // we are using internal APIs here to \"inject\" our custom libraries into\n // tsc, so things like `\"lib\": [ \"deno.ns\" ]` are supported.\n if (!ts.libs.includes(lib)) {\n ts.libs.push(lib);\n ts.libMap.set(lib, `lib.${lib}.d.ts`);\n }\n // we are caching in memory common type libraries that will be re-used by\n // tsc on when the snapshot is restored\n assert(\n host.getSourceFile(`${ASSETS}${specifier}`, ts.ScriptTarget.ESNext),\n );\n }\n // this helps ensure as much as possible is in memory that is re-usable\n // before the snapshotting is done, which helps unsure fast \"startup\" for\n // subsequent uses of tsc in Deno.\n const TS_SNAPSHOT_PROGRAM = ts.createProgram({\n rootNames: [buildSpecifier],\n options: SNAPSHOT_COMPILE_OPTIONS,\n host,\n });\n ts.getPreEmitDiagnostics(TS_SNAPSHOT_PROGRAM);\n\n // exposes the two functions that are called by `tsc::exec()` when type\n // checking TypeScript.\n globalThis.startup = startup;\n globalThis.exec = exec;\n\n // exposes the functions that are called when the compiler is used as a\n // language service.\n globalThis.serverInit = serverInit;\n globalThis.serverRequest = serverRequest;\n})(this);\n" + } +} diff --git a/cli/tests/lsp/did_open_params_semantic_tokens.json b/cli/tests/lsp/did_open_params_semantic_tokens.json new file mode 100644 index 0000000000..5cf48ae051 --- /dev/null +++ b/cli/tests/lsp/did_open_params_semantic_tokens.json @@ -0,0 +1,8 @@ +{ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "enum Values { value1, value2 }\n\nasync function baz(s: string): Promise {\n const r = s.slice(0);\n return r;\n}\n\ninterface IFoo {\n readonly x: number;\n foo(): boolean;\n}\n\nclass Bar implements IFoo {\n constructor(public readonly x: number) { }\n foo() { return true; }\n static staticBar = new Bar(0);\n private static getStaticBar() { return Bar.staticBar; }\n}\n" + } +} diff --git a/cli/tests/lsp/document_symbol_did_open_notification.json b/cli/tests/lsp/document_symbol_did_open_notification.json deleted file mode 100644 index 31c37c152b..0000000000 --- a/cli/tests/lsp/document_symbol_did_open_notification.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "jsonrpc": "2.0", - "method": "textDocument/didOpen", - "params": { - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "interface IFoo {\n foo(): boolean;\n}\n\nclass Bar implements IFoo {\n constructor(public x: number) { }\n foo() { return true; }\n /** @deprecated */\n baz() { return false; }\n get value(): number { return 0; }\n set value(newVavlue: number) { return; }\n static staticBar = new Bar(0);\n private static getStaticBar() { return Bar.staticBar; }\n}\n\nenum Values { value1, value2 }\n\nvar bar: IFoo = new Bar(3);" - } - } -} diff --git a/cli/tests/lsp/document_symbol_request.json b/cli/tests/lsp/document_symbol_request.json deleted file mode 100644 index a31317fc35..0000000000 --- a/cli/tests/lsp/document_symbol_request.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": 2, - "method": "textDocument/documentSymbol", - "params": { - "textDocument": { - "uri": "file:///a/file.ts" - } - } -} diff --git a/cli/tests/lsp/document_symbol_response.json b/cli/tests/lsp/document_symbol_response.json new file mode 100644 index 0000000000..89d56ef707 --- /dev/null +++ b/cli/tests/lsp/document_symbol_response.json @@ -0,0 +1,371 @@ +[ + { + "name": "bar", + "kind": 13, + "range": { + "start": { + "line": 17, + "character": 4 + }, + "end": { + "line": 17, + "character": 26 + } + }, + "selectionRange": { + "start": { + "line": 17, + "character": 4 + }, + "end": { + "line": 17, + "character": 7 + } + } + }, + { + "name": "Bar", + "kind": 5, + "range": { + "start": { + "line": 4, + "character": 0 + }, + "end": { + "line": 13, + "character": 1 + } + }, + "selectionRange": { + "start": { + "line": 4, + "character": 6 + }, + "end": { + "line": 4, + "character": 9 + } + }, + "children": [ + { + "name": "constructor", + "kind": 9, + "range": { + "start": { + "line": 5, + "character": 2 + }, + "end": { + "line": 5, + "character": 35 + } + }, + "selectionRange": { + "start": { + "line": 5, + "character": 2 + }, + "end": { + "line": 5, + "character": 35 + } + } + }, + { + "name": "baz", + "kind": 6, + "tags": [ + 1 + ], + "range": { + "start": { + "line": 8, + "character": 2 + }, + "end": { + "line": 8, + "character": 25 + } + }, + "selectionRange": { + "start": { + "line": 8, + "character": 2 + }, + "end": { + "line": 8, + "character": 5 + } + } + }, + { + "name": "foo", + "kind": 6, + "range": { + "start": { + "line": 6, + "character": 2 + }, + "end": { + "line": 6, + "character": 24 + } + }, + "selectionRange": { + "start": { + "line": 6, + "character": 2 + }, + "end": { + "line": 6, + "character": 5 + } + } + }, + { + "name": "getStaticBar", + "kind": 6, + "range": { + "start": { + "line": 12, + "character": 2 + }, + "end": { + "line": 12, + "character": 57 + } + }, + "selectionRange": { + "start": { + "line": 12, + "character": 17 + }, + "end": { + "line": 12, + "character": 29 + } + } + }, + { + "name": "staticBar", + "kind": 7, + "range": { + "start": { + "line": 11, + "character": 2 + }, + "end": { + "line": 11, + "character": 32 + } + }, + "selectionRange": { + "start": { + "line": 11, + "character": 9 + }, + "end": { + "line": 11, + "character": 18 + } + } + }, + { + "name": "value", + "kind": 7, + "range": { + "start": { + "line": 9, + "character": 2 + }, + "end": { + "line": 9, + "character": 35 + } + }, + "selectionRange": { + "start": { + "line": 9, + "character": 6 + }, + "end": { + "line": 9, + "character": 11 + } + } + }, + { + "name": "value", + "kind": 7, + "range": { + "start": { + "line": 10, + "character": 2 + }, + "end": { + "line": 10, + "character": 42 + } + }, + "selectionRange": { + "start": { + "line": 10, + "character": 6 + }, + "end": { + "line": 10, + "character": 11 + } + } + }, + { + "name": "x", + "kind": 7, + "range": { + "start": { + "line": 5, + "character": 14 + }, + "end": { + "line": 5, + "character": 30 + } + }, + "selectionRange": { + "start": { + "line": 5, + "character": 21 + }, + "end": { + "line": 5, + "character": 22 + } + } + } + ] + }, + { + "name": "IFoo", + "kind": 11, + "range": { + "start": { + "line": 0, + "character": 0 + }, + "end": { + "line": 2, + "character": 1 + } + }, + "selectionRange": { + "start": { + "line": 0, + "character": 10 + }, + "end": { + "line": 0, + "character": 14 + } + }, + "children": [ + { + "name": "foo", + "kind": 6, + "range": { + "start": { + "line": 1, + "character": 2 + }, + "end": { + "line": 1, + "character": 17 + } + }, + "selectionRange": { + "start": { + "line": 1, + "character": 2 + }, + "end": { + "line": 1, + "character": 5 + } + } + } + ] + }, + { + "name": "Values", + "kind": 10, + "range": { + "start": { + "line": 15, + "character": 0 + }, + "end": { + "line": 15, + "character": 30 + } + }, + "selectionRange": { + "start": { + "line": 15, + "character": 5 + }, + "end": { + "line": 15, + "character": 11 + } + }, + "children": [ + { + "name": "value1", + "kind": 13, + "range": { + "start": { + "line": 15, + "character": 14 + }, + "end": { + "line": 15, + "character": 20 + } + }, + "selectionRange": { + "start": { + "line": 15, + "character": 14 + }, + "end": { + "line": 15, + "character": 20 + } + } + }, + { + "name": "value2", + "kind": 13, + "range": { + "start": { + "line": 15, + "character": 22 + }, + "end": { + "line": 15, + "character": 28 + } + }, + "selectionRange": { + "start": { + "line": 15, + "character": 22 + }, + "end": { + "line": 15, + "character": 28 + } + } + } + ] + } +] diff --git a/cli/tests/lsp/exit_notification.json b/cli/tests/lsp/exit_notification.json deleted file mode 100644 index 799a0d1d53..0000000000 --- a/cli/tests/lsp/exit_notification.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "jsonrpc": "2.0", - "method": "exit", - "params": null -} diff --git a/cli/tests/lsp/folding_range_did_open_notification.json b/cli/tests/lsp/folding_range_did_open_notification.json deleted file mode 100644 index 938d99751d..0000000000 --- a/cli/tests/lsp/folding_range_did_open_notification.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "jsonrpc": "2.0", - "method": "textDocument/didOpen", - "params": { - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "// #region 1\n/*\n * Some comment\n */\nclass Foo {\n bar(a, b) {\n if (a === b) {\n return true;\n }\n return false;\n }\n}\n// #endregion" - } - } -} diff --git a/cli/tests/lsp/folding_range_request.json b/cli/tests/lsp/folding_range_request.json deleted file mode 100644 index e82b6ec0bb..0000000000 --- a/cli/tests/lsp/folding_range_request.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": 2, - "method": "textDocument/foldingRange", - "params": { - "textDocument": { - "uri": "file:///a/file.ts" - } - } -} diff --git a/cli/tests/lsp/formatting_mbc_response.json b/cli/tests/lsp/formatting_mbc_response.json new file mode 100644 index 0000000000..1c0b9f8e89 --- /dev/null +++ b/cli/tests/lsp/formatting_mbc_response.json @@ -0,0 +1,54 @@ +[ + { + "range": { + "start": { + "line": 0, + "character": 12 + }, + "end": { + "line": 0, + "character": 13 + } + }, + "newText": "\"" + }, + { + "range": { + "start": { + "line": 0, + "character": 21 + }, + "end": { + "line": 0, + "character": 22 + } + }, + "newText": "\";" + }, + { + "range": { + "start": { + "line": 1, + "character": 12 + }, + "end": { + "line": 1, + "character": 13 + } + }, + "newText": "\"" + }, + { + "range": { + "start": { + "line": 1, + "character": 23 + }, + "end": { + "line": 1, + "character": 25 + } + }, + "newText": "\");" + } +] diff --git a/cli/tests/lsp/formatting_request_mbc_fmt.json b/cli/tests/lsp/formatting_request_mbc_fmt.json deleted file mode 100644 index f20cc18e69..0000000000 --- a/cli/tests/lsp/formatting_request_mbc_fmt.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": 2, - "method": "textDocument/formatting", - "params": { - "textDocument": { - "uri": "file:///a/file.ts" - }, - "options": { - "tabSize": 2, - "insertSpaces": true - } - } -} diff --git a/cli/tests/lsp/hover_request.json b/cli/tests/lsp/hover_request.json deleted file mode 100644 index f12bd52df6..0000000000 --- a/cli/tests/lsp/hover_request.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": 2, - "method": "textDocument/hover", - "params": { - "textDocument": { - "uri": "file:///a/file.ts" - }, - "position": { - "line": 0, - "character": 19 - } - } -} diff --git a/cli/tests/lsp/hover_request_asset.json b/cli/tests/lsp/hover_request_asset.json deleted file mode 100644 index 2ae96acd5f..0000000000 --- a/cli/tests/lsp/hover_request_asset.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": 5, - "method": "textDocument/hover", - "params": { - "textDocument": { - "uri": "deno:/asset//lib.es2015.symbol.wellknown.d.ts" - }, - "position": { - "line": 109, - "character": 13 - } - } -} diff --git a/cli/tests/lsp/hover_request_large_01.json b/cli/tests/lsp/hover_request_large_01.json deleted file mode 100644 index 78ddee5a33..0000000000 --- a/cli/tests/lsp/hover_request_large_01.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": 2, - "method": "textDocument/hover", - "params": { - "textDocument": { - "uri": "file:///a/file.ts" - }, - "position": { - "line": 421, - "character": 30 - } - } -} diff --git a/cli/tests/lsp/hover_request_large_02.json b/cli/tests/lsp/hover_request_large_02.json deleted file mode 100644 index 4d7eae1509..0000000000 --- a/cli/tests/lsp/hover_request_large_02.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": 2, - "method": "textDocument/hover", - "params": { - "textDocument": { - "uri": "file:///a/file.ts" - }, - "position": { - "line": 444, - "character": 6 - } - } -} diff --git a/cli/tests/lsp/hover_request_large_03.json b/cli/tests/lsp/hover_request_large_03.json deleted file mode 100644 index 5309450dd8..0000000000 --- a/cli/tests/lsp/hover_request_large_03.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": 2, - "method": "textDocument/hover", - "params": { - "textDocument": { - "uri": "file:///a/file.ts" - }, - "position": { - "line": 461, - "character": 34 - } - } -} diff --git a/cli/tests/lsp/hover_request_mbc.json b/cli/tests/lsp/hover_request_mbc.json deleted file mode 100644 index 6e7c55e085..0000000000 --- a/cli/tests/lsp/hover_request_mbc.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": 2, - "method": "textDocument/hover", - "params": { - "textDocument": { - "uri": "file:///a/file.ts" - }, - "position": { - "line": 2, - "character": 15 - } - } -} diff --git a/cli/tests/lsp/incoming_calls_params.json b/cli/tests/lsp/incoming_calls_params.json new file mode 100644 index 0000000000..6b38d26ee5 --- /dev/null +++ b/cli/tests/lsp/incoming_calls_params.json @@ -0,0 +1,28 @@ +{ + "item": { + "name": "baz", + "kind": 6, + "detail": "Bar", + "uri": "file:///a/file.ts", + "range": { + "start": { + "line": 5, + "character": 2 + }, + "end": { + "line": 7, + "character": 3 + } + }, + "selectionRange": { + "start": { + "line": 5, + "character": 2 + }, + "end": { + "line": 5, + "character": 5 + } + } + } +} diff --git a/cli/tests/lsp/incoming_calls_request.json b/cli/tests/lsp/incoming_calls_request.json deleted file mode 100644 index 47af92c1b1..0000000000 --- a/cli/tests/lsp/incoming_calls_request.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": 4, - "method": "callHierarchy/incomingCalls", - "params": { - "item": { - "name": "baz", - "kind": 6, - "detail": "Bar", - "uri": "file:///a/file.ts", - "range": { - "start": { - "line": 5, - "character": 2 - }, - "end": { - "line": 7, - "character": 3 - } - }, - "selectionRange": { - "start": { - "line": 5, - "character": 2 - }, - "end": { - "line": 5, - "character": 5 - } - } - } - } -} diff --git a/cli/tests/lsp/incoming_calls_response.json b/cli/tests/lsp/incoming_calls_response.json new file mode 100644 index 0000000000..231919a8c8 --- /dev/null +++ b/cli/tests/lsp/incoming_calls_response.json @@ -0,0 +1,42 @@ +[ + { + "from": { + "name": "main", + "kind": 12, + "detail": "", + "uri": "file:///a/file.ts", + "range": { + "start": { + "line": 10, + "character": 0 + }, + "end": { + "line": 13, + "character": 1 + } + }, + "selectionRange": { + "start": { + "line": 10, + "character": 9 + }, + "end": { + "line": 10, + "character": 13 + } + } + }, + "fromRanges": [ + { + "start": { + "line": 12, + "character": 6 + }, + "end": { + "line": 12, + "character": 9 + } + } + ] + } +] diff --git a/cli/tests/lsp/initialize_params.json b/cli/tests/lsp/initialize_params.json new file mode 100644 index 0000000000..b00e5720dd --- /dev/null +++ b/cli/tests/lsp/initialize_params.json @@ -0,0 +1,56 @@ +{ + "processId": 0, + "clientInfo": { + "name": "test-harness", + "version": "1.0.0" + }, + "rootUri": null, + "initializationOptions": { + "enable": true, + "codeLens": { + "implementations": true, + "references": true + }, + "importMap": null, + "lint": true, + "suggest": { + "autoImports": true, + "completeFunctionCalls": false, + "names": true, + "paths": true, + "imports": { + "hosts": {} + } + }, + "unstable": false + }, + "capabilities": { + "textDocument": { + "codeAction": { + "codeActionLiteralSupport": { + "codeActionKind": { + "valueSet": [ + "quickfix" + ] + } + }, + "isPreferredSupport": true, + "dataSupport": true, + "resolveSupport": { + "properties": [ + "edit" + ] + } + }, + "foldingRange": { + "lineFoldingOnly": true + }, + "synchronization": { + "dynamicRegistration": true, + "willSave": true, + "willSaveWaitUntil": true, + "didSave": true + } + } + } +} diff --git a/cli/tests/lsp/initialize_params_disabled.json b/cli/tests/lsp/initialize_params_disabled.json new file mode 100644 index 0000000000..ea12dfac58 --- /dev/null +++ b/cli/tests/lsp/initialize_params_disabled.json @@ -0,0 +1,56 @@ +{ + "processId": 0, + "clientInfo": { + "name": "test-harness", + "version": "1.0.0" + }, + "rootUri": null, + "initializationOptions": { + "enable": false, + "codeLens": { + "implementations": true, + "references": true + }, + "importMap": null, + "lint": true, + "suggest": { + "autoImports": true, + "completeFunctionCalls": false, + "names": true, + "paths": true, + "imports": { + "hosts": {} + } + }, + "unstable": false + }, + "capabilities": { + "textDocument": { + "codeAction": { + "codeActionLiteralSupport": { + "codeActionKind": { + "valueSet": [ + "quickfix" + ] + } + }, + "isPreferredSupport": true, + "dataSupport": true, + "resolveSupport": { + "properties": [ + "edit" + ] + } + }, + "foldingRange": { + "lineFoldingOnly": true + }, + "synchronization": { + "dynamicRegistration": true, + "willSave": true, + "willSaveWaitUntil": true, + "didSave": true + } + } + } +} diff --git a/cli/tests/lsp/initialize_params_registry.json b/cli/tests/lsp/initialize_params_registry.json new file mode 100644 index 0000000000..38777b3d48 --- /dev/null +++ b/cli/tests/lsp/initialize_params_registry.json @@ -0,0 +1,58 @@ +{ + "processId": 0, + "clientInfo": { + "name": "test-harness", + "version": "1.0.0" + }, + "rootUri": null, + "initializationOptions": { + "enable": true, + "codeLens": { + "implementations": true, + "references": true + }, + "importMap": null, + "lint": true, + "suggest": { + "autoImports": true, + "completeFunctionCalls": false, + "names": true, + "paths": true, + "imports": { + "hosts": { + "http://localhost:4545/": true + } + } + }, + "unstable": false + }, + "capabilities": { + "textDocument": { + "codeAction": { + "codeActionLiteralSupport": { + "codeActionKind": { + "valueSet": [ + "quickfix" + ] + } + }, + "isPreferredSupport": true, + "dataSupport": true, + "resolveSupport": { + "properties": [ + "edit" + ] + } + }, + "foldingRange": { + "lineFoldingOnly": true + }, + "synchronization": { + "dynamicRegistration": true, + "willSave": true, + "willSaveWaitUntil": true, + "didSave": true + } + } + } +} diff --git a/cli/tests/lsp/initialize_params_unstable.json b/cli/tests/lsp/initialize_params_unstable.json new file mode 100644 index 0000000000..fd8ccdd83a --- /dev/null +++ b/cli/tests/lsp/initialize_params_unstable.json @@ -0,0 +1,56 @@ +{ + "processId": 0, + "clientInfo": { + "name": "test-harness", + "version": "1.0.0" + }, + "rootUri": null, + "initializationOptions": { + "enable": true, + "codeLens": { + "implementations": true, + "references": true + }, + "importMap": null, + "lint": true, + "suggest": { + "autoImports": true, + "completeFunctionCalls": false, + "names": true, + "paths": true, + "imports": { + "hosts": {} + } + }, + "unstable": true + }, + "capabilities": { + "textDocument": { + "codeAction": { + "codeActionLiteralSupport": { + "codeActionKind": { + "valueSet": [ + "quickfix" + ] + } + }, + "isPreferredSupport": true, + "dataSupport": true, + "resolveSupport": { + "properties": [ + "edit" + ] + } + }, + "foldingRange": { + "lineFoldingOnly": true + }, + "synchronization": { + "dynamicRegistration": true, + "willSave": true, + "willSaveWaitUntil": true, + "didSave": true + } + } + } +} diff --git a/cli/tests/lsp/initialize_request.json b/cli/tests/lsp/initialize_request.json deleted file mode 100644 index a6610ffe95..0000000000 --- a/cli/tests/lsp/initialize_request.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": 1, - "method": "initialize", - "params": { - "processId": 0, - "clientInfo": { - "name": "test-harness", - "version": "1.0.0" - }, - "rootUri": null, - "initializationOptions": { - "enable": true, - "codeLens": { - "implementations": true, - "references": true - }, - "importMap": null, - "lint": true, - "suggest": { - "autoImports": true, - "completeFunctionCalls": false, - "names": true, - "paths": true, - "imports": { - "hosts": {} - } - }, - "unstable": false - }, - "capabilities": { - "textDocument": { - "codeAction": { - "codeActionLiteralSupport": { - "codeActionKind": { - "valueSet": [ - "quickfix" - ] - } - }, - "isPreferredSupport": true, - "dataSupport": true, - "resolveSupport": { - "properties": [ - "edit" - ] - } - }, - "foldingRange": { - "lineFoldingOnly": true - }, - "synchronization": { - "dynamicRegistration": true, - "willSave": true, - "willSaveWaitUntil": true, - "didSave": true - } - } - } - } -} diff --git a/cli/tests/lsp/initialize_request_disabled.json b/cli/tests/lsp/initialize_request_disabled.json deleted file mode 100644 index f763375f88..0000000000 --- a/cli/tests/lsp/initialize_request_disabled.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": 1, - "method": "initialize", - "params": { - "processId": 0, - "clientInfo": { - "name": "test-harness", - "version": "1.0.0" - }, - "rootUri": null, - "initializationOptions": { - "enable": false, - "lint": true, - "importMap": null, - "unstable": false - }, - "capabilities": { - "textDocument": { - "synchronization": { - "dynamicRegistration": true, - "willSave": true, - "willSaveWaitUntil": true, - "didSave": true - } - } - } - } -} diff --git a/cli/tests/lsp/initialize_request_registry.json b/cli/tests/lsp/initialize_request_registry.json deleted file mode 100644 index 94480934f9..0000000000 --- a/cli/tests/lsp/initialize_request_registry.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": 1, - "method": "initialize", - "params": { - "processId": 0, - "clientInfo": { - "name": "test-harness", - "version": "1.0.0" - }, - "rootUri": null, - "initializationOptions": { - "enable": true, - "codeLens": { - "implementations": true, - "references": true - }, - "importMap": null, - "lint": true, - "suggest": { - "autoImports": true, - "completeFunctionCalls": false, - "names": true, - "paths": true, - "imports": { - "hosts": { - "http://localhost:4545/": true - } - } - }, - "unstable": false - }, - "capabilities": { - "textDocument": { - "codeAction": { - "codeActionLiteralSupport": { - "codeActionKind": { - "valueSet": [ - "quickfix" - ] - } - }, - "isPreferredSupport": true, - "dataSupport": true, - "resolveSupport": { - "properties": [ - "edit" - ] - } - }, - "foldingRange": { - "lineFoldingOnly": true - }, - "synchronization": { - "dynamicRegistration": true, - "willSave": true, - "willSaveWaitUntil": true, - "didSave": true - } - } - } - } -} diff --git a/cli/tests/lsp/initialize_request_unstable.json b/cli/tests/lsp/initialize_request_unstable.json deleted file mode 100644 index 8c086c838b..0000000000 --- a/cli/tests/lsp/initialize_request_unstable.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": 1, - "method": "initialize", - "params": { - "processId": 0, - "clientInfo": { - "name": "test-harness", - "version": "1.0.0" - }, - "rootUri": null, - "initializationOptions": { - "enable": true, - "lint": true, - "unstable": true, - "config": null, - "importMap": null - }, - "capabilities": { - "textDocument": { - "synchronization": { - "dynamicRegistration": true, - "willSave": true, - "willSaveWaitUntil": true, - "didSave": true - } - } - } - } -} diff --git a/cli/tests/lsp/initialized_notification.json b/cli/tests/lsp/initialized_notification.json deleted file mode 100644 index 972f8abc8a..0000000000 --- a/cli/tests/lsp/initialized_notification.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "jsonrpc": "2.0", - "method": "initialized", - "params": {} -} diff --git a/cli/tests/lsp/outgoing_calls_params.json b/cli/tests/lsp/outgoing_calls_params.json new file mode 100644 index 0000000000..6b38d26ee5 --- /dev/null +++ b/cli/tests/lsp/outgoing_calls_params.json @@ -0,0 +1,28 @@ +{ + "item": { + "name": "baz", + "kind": 6, + "detail": "Bar", + "uri": "file:///a/file.ts", + "range": { + "start": { + "line": 5, + "character": 2 + }, + "end": { + "line": 7, + "character": 3 + } + }, + "selectionRange": { + "start": { + "line": 5, + "character": 2 + }, + "end": { + "line": 5, + "character": 5 + } + } + } +} diff --git a/cli/tests/lsp/outgoing_calls_request.json b/cli/tests/lsp/outgoing_calls_request.json deleted file mode 100644 index a8d224ae8e..0000000000 --- a/cli/tests/lsp/outgoing_calls_request.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": 5, - "method": "callHierarchy/outgoingCalls", - "params": { - "item": { - "name": "baz", - "kind": 6, - "detail": "Bar", - "uri": "file:///a/file.ts", - "range": { - "start": { - "line": 5, - "character": 2 - }, - "end": { - "line": 7, - "character": 3 - } - }, - "selectionRange": { - "start": { - "line": 5, - "character": 2 - }, - "end": { - "line": 5, - "character": 5 - } - } - } - } -} diff --git a/cli/tests/lsp/outgoing_calls_response.json b/cli/tests/lsp/outgoing_calls_response.json new file mode 100644 index 0000000000..c7cf85cf81 --- /dev/null +++ b/cli/tests/lsp/outgoing_calls_response.json @@ -0,0 +1,42 @@ +[ + { + "to": { + "name": "foo", + "kind": 12, + "detail": "", + "uri": "file:///a/file.ts", + "range": { + "start": { + "line": 0, + "character": 0 + }, + "end": { + "line": 2, + "character": 1 + } + }, + "selectionRange": { + "start": { + "line": 0, + "character": 9 + }, + "end": { + "line": 0, + "character": 12 + } + } + }, + "fromRanges": [ + { + "start": { + "line": 6, + "character": 11 + }, + "end": { + "line": 6, + "character": 14 + } + } + ] + } +] diff --git a/cli/tests/lsp/performance_request.json b/cli/tests/lsp/performance_request.json deleted file mode 100644 index cb389cb9d3..0000000000 --- a/cli/tests/lsp/performance_request.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": 99, - "method": "deno/performance", - "params": {} -} diff --git a/cli/tests/lsp/prepare_call_hierarchy_did_open_notification.json b/cli/tests/lsp/prepare_call_hierarchy_did_open_notification.json deleted file mode 100644 index a75bd3a53c..0000000000 --- a/cli/tests/lsp/prepare_call_hierarchy_did_open_notification.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "jsonrpc": "2.0", - "method": "textDocument/didOpen", - "params": { - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "function foo() {\n return false;\n}\n\nclass Bar {\n baz() {\n return foo();\n }\n}\n\nfunction main() {\n const bar = new Bar();\n bar.baz();\n}\n\nmain();" - } - } -} diff --git a/cli/tests/lsp/prepare_call_hierarchy_request.json b/cli/tests/lsp/prepare_call_hierarchy_request.json deleted file mode 100644 index 1f469ee8b4..0000000000 --- a/cli/tests/lsp/prepare_call_hierarchy_request.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": 2, - "method": "textDocument/prepareCallHierarchy", - "params": { - "textDocument": { - "uri": "file:///a/file.ts" - }, - "position": { - "line": 5, - "character": 3 - } - } -} diff --git a/cli/tests/lsp/prepare_call_hierarchy_response.json b/cli/tests/lsp/prepare_call_hierarchy_response.json new file mode 100644 index 0000000000..93a7d4f1be --- /dev/null +++ b/cli/tests/lsp/prepare_call_hierarchy_response.json @@ -0,0 +1,28 @@ +[ + { + "name": "baz", + "kind": 6, + "detail": "Bar", + "uri": "file:///a/file.ts", + "range": { + "start": { + "line": 5, + "character": 2 + }, + "end": { + "line": 7, + "character": 3 + } + }, + "selectionRange": { + "start": { + "line": 5, + "character": 2 + }, + "end": { + "line": 5, + "character": 5 + } + } + } +] diff --git a/cli/tests/lsp/references_request_asset.json b/cli/tests/lsp/references_request_asset.json deleted file mode 100644 index 6c2430e505..0000000000 --- a/cli/tests/lsp/references_request_asset.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": 2, - "method": "textDocument/references", - "params": { - "textDocument": { - "uri": "file:///a/file.ts" - }, - "position": { - "line": 0, - "character": 3 - }, - "context": { - "includeDeclaration": true - } - } -} diff --git a/cli/tests/lsp/rename_did_open_notification.json b/cli/tests/lsp/rename_did_open_notification.json deleted file mode 100644 index c6323b7421..0000000000 --- a/cli/tests/lsp/rename_did_open_notification.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "jsonrpc": "2.0", - "method": "textDocument/didOpen", - "params": { - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "let variable = 'a';\nconsole.log(variable);" - } - } -} diff --git a/cli/tests/lsp/rename_request.json b/cli/tests/lsp/rename_request.json deleted file mode 100644 index a317d92572..0000000000 --- a/cli/tests/lsp/rename_request.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": 2, - "method": "textDocument/rename", - "params": { - "textDocument": { - "uri": "file:///a/file.ts" - }, - "position": { - "line": 0, - "character": 4 - }, - "newName": "variable_modified" - } -} diff --git a/cli/tests/lsp/rename_response.json b/cli/tests/lsp/rename_response.json new file mode 100644 index 0000000000..5e0e28e4c0 --- /dev/null +++ b/cli/tests/lsp/rename_response.json @@ -0,0 +1,38 @@ +{ + "documentChanges": [ + { + "textDocument": { + "uri": "file:///a/file.ts", + "version": 1 + }, + "edits": [ + { + "range": { + "start": { + "line": 0, + "character": 4 + }, + "end": { + "line": 0, + "character": 12 + } + }, + "newText": "variable_modified" + }, + { + "range": { + "start": { + "line": 1, + "character": 12 + }, + "end": { + "line": 1, + "character": 20 + } + }, + "newText": "variable_modified" + } + ] + } + ] +} diff --git a/cli/tests/lsp/selection_range_did_open_notification.json b/cli/tests/lsp/selection_range_did_open_notification.json deleted file mode 100644 index a6b3d9d39c..0000000000 --- a/cli/tests/lsp/selection_range_did_open_notification.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "jsonrpc": "2.0", - "method": "textDocument/didOpen", - "params": { - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "class Foo {\n bar(a, b) {\n if (a === b) {\n return true;\n }\n return false;\n }\n}" - } - } -} diff --git a/cli/tests/lsp/selection_range_request.json b/cli/tests/lsp/selection_range_request.json deleted file mode 100644 index 5125fa6a02..0000000000 --- a/cli/tests/lsp/selection_range_request.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": 2, - "method": "textDocument/selectionRange", - "params": { - "textDocument": { - "uri": "file:///a/file.ts" - }, - "positions": [ - { - "line": 2, - "character": 8 - } - ] - } -} diff --git a/cli/tests/lsp/selection_range_response.json b/cli/tests/lsp/selection_range_response.json new file mode 100644 index 0000000000..b5eef5ddce --- /dev/null +++ b/cli/tests/lsp/selection_range_response.json @@ -0,0 +1,86 @@ +[ + { + "range": { + "start": { + "line": 2, + "character": 8 + }, + "end": { + "line": 2, + "character": 9 + } + }, + "parent": { + "range": { + "start": { + "line": 2, + "character": 8 + }, + "end": { + "line": 2, + "character": 15 + } + }, + "parent": { + "range": { + "start": { + "line": 2, + "character": 4 + }, + "end": { + "line": 4, + "character": 5 + } + }, + "parent": { + "range": { + "start": { + "line": 1, + "character": 13 + }, + "end": { + "line": 6, + "character": 2 + } + }, + "parent": { + "range": { + "start": { + "line": 1, + "character": 2 + }, + "end": { + "line": 6, + "character": 3 + } + }, + "parent": { + "range": { + "start": { + "line": 0, + "character": 11 + }, + "end": { + "line": 7, + "character": 0 + } + }, + "parent": { + "range": { + "start": { + "line": 0, + "character": 0 + }, + "end": { + "line": 7, + "character": 1 + } + } + } + } + } + } + } + } + } +] diff --git a/cli/tests/lsp/semantic_tokens_did_open_notification.json b/cli/tests/lsp/semantic_tokens_did_open_notification.json deleted file mode 100644 index 3bf1f6c448..0000000000 --- a/cli/tests/lsp/semantic_tokens_did_open_notification.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "jsonrpc": "2.0", - "method": "textDocument/didOpen", - "params": { - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "enum Values { value1, value2 }\n\nasync function baz(s: string): Promise {\n const r = s.slice(0);\n return r;\n}\n\ninterface IFoo {\n readonly x: number;\n foo(): boolean;\n}\n\nclass Bar implements IFoo {\n constructor(public readonly x: number) { }\n foo() { return true; }\n static staticBar = new Bar(0);\n private static getStaticBar() { return Bar.staticBar; }\n}\n" - } - } -} diff --git a/cli/tests/lsp/semantic_tokens_full_request.json b/cli/tests/lsp/semantic_tokens_full_request.json deleted file mode 100644 index 748cc43823..0000000000 --- a/cli/tests/lsp/semantic_tokens_full_request.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": 2, - "method": "textDocument/semanticTokens/full", - "params": { - "textDocument": { - "uri": "file:///a/file.ts" - } - } -} diff --git a/cli/tests/lsp/semantic_tokens_range_request.json b/cli/tests/lsp/semantic_tokens_range_request.json deleted file mode 100644 index 3b09944a1d..0000000000 --- a/cli/tests/lsp/semantic_tokens_range_request.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": 4, - "method": "textDocument/semanticTokens/range", - "params": { - "textDocument": { - "uri": "file:///a/file.ts" - }, - "range": { - "start": { - "line": 0, - "character": 0 - }, - "end": { - "line": 6, - "character": 0 - } - } - } -} diff --git a/cli/tests/lsp/shutdown_request.json b/cli/tests/lsp/shutdown_request.json deleted file mode 100644 index fd4d784607..0000000000 --- a/cli/tests/lsp/shutdown_request.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": 3, - "method": "shutdown", - "params": null -} diff --git a/cli/tests/lsp/signature_help_did_change_notification.json b/cli/tests/lsp/signature_help_did_change_notification.json deleted file mode 100644 index f88eaa9ffd..0000000000 --- a/cli/tests/lsp/signature_help_did_change_notification.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "jsonrpc": "2.0", - "method": "textDocument/didChange", - "params": { - "textDocument": { - "uri": "file:///a/file.ts", - "version": 2 - }, - "contentChanges": [ - { - "range": { - "start": { - "line": 9, - "character": 4 - }, - "end": { - "line": 9, - "character": 4 - } - }, - "text": "123, " - } - ] - } -} diff --git a/cli/tests/lsp/signature_help_did_open_notification.json b/cli/tests/lsp/signature_help_did_open_notification.json deleted file mode 100644 index 1ba1f75865..0000000000 --- a/cli/tests/lsp/signature_help_did_open_notification.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "jsonrpc": "2.0", - "method": "textDocument/didOpen", - "params": { - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "/**\n * Adds two numbers.\n * @param a This is a first number.\n * @param b This is a second number.\n */\nfunction add(a: number, b: number) {\n return a + b;\n}\n\nadd(" - } - } -} diff --git a/cli/tests/lsp/signature_help_request_01.json b/cli/tests/lsp/signature_help_request_01.json deleted file mode 100644 index c3e185e08b..0000000000 --- a/cli/tests/lsp/signature_help_request_01.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": 1, - "method": "textDocument/signatureHelp", - "params": { - "textDocument": { - "uri": "file:///a/file.ts" - }, - "position": { - "character": 4, - "line": 9 - }, - "context": { - "triggerKind": 2, - "triggerCharacter": "(", - "isRetrigger": false - } - } -} diff --git a/cli/tests/lsp/signature_help_request_02.json b/cli/tests/lsp/signature_help_request_02.json deleted file mode 100644 index c2a6e0abbb..0000000000 --- a/cli/tests/lsp/signature_help_request_02.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "id": 2, - "jsonrpc": "2.0", - "method": "textDocument/signatureHelp", - "params": { - "textDocument": { - "uri": "file:///a/file.ts" - }, - "position": { - "character": 8, - "line": 9 - } - } -} diff --git a/cli/tests/lsp/virtual_text_document_request.json b/cli/tests/lsp/virtual_text_document_request.json deleted file mode 100644 index 6ffab4a356..0000000000 --- a/cli/tests/lsp/virtual_text_document_request.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": 4, - "method": "deno/virtualTextDocument", - "params": { - "textDocument": { - "uri": "deno:/asset//lib.deno.shared_globals.d.ts" - } - } -} diff --git a/test_util/Cargo.toml b/test_util/Cargo.toml index ddb616ede3..b1750c5995 100644 --- a/test_util/Cargo.toml +++ b/test_util/Cargo.toml @@ -12,6 +12,7 @@ name = "test_server" path = "src/test_server.rs" [dependencies] +anyhow = "1.0.40" async-stream = "0.3.0" bytes = "1.0.1" futures = "0.3.13" @@ -20,6 +21,7 @@ lazy_static = "1.4.0" os_pipe = "0.9.2" regex = "1.4.3" serde = { version = "1.0.125", features = ["derive"] } +serde_json = "1.0.64" tempfile = "3.2.0" tokio = { version = "1.6.0", features = ["full"] } tokio-rustls = "0.22.0" diff --git a/test_util/src/lib.rs b/test_util/src/lib.rs index d2669e42ed..4cfc5cc9c0 100644 --- a/test_util/src/lib.rs +++ b/test_util/src/lib.rs @@ -46,6 +46,8 @@ use tokio_tungstenite::accept_async; #[cfg(unix)] pub use pty; +pub mod lsp; + const PORT: u16 = 4545; const TEST_AUTH_TOKEN: &str = "abcdef123456789"; const REDIRECT_PORT: u16 = 4546; diff --git a/test_util/src/lsp.rs b/test_util/src/lsp.rs new file mode 100644 index 0000000000..52099ebe3f --- /dev/null +++ b/test_util/src/lsp.rs @@ -0,0 +1,270 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +use super::new_deno_dir; + +use lazy_static::lazy_static; +use regex::Regex; +use serde::de; +use serde::Deserialize; +use serde::Serialize; +use serde_json::json; +use serde_json::Value; +use std::io; +use std::io::Write; +use std::path::Path; +use std::process::Child; +use std::process::ChildStdin; +use std::process::ChildStdout; +use std::process::Command; +use std::process::Stdio; +use std::time::Duration; +use std::time::Instant; + +lazy_static! { + static ref CONTENT_TYPE_REG: Regex = + Regex::new(r"(?i)^content-length:\s+(\d+)").unwrap(); +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct LspResponseError { + code: i32, + message: String, + data: Option, +} + +#[derive(Debug)] +pub enum LspMessage { + Notification(String, Option), + Request(u64, String, Option), + Response(u64, Option, Option), +} + +impl<'a> From<&'a [u8]> for LspMessage { + fn from(s: &'a [u8]) -> Self { + let value: Value = serde_json::from_slice(s).unwrap(); + let obj = value.as_object().unwrap(); + if obj.contains_key("id") && obj.contains_key("method") { + let id = obj.get("id").unwrap().as_u64().unwrap(); + let method = obj.get("method").unwrap().as_str().unwrap().to_string(); + Self::Request(id, method, obj.get("params").cloned()) + } else if obj.contains_key("id") { + let id = obj.get("id").unwrap().as_u64().unwrap(); + let maybe_error: Option = obj + .get("error") + .map(|v| serde_json::from_value(v.clone()).unwrap()); + Self::Response(id, obj.get("result").cloned(), maybe_error) + } else { + assert!(obj.contains_key("method")); + let method = obj.get("method").unwrap().as_str().unwrap().to_string(); + Self::Notification(method, obj.get("params").cloned()) + } + } +} + +fn read_message(reader: &mut R) -> Result, anyhow::Error> +where + R: io::Read + io::BufRead, +{ + let mut content_length = 0_usize; + loop { + let mut buf = String::new(); + reader.read_line(&mut buf)?; + if let Some(captures) = CONTENT_TYPE_REG.captures(&buf) { + let content_length_match = captures + .get(1) + .ok_or_else(|| anyhow::anyhow!("missing capture"))?; + content_length = content_length_match.as_str().parse::()?; + } + if &buf == "\r\n" { + break; + } + } + + let mut msg_buf = vec![0_u8; content_length]; + reader.read_exact(&mut msg_buf)?; + Ok(msg_buf) +} + +pub struct LspClient { + reader: io::BufReader, + child: Child, + request_id: u64, + start: Instant, + writer: io::BufWriter, +} + +impl Drop for LspClient { + fn drop(&mut self) { + match self.child.try_wait() { + Ok(None) => { + self.child.kill().unwrap(); + let _ = self.child.wait(); + } + Ok(Some(status)) => panic!("deno lsp exited unexpectedly {}", status), + Err(e) => panic!("pebble error: {}", e), + } + } +} + +impl LspClient { + pub fn new(deno_exe: &Path) -> Result { + let deno_dir = new_deno_dir(); + let mut child = Command::new(deno_exe) + .env("DENO_DIR", deno_dir.path()) + .arg("lsp") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::null()) + .spawn()?; + + let stdout = child.stdout.take().unwrap(); + let reader = io::BufReader::new(stdout); + + let stdin = child.stdin.take().unwrap(); + let writer = io::BufWriter::new(stdin); + + Ok(Self { + child, + reader, + request_id: 1, + start: Instant::now(), + writer, + }) + } + + pub fn duration(&self) -> Duration { + self.start.elapsed() + } + + fn read(&mut self) -> Result { + let msg_buf = read_message(&mut self.reader)?; + let msg = LspMessage::from(msg_buf.as_slice()); + Ok(msg) + } + + pub fn read_notification( + &mut self, + ) -> Result<(String, Option), anyhow::Error> + where + R: de::DeserializeOwned, + { + loop { + if let LspMessage::Notification(method, maybe_params) = self.read()? { + if let Some(p) = maybe_params { + let params = serde_json::from_value(p)?; + return Ok((method, Some(params))); + } else { + return Ok((method, None)); + } + } + } + } + + pub fn read_request( + &mut self, + ) -> Result<(u64, String, Option), anyhow::Error> + where + R: de::DeserializeOwned, + { + loop { + if let LspMessage::Request(id, method, maybe_params) = self.read()? { + if let Some(p) = maybe_params { + let params = serde_json::from_value(p)?; + return Ok((id, method, Some(params))); + } else { + return Ok((id, method, None)); + } + } + } + } + + fn write(&mut self, value: Value) -> Result<(), anyhow::Error> { + let value_str = value.to_string(); + let msg = format!( + "Content-Length: {}\r\n\r\n{}", + value_str.as_bytes().len(), + value_str + ); + self.writer.write_all(msg.as_bytes())?; + self.writer.flush()?; + Ok(()) + } + + pub fn write_request( + &mut self, + method: S, + params: V, + ) -> Result<(Option, Option), anyhow::Error> + where + S: AsRef, + V: Serialize, + R: de::DeserializeOwned, + { + let value = json!({ + "jsonrpc": "2.0", + "id": self.request_id, + "method": method.as_ref(), + "params": params, + }); + self.write(value)?; + + loop { + if let LspMessage::Response(id, result, error) = self.read()? { + assert_eq!(id, self.request_id); + self.request_id += 1; + if let Some(r) = result { + let result = serde_json::from_value(r)?; + return Ok((Some(result), error)); + } else { + return Ok((None, error)); + } + } + } + } + + pub fn write_response( + &mut self, + id: u64, + result: V, + ) -> Result<(), anyhow::Error> + where + V: Serialize, + { + let value = json!({ + "jsonrpc": "2.0", + "id": id, + "result": result + }); + self.write(value) + } + + pub fn write_notification( + &mut self, + method: S, + params: V, + ) -> Result<(), anyhow::Error> + where + S: AsRef, + V: Serialize, + { + let value = json!({ + "jsonrpc": "2.0", + "method": method.as_ref(), + "params": params, + }); + self.write(value)?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_read_message() { + let msg = b"content-length: 11\r\n\r\nhello world"; + let mut reader = std::io::Cursor::new(msg); + assert_eq!(read_message(&mut reader).unwrap(), b"hello world"); + } +}