From cbb78e138ffbfc92ca64f2cce56dd5c629e4f142 Mon Sep 17 00:00:00 2001 From: Nayeem Rahman Date: Tue, 7 May 2024 03:20:15 +0100 Subject: [PATCH] refactor(lsp): reland move resolver fields to LspResolver (#23685) --- cli/jsr.rs | 6 ++ cli/lsp/analysis.rs | 5 +- cli/lsp/diagnostics.rs | 4 +- cli/lsp/documents.rs | 184 ++++++------------------------------- cli/lsp/language_server.rs | 44 ++++----- cli/lsp/resolver.rs | 170 ++++++++++++++++++++++++++++++++-- cli/lsp/tsc.rs | 8 +- 7 files changed, 222 insertions(+), 199 deletions(-) diff --git a/cli/jsr.rs b/cli/jsr.rs index 8b13f58931..c4cb87dbd9 100644 --- a/cli/jsr.rs +++ b/cli/jsr.rs @@ -163,6 +163,12 @@ impl JsrCacheResolver { self.info_by_nv.insert(nv.clone(), info.clone()); info } + + pub fn did_cache(&self) { + self.nv_by_req.retain(|_, nv| nv.is_some()); + self.info_by_nv.retain(|_, info| info.is_some()); + self.info_by_name.retain(|_, info| info.is_some()); + } } fn read_cached_url( diff --git a/cli/lsp/analysis.rs b/cli/lsp/analysis.rs index 63d39ad6e3..23b6bb0999 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 87bb72d1e5..1825a97a42 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 ee7adc83a9..d008dbb74b 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,9 +31,7 @@ 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; use deno_runtime::deno_node::NodeResolution; use deno_runtime::deno_node::NodeResolutionMode; @@ -705,64 +702,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>, @@ -907,21 +846,14 @@ 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, } @@ -934,29 +866,13 @@ 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 @@ -1091,11 +1007,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) } } @@ -1268,7 +1187,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)); @@ -1297,62 +1217,18 @@ 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); @@ -1450,7 +1326,7 @@ impl Documents { } // fill the reqs from the lockfile - if let Some(lockfile) = self.lockfile.as_ref() { + if let Some(lockfile) = self.config.tree.root_lockfile() { let lockfile = lockfile.lock(); for key in lockfile.content.packages.specifiers.keys() { if let Some(key) = key.strip_prefix("npm:") { @@ -1505,19 +1381,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( @@ -1691,20 +1554,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); @@ -1730,7 +1593,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); @@ -1774,7 +1637,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(); @@ -1802,7 +1665,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"); @@ -1851,9 +1714,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( @@ -1895,9 +1763,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 5879a74916..f7b509a1c5 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -316,10 +316,17 @@ impl LanguageServer { // now get the lock back to update with the new information let mut inner = self.0.write().await; - let lockfile = inner.config.tree.root_lockfile().cloned(); - inner.documents.refresh_lockfile(lockfile); + inner.resolver.did_cache(); inner.refresh_npm_specifiers().await; - inner.post_cache(result.mark).await; + inner.diagnostics_server.invalidate_all(); + inner.project_changed([], false); + inner + .ts_server + .cleanup_semantic_cache(inner.snapshot()) + .await; + inner.send_diagnostics_update(); + inner.send_testing_update(); + inner.performance.measure(result.mark); } Ok(Some(json!(true))) } @@ -689,7 +696,6 @@ impl Inner { .clone() .map(|c| c as Arc) .unwrap_or(self.global_cache.clone()); - self.documents.set_cache(self.cache.clone()); self.cache_metadata.set_cache(self.cache.clone()); self.performance.measure(mark); } @@ -781,8 +787,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()) @@ -986,10 +990,14 @@ impl Inner { } } } + } + + async fn refresh_resolver(&mut self) { self.resolver = self .resolver .with_new_config( &self.config, + self.cache.clone(), self.maybe_global_cache_path.as_deref(), Some(&self.http_client), ) @@ -1000,6 +1008,7 @@ impl Inner { self.documents.update_config( &self.config, &self.resolver, + self.cache.clone(), &self.workspace_files, ); @@ -1133,6 +1142,7 @@ impl Inner { self.refresh_workspace_files(); self.refresh_config_tree().await; self.update_cache(); + self.refresh_resolver().await; self.refresh_documents_config().await; self.diagnostics_server.invalidate_all(); self.send_diagnostics_update(); @@ -1181,6 +1191,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 { @@ -1482,10 +1493,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}>)"); } @@ -2961,6 +2969,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(); @@ -3095,6 +3104,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(); @@ -3354,20 +3364,6 @@ impl Inner { })) } - async fn post_cache(&mut self, mark: PerformanceMark) { - // Now that we have dependencies loaded, we need to re-analyze all the files. - // For that we're invalidating all the existing diagnostics and restarting - // the language server for TypeScript (as it might hold to some stale - // documents). - self.diagnostics_server.invalidate_all(); - self.project_changed([], false); - self.ts_server.cleanup_semantic_cache(self.snapshot()).await; - self.send_diagnostics_update(); - self.send_testing_update(); - - self.performance.measure(mark); - } - fn get_performance(&self) -> Value { let averages = self.performance.averages(); json!({ "averages": averages }) diff --git a/cli/lsp/resolver.rs b/cli/lsp/resolver.rs index 076d48bb4d..ebe254b9de 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,19 +149,26 @@ 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(), }) } + pub fn did_cache(&self) { + self.jsr_resolver.as_ref().inspect(|r| r.did_cache()); + } + pub async fn set_npm_package_reqs( &self, reqs: &[PackageReq], @@ -136,10 +189,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 +295,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 +377,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 +397,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 +427,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 61308092ec..bed71f6d92 100644 --- a/cli/lsp/tsc.rs +++ b/cli/lsp/tsc.rs @@ -4130,9 +4130,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()); } } @@ -5110,7 +5110,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,