diff options
Diffstat (limited to 'tests/integration/lsp_tests.rs')
-rw-r--r-- | tests/integration/lsp_tests.rs | 129 |
1 files changed, 129 insertions, 0 deletions
diff --git a/tests/integration/lsp_tests.rs b/tests/integration/lsp_tests.rs index f5d09bc77..9d82e0afd 100644 --- a/tests/integration/lsp_tests.rs +++ b/tests/integration/lsp_tests.rs @@ -9,6 +9,7 @@ use deno_core::url::Url; use pretty_assertions::assert_eq; use std::fs; use test_util::assert_starts_with; +use test_util::assertions::assert_json_subset; use test_util::deno_cmd_with_deno_dir; use test_util::env_vars_for_npm_tests; use test_util::lsp::LspClient; @@ -16,6 +17,65 @@ use test_util::testdata_path; use test_util::TestContextBuilder; use tower_lsp::lsp_types as lsp; +/// Helper to get the `lsp::Range` of the `n`th occurrence of +/// `text` in `src`. `n` is zero-based, like most indexes. +fn range_of_nth( + n: usize, + text: impl AsRef<str>, + src: impl AsRef<str>, +) -> lsp::Range { + let text = text.as_ref(); + + let src = src.as_ref(); + + let start = src + .match_indices(text) + .nth(n) + .map(|(i, _)| i) + .unwrap_or_else(|| panic!("couldn't find text {text} in source {src}")); + let end = start + text.len(); + let mut line = 0; + let mut col = 0; + let mut byte_idx = 0; + + let pos = |line, col| lsp::Position { + line, + character: col, + }; + + let mut start_pos = None; + let mut end_pos = None; + for c in src.chars() { + if byte_idx == start { + start_pos = Some(pos(line, col)); + } + if byte_idx == end { + end_pos = Some(pos(line, col)); + break; + } + if c == '\n' { + line += 1; + col = 0; + } else { + col += c.len_utf16() as u32; + } + byte_idx += c.len_utf8(); + } + if start_pos.is_some() && end_pos.is_none() { + // range extends to end of string + end_pos = Some(pos(line, col)); + } + + let (start, end) = (start_pos.unwrap(), end_pos.unwrap()); + lsp::Range { start, end } +} + +/// Helper to get the `lsp::Range` of the first occurrence of +/// `text` in `src`. Equivalent to `range_of_nth(0, text, src)`. +fn range_of(text: impl AsRef<str>, src: impl AsRef<str>) -> lsp::Range { + range_of_nth(0, text, src) +} + #[test] fn lsp_startup_shutdown() { let context = TestContextBuilder::new().use_temp_cwd().build(); @@ -12548,3 +12608,72 @@ fn lsp_cjs_internal_types_default_export() { // previously, diagnostic about `add` not being callable assert_eq!(json!(diagnostics.all()), json!([])); } + +#[test] +fn lsp_ts_code_fix_any_param() { + let context = TestContextBuilder::new().use_temp_cwd().build(); + let temp_dir = context.temp_dir(); + + let mut client = context.new_lsp_command().build(); + client.initialize_default(); + + let src = "export function foo(param) { console.log(param); }"; + + let param_def = range_of("param", src); + + let main_url = temp_dir.path().join("main.ts").uri_file(); + let diagnostics = client.did_open(json!({ + "textDocument": { + "uri": main_url, + "languageId": "typescript", + "version": 1, + "text": src, + } + })); + // make sure the "implicit any type" diagnostic is there for "param" + assert_json_subset( + json!(diagnostics.all()), + json!([{ + "range": param_def, + "code": 7006, + "message": "Parameter 'param' implicitly has an 'any' type." + }]), + ); + + // response is array of fixes + let response = client.write_request( + "textDocument/codeAction", + json!({ + "textDocument": { + "uri": main_url, + }, + "range": lsp::Range { + start: param_def.end, + ..param_def + }, + "context": { + "diagnostics": diagnostics.all(), + } + }), + ); + let fixes = response.as_array().unwrap(); + + // we're looking for the quick fix that pertains to our diagnostic, + // specifically the "Infer parameter types from usage" fix + for fix in fixes { + let Some(diags) = fix.get("diagnostics") else { + continue; + }; + let Some(fix_title) = fix.get("title").and_then(|s| s.as_str()) else { + continue; + }; + if diags == &json!(diagnostics.all()) + && fix_title == "Infer parameter types from usage" + { + // found it! + return; + } + } + + panic!("failed to find 'Infer parameter types from usage' fix in fixes: {fixes:#?}"); +} |