From d472c48b388992e2e0b059492dddf50400520809 Mon Sep 17 00:00:00 2001 From: Nayeem Rahman Date: Mon, 8 Jul 2024 22:31:27 +0100 Subject: [PATCH] fix(lsp): inherit workspace-root-only fields in members (#24440) --- cli/lsp/config.rs | 63 ++++-- tests/integration/lsp_tests.rs | 402 ++++++++++++++++++++++++++++++++- 2 files changed, 442 insertions(+), 23 deletions(-) diff --git a/cli/lsp/config.rs b/cli/lsp/config.rs index 3fded336b6..4812739309 100644 --- a/cli/lsp/config.rs +++ b/cli/lsp/config.rs @@ -1299,16 +1299,27 @@ impl ConfigData { } }; - let vendor_dir = config_file.as_ref().and_then(|c| { - if c.vendor() == Some(true) { - Some(c.specifier.to_file_path().ok()?.parent()?.join("vendor")) - } else { - None - } - }); + let vendor_dir = if let Some(workspace_root) = workspace_root { + workspace_root.vendor_dir.clone() + } else { + config_file.as_ref().and_then(|c| { + if c.vendor() == Some(true) { + Some(c.specifier.to_file_path().ok()?.parent()?.join("vendor")) + } else { + None + } + }) + }; // Load lockfile - let lockfile = config_file.as_ref().and_then(resolve_lockfile_from_config); + let lockfile = if let Some(workspace_root) = workspace_root { + workspace_root.lockfile.clone() + } else { + config_file + .as_ref() + .and_then(resolve_lockfile_from_config) + .map(Arc::new) + }; if let Some(lockfile) = &lockfile { if let Ok(specifier) = ModuleSpecifier::from_file_path(&lockfile.filename) { @@ -1376,23 +1387,31 @@ impl ConfigData { }) .map(|(r, _)| r) .ok(); - let byonm = std::env::var("DENO_UNSTABLE_BYONM").is_ok() - || config_file - .as_ref() - .map(|c| c.has_unstable("byonm")) - .unwrap_or(false) - || (*DENO_FUTURE - && package_json.is_some() - && config_file + let byonm = if let Some(workspace_root) = workspace_root { + workspace_root.byonm + } else { + std::env::var("DENO_UNSTABLE_BYONM").is_ok() + || config_file .as_ref() - .map(|c| c.json.node_modules_dir.is_none()) - .unwrap_or(true)); + .map(|c| c.has_unstable("byonm")) + .unwrap_or(false) + || (*DENO_FUTURE + && package_json.is_some() + && config_file + .as_ref() + .map(|c| c.json.node_modules_dir.is_none()) + .unwrap_or(true)) + }; if byonm { lsp_log!(" Enabled 'bring your own node_modules'."); } - let node_modules_dir = config_file - .as_ref() - .and_then(|c| resolve_node_modules_dir(c, byonm)); + let node_modules_dir = if let Some(workspace_root) = workspace_root { + workspace_root.node_modules_dir.clone() + } else { + config_file + .as_ref() + .and_then(|c| resolve_node_modules_dir(c, byonm)) + }; // Load import map let mut import_map = None; @@ -1547,7 +1566,7 @@ impl ConfigData { byonm, node_modules_dir, vendor_dir, - lockfile: lockfile.map(Arc::new), + lockfile, package_json: package_json.map(Arc::new), npmrc, import_map, diff --git a/tests/integration/lsp_tests.rs b/tests/integration/lsp_tests.rs index f66fc97bed..2111c6f076 100644 --- a/tests/integration/lsp_tests.rs +++ b/tests/integration/lsp_tests.rs @@ -12366,7 +12366,7 @@ fn lsp_deno_json_scopes_import_map() { } #[test] -fn lsp_deno_json_scopes_vendor_dirs() { +fn lsp_deno_json_scopes_vendor_dir() { let context = TestContextBuilder::new() .use_http_server() .use_temp_cwd() @@ -12550,6 +12550,194 @@ fn lsp_deno_json_scopes_vendor_dirs() { client.shutdown(); } +#[test] +fn lsp_deno_json_scopes_node_modules_dir() { + let context = TestContextBuilder::new() + .use_http_server() + .use_temp_cwd() + .build(); + let temp_dir = context.temp_dir(); + temp_dir.create_dir_all("project1"); + temp_dir.create_dir_all("project2/project3"); + temp_dir.write( + "project1/deno.json", + json!({ + "nodeModulesDir": true, + }) + .to_string(), + ); + temp_dir.write( + "project2/deno.json", + json!({ + "nodeModulesDir": true, + }) + .to_string(), + ); + temp_dir.write( + "project2/project3/deno.json", + json!({ + "nodeModulesDir": true, + }) + .to_string(), + ); + let mut client = context.new_lsp_command().build(); + client.initialize_default(); + client.did_open(json!({ + "textDocument": { + "uri": temp_dir.uri().join("project1/file.ts").unwrap(), + "languageId": "typescript", + "version": 1, + "text": "import \"npm:@denotest/add@1\";\n", + }, + })); + client.write_request( + "workspace/executeCommand", + json!({ + "command": "deno.cache", + "arguments": [[], temp_dir.uri().join("project1/file.ts").unwrap()], + }), + ); + let res = client.write_request( + "textDocument/definition", + json!({ + "textDocument": { + "uri": temp_dir.uri().join("project1/file.ts").unwrap(), + }, + "position": { "line": 0, "character": 7 }, + }), + ); + // The temp dir is symlinked in macos, and `node_modules` is canonicalized. + let canon_temp_dir = + Url::from_directory_path(temp_dir.path().canonicalize()).unwrap(); + assert_eq!( + res, + json!([{ + "targetUri": canon_temp_dir.join("project1/node_modules/.deno/@denotest+add@1.0.0/node_modules/@denotest/add/index.d.ts").unwrap(), + "targetRange": { + "start": { + "line": 0, + "character": 0, + }, + "end": { + "line": 1, + "character": 0, + }, + }, + "targetSelectionRange": { + "start": { + "line": 0, + "character": 0, + }, + "end": { + "line": 1, + "character": 0, + }, + }, + }]), + ); + client.did_open(json!({ + "textDocument": { + "uri": temp_dir.uri().join("project2/file.ts").unwrap(), + "languageId": "typescript", + "version": 1, + "text": "import \"npm:@denotest/add@1\";\n", + }, + })); + client.write_request( + "workspace/executeCommand", + json!({ + "command": "deno.cache", + "arguments": [[], temp_dir.uri().join("project2/file.ts").unwrap()], + }), + ); + let res = client.write_request( + "textDocument/definition", + json!({ + "textDocument": { + "uri": temp_dir.uri().join("project2/file.ts").unwrap(), + }, + "position": { "line": 0, "character": 7 }, + }), + ); + assert_eq!( + res, + json!([{ + "targetUri": canon_temp_dir.join("project2/node_modules/.deno/@denotest+add@1.0.0/node_modules/@denotest/add/index.d.ts").unwrap(), + "targetRange": { + "start": { + "line": 0, + "character": 0, + }, + "end": { + "line": 1, + "character": 0, + }, + }, + "targetSelectionRange": { + "start": { + "line": 0, + "character": 0, + }, + "end": { + "line": 1, + "character": 0, + }, + }, + }]), + ); + client.did_open(json!({ + "textDocument": { + "uri": temp_dir.uri().join("project2/project3/file.ts").unwrap(), + "languageId": "typescript", + "version": 1, + "text": "import \"npm:@denotest/add@1\";\n", + }, + })); + client.write_request( + "workspace/executeCommand", + json!({ + "command": "deno.cache", + "arguments": [[], temp_dir.uri().join("project2/project3/file.ts").unwrap()], + }), + ); + let res = client.write_request( + "textDocument/definition", + json!({ + "textDocument": { + "uri": temp_dir.uri().join("project2/project3/file.ts").unwrap(), + }, + "position": { "line": 0, "character": 7 }, + }), + ); + assert_eq!( + res, + json!([{ + "targetUri": canon_temp_dir.join("project2/project3/node_modules/.deno/@denotest+add@1.0.0/node_modules/@denotest/add/index.d.ts").unwrap(), + "targetRange": { + "start": { + "line": 0, + "character": 0, + }, + "end": { + "line": 1, + "character": 0, + }, + }, + "targetSelectionRange": { + "start": { + "line": 0, + "character": 0, + }, + "end": { + "line": 1, + "character": 0, + }, + }, + }]), + ); + client.shutdown(); +} + #[test] fn lsp_deno_json_scopes_ts_config() { let context = TestContextBuilder::new().use_temp_cwd().build(); @@ -13369,6 +13557,218 @@ fn lsp_deno_json_workspace_import_map() { client.shutdown(); } +#[test] +fn lsp_workspace_lockfile() { + let context = TestContextBuilder::new() + .use_http_server() + .use_temp_cwd() + .build(); + let temp_dir = context.temp_dir(); + temp_dir.create_dir_all("project1/project2"); + temp_dir.write( + "project1/deno.json", + json!({ + "workspace": ["project2"], + }) + .to_string(), + ); + temp_dir.write("project1/deno.lock", json!({ + "version": "3", + "redirects": { + "http://localhost:4545/subdir/mod1.ts": "http://localhost:4545/subdir/mod2.ts", + }, + "remote": {}, + }).to_string()); + temp_dir.write("project1/project2/deno.json", json!({}).to_string()); + let mut client = context.new_lsp_command().build(); + client.initialize_default(); + client.did_open(json!({ + "textDocument": { + "uri": temp_dir.uri().join("project1/project2/file.ts").unwrap(), + "languageId": "typescript", + "version": 1, + "text": "import \"http://localhost:4545/subdir/mod1.ts\";\n", + }, + })); + client.write_request( + "workspace/executeCommand", + json!({ + "command": "deno.cache", + "arguments": [[], temp_dir.uri().join("project1/project2/file.ts").unwrap()], + }), + ); + client.read_diagnostics(); + let res = client.write_request( + "textDocument/definition", + json!({ + "textDocument": { "uri": temp_dir.uri().join("project1/project2/file.ts").unwrap() }, + "position": { "line": 0, "character": 7 }, + }), + ); + assert_eq!( + res, + json!([{ + "targetUri": "deno:/http/localhost%3A4545/subdir/mod2.ts", + "targetRange": { + "start": { "line": 0, "character": 0 }, + "end": { "line": 1, "character": 0 }, + }, + "targetSelectionRange": { + "start": { "line": 0, "character": 0 }, + "end": { "line": 1, "character": 0 }, + }, + }]), + ); + client.shutdown(); +} + +#[test] +fn lsp_deno_json_workspace_vendor_dir() { + let context = TestContextBuilder::new() + .use_http_server() + .use_temp_cwd() + .build(); + let temp_dir = context.temp_dir(); + temp_dir.create_dir_all("project1/project2"); + temp_dir.write( + "project1/deno.json", + json!({ + "workspace": ["project2"], + "vendor": true, + }) + .to_string(), + ); + temp_dir.write("project1/project2/deno.json", json!({}).to_string()); + let mut client = context.new_lsp_command().build(); + client.initialize_default(); + client.did_open(json!({ + "textDocument": { + "uri": temp_dir.uri().join("project1/project2/file.ts").unwrap(), + "languageId": "typescript", + "version": 1, + "text": "import \"http://localhost:4545/subdir/mod1.ts\";\n", + }, + })); + client.write_request( + "workspace/executeCommand", + json!({ + "command": "deno.cache", + "arguments": [[], temp_dir.uri().join("project1/project2/file.ts").unwrap()], + }), + ); + let res = client.write_request( + "textDocument/definition", + json!({ + "textDocument": { + "uri": temp_dir.uri().join("project1/project2/file.ts").unwrap(), + }, + "position": { "line": 0, "character": 7 }, + }), + ); + assert_eq!( + res, + json!([{ + "targetUri": temp_dir.uri().join("project1/vendor/http_localhost_4545/subdir/mod1.ts").unwrap(), + "targetRange": { + "start": { + "line": 0, + "character": 0, + }, + "end": { + "line": 17, + "character": 0, + }, + }, + "targetSelectionRange": { + "start": { + "line": 0, + "character": 0, + }, + "end": { + "line": 17, + "character": 0, + }, + }, + }]), + ); + client.shutdown(); +} + +#[test] +fn lsp_deno_json_workspace_node_modules_dir() { + let context = TestContextBuilder::new() + .use_http_server() + .use_temp_cwd() + .build(); + let temp_dir = context.temp_dir(); + temp_dir.create_dir_all("project1/project2"); + temp_dir.write( + "project1/deno.json", + json!({ + "workspace": ["project2"], + "nodeModulesDir": true, + }) + .to_string(), + ); + temp_dir.write("project1/project2/deno.json", json!({}).to_string()); + let mut client = context.new_lsp_command().build(); + client.initialize_default(); + client.did_open(json!({ + "textDocument": { + "uri": temp_dir.uri().join("project1/project2/file.ts").unwrap(), + "languageId": "typescript", + "version": 1, + "text": "import \"npm:@denotest/add@1\";\n", + }, + })); + client.write_request( + "workspace/executeCommand", + json!({ + "command": "deno.cache", + "arguments": [[], temp_dir.uri().join("project1/project2/file.ts").unwrap()], + }), + ); + let res = client.write_request( + "textDocument/definition", + json!({ + "textDocument": { + "uri": temp_dir.uri().join("project1/project2/file.ts").unwrap(), + }, + "position": { "line": 0, "character": 7 }, + }), + ); + // The temp dir is symlinked in macos, and `node_modules` is canonicalized. + let canon_temp_dir = + Url::from_directory_path(temp_dir.path().canonicalize()).unwrap(); + assert_eq!( + res, + json!([{ + "targetUri": canon_temp_dir.join("project1/node_modules/.deno/@denotest+add@1.0.0/node_modules/@denotest/add/index.d.ts").unwrap(), + "targetRange": { + "start": { + "line": 0, + "character": 0, + }, + "end": { + "line": 1, + "character": 0, + }, + }, + "targetSelectionRange": { + "start": { + "line": 0, + "character": 0, + }, + "end": { + "line": 1, + "character": 0, + }, + }, + }]), + ); + client.shutdown(); +} + #[test] fn lsp_deno_json_workspace_jsr_resolution() { let context = TestContextBuilder::new().use_temp_cwd().build();