Repository: denoland/deno
Pull request: fix(lsp): respect lockfile redirects entries for resolution


Changed files:
filename: cli/lsp/resolver.rs
patch: @@ -26,10 +26,12 @@ use dashmap::DashMap;
 use deno_ast::MediaType;
 use deno_cache_dir::HttpCache;
 use deno_core::error::AnyError;
+use deno_core::parking_lot::Mutex;
 use deno_core::url::Url;
 use deno_graph::source::Resolver;
 use deno_graph::GraphImport;
 use deno_graph::ModuleSpecifier;
+use deno_lockfile::Lockfile;
 use deno_npm::NpmSystemInfo;
 use deno_runtime::deno_fs;
 use deno_runtime::deno_node::NodeResolution;
@@ -108,6 +110,7 @@ impl LspScopeResolver {
     )));
     let redirect_resolver = Some(Arc::new(RedirectResolver::new(
       cache.for_specifier(config_data.map(|d| &d.scope)),
+      config_data.and_then(|d| d.lockfile.as_deref()),
     )));
     let npm_graph_resolver = graph_resolver.create_graph_npm_resolver();
     let graph_imports = config_data
@@ -220,6 +223,10 @@ impl LspResolver {
       std::iter::once(&self.unscoped).chain(self.by_scope.values())
     {
       resolver.jsr_resolver.as_ref().inspect(|r| r.did_cache());
+      resolver
+        .redirect_resolver
+        .as_ref()
+        .inspect(|r| r.did_cache());
     }
   }
 
@@ -543,13 +550,36 @@ impl std::fmt::Debug for RedirectResolver {
 }
 
 impl RedirectResolver {
-  fn new(cache: Arc<dyn HttpCache>) -> Self {
+  fn new(
+    cache: Arc<dyn HttpCache>,
+    lockfile: Option<&Mutex<Lockfile>>,
+  ) -> Self {
+    let entries = DashMap::new();
+    if let Some(lockfile) = lockfile {
+      for (source, destination) in &lockfile.lock().content.redirects {
+        let Ok(source) = ModuleSpecifier::parse(source) else {
+          continue;
+        };
+        let Ok(destination) = ModuleSpecifier::parse(destination) else {
+          continue;
+        };
+        entries.insert(
+          source,
+          Some(Arc::new(RedirectEntry {
+            headers: Default::default(),
+            target: destination.clone(),
+            destination: Some(destination.clone()),
+          })),
+        );
+        entries.insert(destination, None);
+      }
+    }
     Self {
       get_headers: Box::new(move |specifier| {
         let cache_key = cache.cache_item_key(specifier).ok()?;
         cache.read_headers(&cache_key).ok().flatten()
       }),
-      entries: Default::default(),
+      entries,
     }
   }
 
@@ -629,6 +659,10 @@ impl RedirectResolver {
     }
     result
   }
+
+  fn did_cache(&self) {
+    self.entries.retain(|_, entry| entry.is_some());
+  }
 }
 
 #[cfg(test)]

filename: tests/integration/lsp_tests.rs
patch: @@ -9188,6 +9188,63 @@ fn lsp_redirect_quick_fix() {
   client.shutdown();
 }
 
+#[test]
+fn lsp_lockfile_redirect_resolution() {
+  let context = TestContextBuilder::new()
+    .use_http_server()
+    .use_temp_cwd()
+    .build();
+  let temp_dir = context.temp_dir();
+  temp_dir.write("deno.json", json!({}).to_string());
+  temp_dir.write("deno.lock", json!({
+    "version": "3",
+    "redirects": {
+      "http://localhost:4545/subdir/mod1.ts": "http://localhost:4545/subdir/mod2.ts",
+    },
+    "remote": {},
+  }).to_string());
+  let mut client = context.new_lsp_command().build();
+  client.initialize_default();
+  client.did_open(json!({
+    "textDocument": {
+      "uri": temp_dir.uri().join("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("file.ts").unwrap()],
+    }),
+  );
+  client.read_diagnostics();
+  let res = client.write_request(
+    "textDocument/definition",
+    json!({
+      "textDocument": { "uri": temp_dir.uri().join("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_diagnostics_deprecated() {
   let context = TestContextBuilder::new().use_temp_cwd().build();