From 5cae3439912ad60eb2866f3d4372a5fe4d0de957 Mon Sep 17 00:00:00 2001 From: Nayeem Rahman Date: Tue, 30 Apr 2024 02:41:19 +0100 Subject: [PATCH] refactor(lsp): move fields from Documents to LspResolver (#23585) --- cli/lsp/analysis.rs | 5 +- cli/lsp/diagnostics.rs | 4 +- cli/lsp/documents.rs | 178 ++++++------------------------------- cli/lsp/language_server.rs | 22 ++--- cli/lsp/resolver.rs | 166 ++++++++++++++++++++++++++++++++-- cli/lsp/tsc.rs | 8 +- 6 files changed, 203 insertions(+), 180 deletions(-) diff --git a/cli/lsp/analysis.rs b/cli/lsp/analysis.rs index 63d39ad6e379d..23b6bb09990ba 100644 --- a/cli/lsp/analysis.rs +++ b/cli/lsp/analysis.rs @@ -259,8 +259,7 @@ impl<'a> TsResponseImportMapper<'a> { let version = Version::parse_standard(segments.next()?).ok()?; let nv = PackageNv { name, version }; let path = segments.collect::>().join("/"); - let jsr_resolver = self.documents.get_jsr_resolver(); - let export = jsr_resolver.lookup_export_for_path(&nv, &path)?; + let export = self.resolver.jsr_lookup_export_for_path(&nv, &path)?; let sub_path = (export != ".").then_some(export); let mut req = None; req = req.or_else(|| { @@ -282,7 +281,7 @@ impl<'a> TsResponseImportMapper<'a> { } None }); - req = req.or_else(|| jsr_resolver.lookup_req_for_nv(&nv)); + req = req.or_else(|| self.resolver.jsr_lookup_req_for_nv(&nv)); let spec_str = if let Some(req) = req { let req_ref = PackageReqReference { req, sub_path }; JsrPackageReqReference::new(req_ref).to_string() diff --git a/cli/lsp/diagnostics.rs b/cli/lsp/diagnostics.rs index 87bb72d1e53d7..1825a97a42c42 100644 --- a/cli/lsp/diagnostics.rs +++ b/cli/lsp/diagnostics.rs @@ -1591,7 +1591,7 @@ mod tests { location.to_path_buf(), RealDenoCacheEnv, )); - let mut documents = Documents::new(cache); + let mut documents = Documents::new(cache.clone()); for (specifier, source, version, language_id) in fixtures { let specifier = resolve_url(specifier).expect("failed to create specifier"); @@ -1614,7 +1614,7 @@ mod tests { config.tree.inject_config_file(config_file).await; } let resolver = LspResolver::default() - .with_new_config(&config, None, None) + .with_new_config(&config, cache, None, None) .await; StateSnapshot { project_version: 0, diff --git a/cli/lsp/documents.rs b/cli/lsp/documents.rs index 71cc63f836065..60b2385cb584d 100644 --- a/cli/lsp/documents.rs +++ b/cli/lsp/documents.rs @@ -12,7 +12,6 @@ use super::tsc::AssetDocument; use crate::cache::HttpCache; use crate::graph_util::CliJsrUrlProvider; -use crate::jsr::JsrCacheResolver; use crate::lsp::logging::lsp_warn; use crate::resolver::SloppyImportsFsEntry; use crate::resolver::SloppyImportsResolution; @@ -32,7 +31,6 @@ use deno_core::futures::FutureExt; use deno_core::parking_lot::Mutex; use deno_core::ModuleSpecifier; use deno_graph::source::ResolutionMode; -use deno_graph::GraphImport; use deno_graph::Resolution; use deno_lockfile::Lockfile; use deno_runtime::deno_node; @@ -716,64 +714,6 @@ pub fn to_lsp_range(range: &deno_graph::Range) -> lsp::Range { } } -#[derive(Debug)] -struct RedirectResolver { - cache: Arc, - redirects: Mutex>, -} - -impl RedirectResolver { - pub fn new(cache: Arc) -> Self { - Self { - cache, - redirects: Mutex::new(HashMap::new()), - } - } - - pub fn resolve( - &self, - specifier: &ModuleSpecifier, - ) -> Option { - let scheme = specifier.scheme(); - if !DOCUMENT_SCHEMES.contains(&scheme) { - return None; - } - - if scheme == "http" || scheme == "https" { - let mut redirects = self.redirects.lock(); - if let Some(specifier) = redirects.get(specifier) { - Some(specifier.clone()) - } else { - let redirect = self.resolve_remote(specifier, 10)?; - redirects.insert(specifier.clone(), redirect.clone()); - Some(redirect) - } - } else { - Some(specifier.clone()) - } - } - - fn resolve_remote( - &self, - specifier: &ModuleSpecifier, - redirect_limit: usize, - ) -> Option { - if redirect_limit > 0 { - let cache_key = self.cache.cache_item_key(specifier).ok()?; - let headers = self.cache.read_headers(&cache_key).ok().flatten()?; - if let Some(location) = headers.get("location") { - let redirect = - deno_core::resolve_import(location, specifier.as_str()).ok()?; - self.resolve_remote(&redirect, redirect_limit - 1) - } else { - Some(specifier.clone()) - } - } else { - None - } - } -} - #[derive(Debug, Default)] struct FileSystemDocuments { docs: DashMap>, @@ -918,21 +858,15 @@ pub struct Documents { open_docs: HashMap>, /// Documents stored on the file system. file_system_docs: Arc, - /// Any imports to the context supplied by configuration files. This is like - /// the imports into the a module graph in CLI. - imports: Arc>, /// A resolver that takes into account currently loaded import map and JSX /// settings. resolver: Arc, - jsr_resolver: Arc, lockfile: Option>>, /// The npm package requirements found in npm specifiers. npm_specifier_reqs: Arc>, /// Gets if any document had a node: specifier such that a @types/node package /// should be injected. has_injected_types_node_package: bool, - /// Resolves a specifier to its final redirected to specifier. - redirect_resolver: Arc, /// If --unstable-sloppy-imports is enabled. unstable_sloppy_imports: bool, } @@ -945,29 +879,14 @@ impl Documents { dirty: true, open_docs: HashMap::default(), file_system_docs: Default::default(), - imports: Default::default(), resolver: Default::default(), - jsr_resolver: Arc::new(JsrCacheResolver::new(cache.clone(), None)), lockfile: None, npm_specifier_reqs: Default::default(), has_injected_types_node_package: false, - redirect_resolver: Arc::new(RedirectResolver::new(cache)), unstable_sloppy_imports: false, } } - pub fn initialize(&mut self, config: &Config) { - self.config = Arc::new(config.clone()); - } - - pub fn module_graph_imports(&self) -> impl Iterator { - self - .imports - .values() - .flat_map(|i| i.dependencies.values()) - .flat_map(|value| value.get_type().or_else(|| value.get_code())) - } - /// "Open" a document from the perspective of the editor, meaning that /// requests for information from the document will come from the in-memory /// representation received from the language server client, versus reading @@ -1102,11 +1021,14 @@ impl Documents { let specifier = if let Ok(jsr_req_ref) = JsrPackageReqReference::from_specifier(specifier) { - Cow::Owned(self.jsr_resolver.jsr_to_registry_url(&jsr_req_ref)?) + Cow::Owned(self.resolver.jsr_to_registry_url(&jsr_req_ref)?) } else { Cow::Borrowed(specifier) }; - self.redirect_resolver.resolve(&specifier) + if !DOCUMENT_SCHEMES.contains(&specifier.scheme()) { + return None; + } + self.resolver.resolve_redirects(&specifier) } } @@ -1279,7 +1201,8 @@ impl Documents { results.push(None); } } else if let Some(specifier) = self - .resolve_imports_dependency(specifier) + .resolver + .resolve_graph_import(specifier) .and_then(|r| r.maybe_specifier()) { results.push(self.resolve_dependency(specifier, referrer)); @@ -1308,62 +1231,19 @@ impl Documents { results } - /// Update the location of the on disk cache for the document store. - pub fn set_cache(&mut self, cache: Arc) { - // TODO update resolved dependencies? - self.cache = cache.clone(); - self.redirect_resolver = Arc::new(RedirectResolver::new(cache)); - self.dirty = true; - } - - pub fn get_jsr_resolver(&self) -> &Arc { - &self.jsr_resolver - } - - pub fn refresh_lockfile(&mut self, lockfile: Option>>) { - self.jsr_resolver = - Arc::new(JsrCacheResolver::new(self.cache.clone(), lockfile.clone())); - self.lockfile = lockfile; - } - pub fn update_config( &mut self, config: &Config, resolver: &Arc, + cache: Arc, workspace_files: &BTreeSet, ) { self.config = Arc::new(config.clone()); + self.cache = cache; let config_data = config.tree.root_data(); let config_file = config_data.and_then(|d| d.config_file.as_deref()); self.resolver = resolver.clone(); - self.jsr_resolver = Arc::new(JsrCacheResolver::new( - self.cache.clone(), - config.tree.root_lockfile().cloned(), - )); self.lockfile = config.tree.root_lockfile().cloned(); - self.redirect_resolver = - Arc::new(RedirectResolver::new(self.cache.clone())); - let graph_resolver = self.resolver.as_graph_resolver(); - let npm_resolver = self.resolver.as_graph_npm_resolver(); - self.imports = Arc::new( - if let Some(Ok(imports)) = config_file.map(|cf| cf.to_maybe_imports()) { - imports - .into_iter() - .map(|(referrer, imports)| { - let graph_import = GraphImport::new( - &referrer, - imports, - &CliJsrUrlProvider, - Some(graph_resolver), - Some(npm_resolver), - ); - (referrer, graph_import) - }) - .collect() - } else { - IndexMap::new() - }, - ); self.unstable_sloppy_imports = config_file .map(|c| c.has_unstable("sloppy-imports")) .unwrap_or(false); @@ -1516,19 +1396,6 @@ impl Documents { Some((doc.specifier().clone(), media_type)) } } - - /// Iterate through any "imported" modules, checking to see if a dependency - /// is available. This is used to provide "global" imports like the JSX import - /// source. - fn resolve_imports_dependency(&self, specifier: &str) -> Option<&Resolution> { - for graph_imports in self.imports.values() { - let maybe_dep = graph_imports.dependencies.get(specifier); - if maybe_dep.is_some() { - return maybe_dep.map(|d| &d.maybe_type); - } - } - None - } } fn node_resolve_npm_req_ref( @@ -1702,20 +1569,20 @@ mod tests { use test_util::PathRef; use test_util::TempDir; - fn setup(temp_dir: &TempDir) -> (Documents, PathRef) { + fn setup(temp_dir: &TempDir) -> (Documents, PathRef, Arc) { let location = temp_dir.path().join("deps"); let cache = Arc::new(GlobalHttpCache::new( location.to_path_buf(), RealDenoCacheEnv, )); - let documents = Documents::new(cache); - (documents, location) + let documents = Documents::new(cache.clone()); + (documents, location, cache) } #[test] fn test_documents_open_close() { let temp_dir = TempDir::new(); - let (mut documents, _) = setup(&temp_dir); + let (mut documents, _, _) = setup(&temp_dir); let specifier = ModuleSpecifier::parse("file:///a.ts").unwrap(); let content = r#"import * as b from "./b.ts"; console.log(b); @@ -1741,7 +1608,7 @@ console.log(b); #[test] fn test_documents_change() { let temp_dir = TempDir::new(); - let (mut documents, _) = setup(&temp_dir); + let (mut documents, _, _) = setup(&temp_dir); let specifier = ModuleSpecifier::parse("file:///a.ts").unwrap(); let content = r#"import * as b from "./b.ts"; console.log(b); @@ -1785,7 +1652,7 @@ console.log(b, "hello deno"); // it should never happen that a user of this API causes this to happen, // but we'll guard against it anyway let temp_dir = TempDir::new(); - let (mut documents, documents_path) = setup(&temp_dir); + let (mut documents, documents_path, _) = setup(&temp_dir); let file_path = documents_path.join("file.ts"); let file_specifier = ModuleSpecifier::from_file_path(&file_path).unwrap(); documents_path.create_dir_all(); @@ -1813,7 +1680,7 @@ console.log(b, "hello deno"); // it should never happen that a user of this API causes this to happen, // but we'll guard against it anyway let temp_dir = TempDir::new(); - let (mut documents, documents_path) = setup(&temp_dir); + let (mut documents, documents_path, cache) = setup(&temp_dir); fs::create_dir_all(&documents_path).unwrap(); let file1_path = documents_path.join("file1.ts"); @@ -1862,9 +1729,14 @@ console.log(b, "hello deno"); .await; let resolver = LspResolver::default() - .with_new_config(&config, None, None) + .with_new_config(&config, cache.clone(), None, None) .await; - documents.update_config(&config, &resolver, &workspace_files); + documents.update_config( + &config, + &resolver, + cache.clone(), + &workspace_files, + ); // open the document let document = documents.open( @@ -1906,9 +1778,9 @@ console.log(b, "hello deno"); .await; let resolver = LspResolver::default() - .with_new_config(&config, None, None) + .with_new_config(&config, cache.clone(), None, None) .await; - documents.update_config(&config, &resolver, &workspace_files); + documents.update_config(&config, &resolver, cache, &workspace_files); // check the document's dependencies let document = documents.get(&file1_specifier).unwrap(); diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index 07d3d8cb78f82..3a378d0ea404e 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -312,9 +312,8 @@ impl LanguageServer { .show_message(MessageType::WARNING, err); } let mut inner = self.0.write().await; - let lockfile = inner.config.tree.root_lockfile().cloned(); - inner.documents.refresh_lockfile(lockfile); - inner.refresh_npm_specifiers().await; + inner.refresh_resolver().await; + inner.refresh_documents_config().await; inner.post_cache(result.mark).await; } Ok(Some(json!(true))) @@ -687,7 +686,6 @@ impl Inner { .map(|c| c as Arc) .unwrap_or(global_cache); self.deps_http_cache = cache.clone(); - self.documents.set_cache(cache.clone()); self.cache_metadata.set_cache(cache); self.url_map.set_cache(maybe_local_cache); self.maybe_global_cache_path = new_cache_path; @@ -810,8 +808,6 @@ impl Inner { self.config.update_capabilities(¶ms.capabilities); } - self.documents.initialize(&self.config); - if let Err(e) = self .ts_server .start(self.config.internal_inspect().to_address()) @@ -1016,10 +1012,14 @@ impl Inner { } } } + } + + async fn refresh_resolver(&mut self) { self.resolver = self .resolver .with_new_config( &self.config, + self.deps_http_cache.clone(), self.maybe_global_cache_path.as_deref(), Some(&self.http_client), ) @@ -1030,6 +1030,7 @@ impl Inner { self.documents.update_config( &self.config, &self.resolver, + self.deps_http_cache.clone(), &self.workspace_files, ); @@ -1169,6 +1170,7 @@ impl Inner { lsp_warn!("Error updating registries: {:#}", err); self.client.show_message(MessageType::WARNING, err); } + self.refresh_resolver().await; self.refresh_documents_config().await; self.diagnostics_server.invalidate_all(); self.send_diagnostics_update(); @@ -1217,6 +1219,7 @@ impl Inner { self.workspace_files_hash = 0; self.refresh_workspace_files(); self.refresh_config_tree().await; + self.refresh_resolver().await; deno_config_changes.extend(changes.iter().filter_map(|(s, e)| { self.config.tree.watched_file_type(s).and_then(|t| { let configuration_type = match t.1 { @@ -1518,10 +1521,7 @@ impl Inner { if let Ok(jsr_req_ref) = JsrPackageReqReference::from_specifier(specifier) { - if let Some(url) = self - .documents - .get_jsr_resolver() - .jsr_to_registry_url(&jsr_req_ref) + if let Some(url) = self.resolver.jsr_to_registry_url(&jsr_req_ref) { result = format!("{result} (<{url}>)"); } @@ -2991,6 +2991,7 @@ impl tower_lsp::LanguageServer for LanguageServer { { let mut ls = self.0.write().await; init_log_file(ls.config.log_file()); + ls.refresh_resolver().await; ls.refresh_documents_config().await; ls.diagnostics_server.invalidate_all(); ls.send_diagnostics_update(); @@ -3125,6 +3126,7 @@ impl tower_lsp::LanguageServer for LanguageServer { let mut ls = self.0.write().await; ls.refresh_workspace_files(); ls.refresh_config_tree().await; + ls.refresh_resolver().await; ls.refresh_documents_config().await; ls.diagnostics_server.invalidate_all(); ls.send_diagnostics_update(); diff --git a/cli/lsp/resolver.rs b/cli/lsp/resolver.rs index 076d48bb4d977..1aa8830722e43 100644 --- a/cli/lsp/resolver.rs +++ b/cli/lsp/resolver.rs @@ -4,7 +4,9 @@ use crate::args::package_json; use crate::args::CacheSetting; use crate::cache::DenoDir; use crate::cache::FastInsecureHasher; +use crate::graph_util::CliJsrUrlProvider; use crate::http_util::HttpClient; +use crate::jsr::JsrCacheResolver; use crate::lsp::config::Config; use crate::lsp::config::ConfigData; use crate::lsp::logging::lsp_warn; @@ -21,10 +23,14 @@ use crate::resolver::CliGraphResolverOptions; use crate::resolver::CliNodeResolver; use crate::util::progress_bar::ProgressBar; use crate::util::progress_bar::ProgressBarStyle; +use deno_cache_dir::HttpCache; use deno_core::error::AnyError; +use deno_core::parking_lot::Mutex; use deno_graph::source::NpmResolver; use deno_graph::source::Resolver; +use deno_graph::GraphImport; use deno_graph::ModuleSpecifier; +use deno_graph::Resolution; use deno_npm::NpmSystemInfo; use deno_runtime::deno_fs; use deno_runtime::deno_node::NodeResolution; @@ -33,9 +39,13 @@ use deno_runtime::deno_node::NodeResolver; use deno_runtime::deno_node::PackageJson; use deno_runtime::fs_util::specifier_to_file_path; use deno_runtime::permissions::PermissionsContainer; +use deno_semver::jsr::JsrPackageReqReference; use deno_semver::npm::NpmPackageReqReference; +use deno_semver::package::PackageNv; use deno_semver::package::PackageReq; +use indexmap::IndexMap; use package_json::PackageJsonDepsProvider; +use std::collections::HashMap; use std::path::Path; use std::rc::Rc; use std::sync::Arc; @@ -43,19 +53,25 @@ use std::sync::Arc; #[derive(Debug, Clone)] pub struct LspResolver { graph_resolver: Arc, + jsr_resolver: Option>, npm_resolver: Option>, node_resolver: Option>, npm_config_hash: LspNpmConfigHash, + redirect_resolver: Option>, + graph_imports: Arc>, config: Arc, } impl Default for LspResolver { fn default() -> Self { Self { - graph_resolver: create_graph_resolver(&Default::default(), None, None), + graph_resolver: create_graph_resolver(None, None, None), + jsr_resolver: None, npm_resolver: None, node_resolver: None, npm_config_hash: LspNpmConfigHash(0), + redirect_resolver: None, + graph_imports: Default::default(), config: Default::default(), } } @@ -65,15 +81,16 @@ impl LspResolver { pub async fn with_new_config( &self, config: &Config, + cache: Arc, global_cache_path: Option<&Path>, http_client: Option<&Arc>, ) -> Arc { let npm_config_hash = LspNpmConfigHash::new(config, global_cache_path); + let config_data = config.tree.root_data(); let mut npm_resolver = None; let mut node_resolver = None; if npm_config_hash != self.npm_config_hash { - if let (Some(http_client), Some(config_data)) = - (http_client, config.tree.root_data()) + if let (Some(http_client), Some(config_data)) = (http_client, config_data) { npm_resolver = create_npm_resolver(config_data, global_cache_path, http_client) @@ -85,15 +102,44 @@ impl LspResolver { node_resolver = self.node_resolver.clone(); } let graph_resolver = create_graph_resolver( - config, + config_data, npm_resolver.as_ref(), node_resolver.as_ref(), ); + let jsr_resolver = Some(Arc::new(JsrCacheResolver::new( + cache.clone(), + config_data.and_then(|d| d.lockfile.clone()), + ))); + let redirect_resolver = Some(Arc::new(RedirectResolver::new(cache))); + let graph_imports = config_data + .and_then(|d| d.config_file.as_ref()) + .and_then(|cf| cf.to_maybe_imports().ok()) + .map(|imports| { + Arc::new( + imports + .into_iter() + .map(|(referrer, imports)| { + let graph_import = GraphImport::new( + &referrer, + imports, + &CliJsrUrlProvider, + Some(graph_resolver.as_ref()), + Some(graph_resolver.as_ref()), + ); + (referrer, graph_import) + }) + .collect(), + ) + }) + .unwrap_or_default(); Arc::new(Self { graph_resolver, + jsr_resolver, npm_resolver, node_resolver, npm_config_hash, + redirect_resolver, + graph_imports, config: Arc::new(config.clone()), }) } @@ -103,15 +149,18 @@ impl LspResolver { self.npm_resolver.as_ref().map(|r| r.clone_snapshotted()); let node_resolver = create_node_resolver(npm_resolver.as_ref()); let graph_resolver = create_graph_resolver( - &self.config, + self.config.tree.root_data(), npm_resolver.as_ref(), node_resolver.as_ref(), ); Arc::new(Self { graph_resolver, + jsr_resolver: self.jsr_resolver.clone(), npm_resolver, node_resolver, npm_config_hash: self.npm_config_hash.clone(), + redirect_resolver: self.redirect_resolver.clone(), + graph_imports: self.graph_imports.clone(), config: self.config.clone(), }) } @@ -136,10 +185,49 @@ impl LspResolver { self.graph_resolver.as_ref() } + pub fn jsr_to_registry_url( + &self, + req_ref: &JsrPackageReqReference, + ) -> Option { + self.jsr_resolver.as_ref()?.jsr_to_registry_url(req_ref) + } + + pub fn jsr_lookup_export_for_path( + &self, + nv: &PackageNv, + path: &str, + ) -> Option { + self.jsr_resolver.as_ref()?.lookup_export_for_path(nv, path) + } + + pub fn jsr_lookup_req_for_nv(&self, nv: &PackageNv) -> Option { + self.jsr_resolver.as_ref()?.lookup_req_for_nv(nv) + } + pub fn maybe_managed_npm_resolver(&self) -> Option<&ManagedCliNpmResolver> { self.npm_resolver.as_ref().and_then(|r| r.as_managed()) } + pub fn graph_import_specifiers( + &self, + ) -> impl Iterator { + self + .graph_imports + .values() + .flat_map(|i| i.dependencies.values()) + .flat_map(|value| value.get_type().or_else(|| value.get_code())) + } + + pub fn resolve_graph_import(&self, specifier: &str) -> Option<&Resolution> { + for graph_imports in self.graph_imports.values() { + let maybe_dep = graph_imports.dependencies.get(specifier); + if maybe_dep.is_some() { + return maybe_dep.map(|d| &d.maybe_type); + } + } + None + } + pub fn in_npm_package(&self, specifier: &ModuleSpecifier) -> bool { if let Some(npm_resolver) = &self.npm_resolver { return npm_resolver.in_npm_package(specifier); @@ -203,6 +291,16 @@ impl LspResolver { node_resolver .get_closest_package_json(referrer, &PermissionsContainer::allow_all()) } + + pub fn resolve_redirects( + &self, + specifier: &ModuleSpecifier, + ) -> Option { + let Some(redirect_resolver) = self.redirect_resolver.as_ref() else { + return Some(specifier.clone()); + }; + redirect_resolver.resolve(specifier) + } } async fn create_npm_resolver( @@ -275,11 +373,10 @@ fn create_node_resolver( } fn create_graph_resolver( - config: &Config, + config_data: Option<&ConfigData>, npm_resolver: Option<&Arc>, node_resolver: Option<&Arc>, ) -> Arc { - let config_data = config.tree.root_data(); let config_file = config_data.and_then(|d| d.config_file.as_deref()); Arc::new(CliGraphResolver::new(CliGraphResolverOptions { node_resolver: node_resolver.cloned(), @@ -296,7 +393,7 @@ fn create_graph_resolver( maybe_import_map: config_data.and_then(|d| d.import_map.clone()), maybe_vendor_dir: config_data.and_then(|d| d.vendor_dir.as_ref()), bare_node_builtins_enabled: config_file - .map(|config| config.has_unstable("bare-node-builtins")) + .map(|cf| cf.has_unstable("bare-node-builtins")) .unwrap_or(false), // Don't set this for the LSP because instead we'll use the OpenDocumentsLoader // because it's much easier and we get diagnostics/quick fixes about a redirected @@ -326,3 +423,56 @@ impl LspNpmConfigHash { Self(hasher.finish()) } } + +#[derive(Debug)] +struct RedirectResolver { + cache: Arc, + redirects: Mutex>, +} + +impl RedirectResolver { + pub fn new(cache: Arc) -> Self { + Self { + cache, + redirects: Mutex::new(HashMap::new()), + } + } + + pub fn resolve( + &self, + specifier: &ModuleSpecifier, + ) -> Option { + if matches!(specifier.scheme(), "http" | "https") { + let mut redirects = self.redirects.lock(); + if let Some(specifier) = redirects.get(specifier) { + Some(specifier.clone()) + } else { + let redirect = self.resolve_remote(specifier, 10)?; + redirects.insert(specifier.clone(), redirect.clone()); + Some(redirect) + } + } else { + Some(specifier.clone()) + } + } + + fn resolve_remote( + &self, + specifier: &ModuleSpecifier, + redirect_limit: usize, + ) -> Option { + if redirect_limit > 0 { + let cache_key = self.cache.cache_item_key(specifier).ok()?; + let headers = self.cache.read_headers(&cache_key).ok().flatten()?; + if let Some(location) = headers.get("location") { + let redirect = + deno_core::resolve_import(location, specifier.as_str()).ok()?; + self.resolve_remote(&redirect, redirect_limit - 1) + } else { + Some(specifier.clone()) + } + } else { + None + } + } +} diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs index 8a20ffb0f7b90..06f618e1fb20c 100644 --- a/cli/lsp/tsc.rs +++ b/cli/lsp/tsc.rs @@ -4124,9 +4124,9 @@ fn op_script_names(state: &mut OpState) -> Vec { } // inject these next because they're global - for import in documents.module_graph_imports() { - if seen.insert(import.as_str()) { - result.push(import.to_string()); + for specifier in state.state_snapshot.resolver.graph_import_specifiers() { + if seen.insert(specifier.as_str()) { + result.push(specifier.to_string()); } } @@ -5095,7 +5095,7 @@ mod tests { ) .await; let resolver = LspResolver::default() - .with_new_config(&config, None, None) + .with_new_config(&config, cache.clone(), None, None) .await; StateSnapshot { project_version: 0,