diff options
Diffstat (limited to 'cli/tests/integration_tests_lsp.rs')
-rw-r--r-- | cli/tests/integration_tests_lsp.rs | 1661 |
1 files changed, 1661 insertions, 0 deletions
diff --git a/cli/tests/integration_tests_lsp.rs b/cli/tests/integration_tests_lsp.rs new file mode 100644 index 000000000..6b0f6b792 --- /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<V>(client: &mut LspClient, params: V) +where + V: Serialize, +{ + client + .write_notification("textDocument/didOpen", params) + .unwrap(); + let (method, _) = client.read_notification::<Value>().unwrap(); + assert_eq!(method, "textDocument/publishDiagnostics"); + let (method, _) = client.read_notification::<Value>().unwrap(); + assert_eq!(method, "textDocument/publishDiagnostics"); + let (method, _) = client.read_notification::<Value>().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::<Value>().unwrap(); + assert_eq!(method, "textDocument/publishDiagnostics"); + let (method, _) = client.read_notification::<Value>().unwrap(); + assert_eq!(method, "textDocument/publishDiagnostics"); + let (method, _) = client.read_notification::<Value>().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::<Value>().unwrap(); + assert_eq!(method, "textDocument/publishDiagnostics"); + let (method, _) = client.read_notification::<Value>().unwrap(); + assert_eq!(method, "textDocument/publishDiagnostics"); + let (method, _) = client.read_notification::<Value>().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<lsp::CodeLens>>( + "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<PerformanceAverage>, +} + +#[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); +} |