From f7f9383157edc40cae465334f594c2fc5c96dd40 Mon Sep 17 00:00:00 2001 From: Nayeem Rahman Date: Thu, 9 May 2024 20:22:27 +0100 Subject: [PATCH] refactor(lsp): unify caching into LspCache (#23746) --- cli/cache/deno_dir.rs | 5 +- cli/cache/disk_cache.rs | 2 +- cli/lsp/cache.rs | 136 ++++++++++---------- cli/lsp/completions.rs | 47 +++---- cli/lsp/config.rs | 4 - cli/lsp/diagnostics.rs | 132 ++++++++----------- cli/lsp/documents.rs | 130 ++++++++----------- cli/lsp/language_server.rs | 115 +++++------------ cli/lsp/resolver.rs | 254 +++++++++++++++++++++++++++++-------- cli/lsp/tsc.rs | 114 ++++------------- 10 files changed, 445 insertions(+), 494 deletions(-) diff --git a/cli/cache/deno_dir.rs b/cli/cache/deno_dir.rs index b56dfbc8930df..9f2911f718c45 100644 --- a/cli/cache/deno_dir.rs +++ b/cli/cache/deno_dir.rs @@ -33,11 +33,10 @@ impl DenoDirProvider { /// `DenoDir` serves as coordinator for multiple `DiskCache`s containing them /// in single directory that can be controlled with `$DENO_DIR` env variable. -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct DenoDir { /// Example: /Users/rld/.deno/ - /// Note: This is not exposed in order to encourage using re-usable methods. - root: PathBuf, + pub root: PathBuf, /// Used by TsCompiler to cache compiler output. pub gen_cache: DiskCache, } diff --git a/cli/cache/disk_cache.rs b/cli/cache/disk_cache.rs index cd44dd17a64ff..3aeebbc6d94d3 100644 --- a/cli/cache/disk_cache.rs +++ b/cli/cache/disk_cache.rs @@ -14,7 +14,7 @@ use std::path::PathBuf; use std::path::Prefix; use std::str; -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct DiskCache { pub location: PathBuf, } diff --git a/cli/lsp/cache.rs b/cli/lsp/cache.rs index a1048dace7244..d899cd79644d7 100644 --- a/cli/lsp/cache.rs +++ b/cli/lsp/cache.rs @@ -1,11 +1,16 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +use crate::cache::DenoDir; +use crate::cache::GlobalHttpCache; use crate::cache::HttpCache; +use crate::cache::LocalLspHttpCache; +use crate::lsp::config::Config; +use crate::lsp::logging::lsp_log; +use crate::lsp::logging::lsp_warn; use deno_runtime::fs_util::specifier_to_file_path; -use deno_core::parking_lot::Mutex; +use deno_core::url::Url; use deno_core::ModuleSpecifier; -use std::collections::HashMap; use std::fs; use std::path::Path; use std::sync::Arc; @@ -22,7 +27,7 @@ pub const LSP_DISALLOW_GLOBAL_TO_LOCAL_COPY: deno_cache_dir::GlobalToLocalCopy = deno_cache_dir::GlobalToLocalCopy::Disallow; pub fn calculate_fs_version( - cache: &Arc, + cache: &LspCache, specifier: &ModuleSpecifier, ) -> Option { match specifier.scheme() { @@ -49,13 +54,14 @@ pub fn calculate_fs_version_at_path(path: &Path) -> Option { } fn calculate_fs_version_in_cache( - cache: &Arc, + cache: &LspCache, specifier: &ModuleSpecifier, ) -> Option { - let Ok(cache_key) = cache.cache_item_key(specifier) else { + let http_cache = cache.root_vendor_or_global(); + let Ok(cache_key) = http_cache.cache_item_key(specifier) else { return Some("1".to_string()); }; - match cache.read_modified_time(&cache_key) { + match http_cache.read_modified_time(&cache_key) { Ok(Some(modified)) => { match modified.duration_since(SystemTime::UNIX_EPOCH) { Ok(n) => Some(n.as_millis().to_string()), @@ -67,83 +73,71 @@ fn calculate_fs_version_in_cache( } } -/// Populate the metadata map based on the supplied headers -fn parse_metadata( - headers: &HashMap, -) -> HashMap { - let mut metadata = HashMap::new(); - if let Some(warning) = headers.get("x-deno-warning").cloned() { - metadata.insert(MetadataKey::Warning, warning); - } - metadata -} - -#[derive(Debug, PartialEq, Eq, Hash)] -pub enum MetadataKey { - /// Represent the `x-deno-warning` header associated with the document - Warning, -} - #[derive(Debug, Clone)] -struct Metadata { - values: Arc>, - version: Option, +pub struct LspCache { + deno_dir: DenoDir, + global: Arc, + root_vendor: Option>, } -#[derive(Debug, Clone)] -pub struct CacheMetadata { - cache: Arc, - metadata: Arc>>, +impl Default for LspCache { + fn default() -> Self { + Self::new(None) + } } -impl CacheMetadata { - pub fn new(cache: Arc) -> Self { +impl LspCache { + pub fn new(global_cache_url: Option) -> Self { + let global_cache_path = global_cache_url.and_then(|s| { + specifier_to_file_path(&s) + .inspect(|p| { + lsp_log!("Resolved global cache path: \"{}\"", p.to_string_lossy()); + }) + .inspect_err(|err| { + lsp_warn!("Failed to resolve custom cache path: {err}"); + }) + .ok() + }); + let deno_dir = DenoDir::new(global_cache_path) + .expect("should be infallible with absolute custom root"); + let global = Arc::new(GlobalHttpCache::new( + deno_dir.deps_folder_path(), + crate::cache::RealDenoCacheEnv, + )); Self { - cache, - metadata: Default::default(), + deno_dir, + global, + root_vendor: None, } } - /// Return the meta data associated with the specifier. Unlike the `get()` - /// method, redirects of the supplied specifier will not be followed. - pub fn get( - &self, - specifier: &ModuleSpecifier, - ) -> Option>> { - if matches!( - specifier.scheme(), - "file" | "npm" | "node" | "data" | "blob" - ) { - return None; - } - let version = calculate_fs_version_in_cache(&self.cache, specifier); - let metadata = self.metadata.lock().get(specifier).cloned(); - if metadata.as_ref().and_then(|m| m.version.clone()) != version { - self.refresh(specifier).map(|m| m.values) - } else { - metadata.map(|m| m.values) - } + pub fn update_config(&mut self, config: &Config) { + self.root_vendor = config.tree.root_data().and_then(|data| { + let vendor_dir = data.vendor_dir.as_ref()?; + Some(Arc::new(LocalLspHttpCache::new( + vendor_dir.clone(), + self.global.clone(), + ))) + }); } - fn refresh(&self, specifier: &ModuleSpecifier) -> Option { - if matches!( - specifier.scheme(), - "file" | "npm" | "node" | "data" | "blob" - ) { - return None; - } - let cache_key = self.cache.cache_item_key(specifier).ok()?; - let headers = self.cache.read_headers(&cache_key).ok()??; - let values = Arc::new(parse_metadata(&headers)); - let version = calculate_fs_version_in_cache(&self.cache, specifier); - let mut metadata_map = self.metadata.lock(); - let metadata = Metadata { values, version }; - metadata_map.insert(specifier.clone(), metadata.clone()); - Some(metadata) + pub fn deno_dir(&self) -> &DenoDir { + &self.deno_dir + } + + pub fn global(&self) -> &Arc { + &self.global + } + + pub fn root_vendor(&self) -> Option<&Arc> { + self.root_vendor.as_ref() } - pub fn set_cache(&mut self, cache: Arc) { - self.cache = cache; - self.metadata.lock().clear(); + pub fn root_vendor_or_global(&self) -> Arc { + self + .root_vendor + .as_ref() + .map(|v| v.clone() as _) + .unwrap_or(self.global.clone() as _) } } diff --git a/cli/lsp/completions.rs b/cli/lsp/completions.rs index f9d2316ae52cb..3f63d2857f754 100644 --- a/cli/lsp/completions.rs +++ b/cli/lsp/completions.rs @@ -799,41 +799,39 @@ fn get_workspace_completions( #[cfg(test)] mod tests { use super::*; - use crate::cache::GlobalHttpCache; use crate::cache::HttpCache; + use crate::lsp::cache::LspCache; use crate::lsp::documents::Documents; use crate::lsp::documents::LanguageId; use crate::lsp::search::tests::TestPackageSearchApi; use deno_core::resolve_url; use deno_graph::Range; use std::collections::HashMap; - use std::path::Path; - use std::sync::Arc; use test_util::TempDir; - fn mock_documents( - fixtures: &[(&str, &str, i32, LanguageId)], - source_fixtures: &[(&str, &str)], - location: &Path, + fn setup( + open_sources: &[(&str, &str, i32, LanguageId)], + fs_sources: &[(&str, &str)], ) -> Documents { - let cache = Arc::new(GlobalHttpCache::new( - location.to_path_buf(), - crate::cache::RealDenoCacheEnv, - )); - let mut documents = Documents::new(cache); - for (specifier, source, version, language_id) in fixtures { + let temp_dir = TempDir::new(); + let cache = LspCache::new(Some(temp_dir.uri())); + let mut documents = Documents::default(); + documents.update_config( + &Default::default(), + &Default::default(), + &cache, + &Default::default(), + ); + for (specifier, source, version, language_id) in open_sources { let specifier = resolve_url(specifier).expect("failed to create specifier"); documents.open(specifier, *version, *language_id, (*source).into()); } - let http_cache = GlobalHttpCache::new( - location.to_path_buf(), - crate::cache::RealDenoCacheEnv, - ); - for (specifier, source) in source_fixtures { + for (specifier, source) in fs_sources { let specifier = resolve_url(specifier).expect("failed to create specifier"); - http_cache + cache + .global() .set(&specifier, HashMap::default(), source.as_bytes()) .expect("could not cache file"); assert!( @@ -844,15 +842,6 @@ mod tests { documents } - fn setup( - temp_dir: &TempDir, - documents: &[(&str, &str, i32, LanguageId)], - sources: &[(&str, &str)], - ) -> Documents { - let location = temp_dir.path().join("deps"); - mock_documents(documents, sources, location.as_path()) - } - #[test] fn test_get_relative_specifiers() { let base = resolve_url("file:///a/b/c.ts").unwrap(); @@ -936,9 +925,7 @@ mod tests { character: 21, }, }; - let temp_dir = TempDir::new(); let documents = setup( - &temp_dir, &[ ( "file:///a/b/c.ts", diff --git a/cli/lsp/config.rs b/cli/lsp/config.rs index e1fed5a541bfa..597f45688cabf 100644 --- a/cli/lsp/config.rs +++ b/cli/lsp/config.rs @@ -1454,10 +1454,6 @@ impl ConfigTree { .unwrap_or_default() } - pub fn root_vendor_dir(&self) -> Option<&PathBuf> { - self.root_data().and_then(|d| d.vendor_dir.as_ref()) - } - pub fn root_lockfile(&self) -> Option<&Arc>> { self.root_data().and_then(|d| d.lockfile.as_ref()) } diff --git a/cli/lsp/diagnostics.rs b/cli/lsp/diagnostics.rs index ebd6338cd0df3..edaf30e835d4d 100644 --- a/cli/lsp/diagnostics.rs +++ b/cli/lsp/diagnostics.rs @@ -1,7 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. use super::analysis; -use super::cache; use super::client::Client; use super::config::Config; use super::documents; @@ -1328,17 +1327,18 @@ fn diagnose_resolution( match resolution { Resolution::Ok(resolved) => { let specifier = &resolved.specifier; - // If the module is a remote module and has a `X-Deno-Warning` header, we - // want a warning diagnostic with that message. - if let Some(metadata) = snapshot.cache_metadata.get(specifier) { - if let Some(message) = - metadata.get(&cache::MetadataKey::Warning).cloned() - { - diagnostics.push(DenoDiagnostic::DenoWarn(message)); + let managed_npm_resolver = snapshot.resolver.maybe_managed_npm_resolver(); + for (_, headers) in snapshot.resolver.redirect_chain_headers(specifier) { + if let Some(message) = headers.get("x-deno-warning") { + diagnostics.push(DenoDiagnostic::DenoWarn(message.clone())); } } - let managed_npm_resolver = snapshot.resolver.maybe_managed_npm_resolver(); if let Some(doc) = snapshot.documents.get(specifier) { + if let Some(headers) = doc.maybe_headers() { + if let Some(message) = headers.get("x-deno-warning") { + diagnostics.push(DenoDiagnostic::DenoWarn(message.clone())); + } + } if let Some(diagnostic) = check_redirect_diagnostic(specifier, &doc) { diagnostics.push(diagnostic); } @@ -1563,9 +1563,9 @@ fn generate_deno_diagnostics( #[cfg(test)] mod tests { + use super::*; - use crate::cache::GlobalHttpCache; - use crate::cache::RealDenoCacheEnv; + use crate::lsp::cache::LspCache; use crate::lsp::config::Config; use crate::lsp::config::Settings; use crate::lsp::config::WorkspaceSettings; @@ -1575,57 +1575,9 @@ mod tests { use crate::lsp::resolver::LspResolver; use deno_config::ConfigFile; use pretty_assertions::assert_eq; - use std::path::Path; - use std::path::PathBuf; use std::sync::Arc; use test_util::TempDir; - async fn mock_state_snapshot( - fixtures: &[(&str, &str, i32, LanguageId)], - location: &Path, - maybe_import_map: Option<(&str, &str)>, - ) -> StateSnapshot { - let cache = Arc::new(GlobalHttpCache::new( - location.to_path_buf(), - RealDenoCacheEnv, - )); - 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"); - documents.open( - specifier.clone(), - *version, - *language_id, - (*source).into(), - ); - } - let mut config = Config::new_with_roots([resolve_url("file:///").unwrap()]); - if let Some((base_url, json_string)) = maybe_import_map { - let base_url = resolve_url(base_url).unwrap(); - let config_file = ConfigFile::new( - json_string, - base_url, - &deno_config::ParseOptions::default(), - ) - .unwrap(); - config.tree.inject_config_file(config_file).await; - } - let resolver = LspResolver::default() - .with_new_config(&config, cache, None, None) - .await; - StateSnapshot { - project_version: 0, - documents, - assets: Default::default(), - cache_metadata: cache::CacheMetadata::new(Arc::new( - GlobalHttpCache::new(location.to_path_buf(), RealDenoCacheEnv), - )), - config: Arc::new(config), - resolver, - } - } - fn mock_config() -> Config { let root_uri = resolve_url("file:///").unwrap(); Config { @@ -1649,21 +1601,49 @@ mod tests { } async fn setup( - temp_dir: &TempDir, sources: &[(&str, &str, i32, LanguageId)], maybe_import_map: Option<(&str, &str)>, - ) -> (StateSnapshot, PathBuf) { - let location = temp_dir.path().join("deps").to_path_buf(); - let state_snapshot = - mock_state_snapshot(sources, &location, maybe_import_map).await; - (state_snapshot, location) + ) -> StateSnapshot { + let temp_dir = TempDir::new(); + let cache = LspCache::new(Some(temp_dir.uri())); + let mut config = Config::new_with_roots([resolve_url("file:///").unwrap()]); + if let Some((base_url, json_string)) = maybe_import_map { + let base_url = resolve_url(base_url).unwrap(); + let config_file = ConfigFile::new( + json_string, + base_url, + &deno_config::ParseOptions::default(), + ) + .unwrap(); + config.tree.inject_config_file(config_file).await; + } + let resolver = LspResolver::default() + .with_new_config(&config, &cache, None) + .await; + let mut documents = Documents::default(); + documents.update_config(&config, &resolver, &cache, &Default::default()); + for (specifier, source, version, language_id) in sources { + let specifier = + resolve_url(specifier).expect("failed to create specifier"); + documents.open( + specifier.clone(), + *version, + *language_id, + (*source).into(), + ); + } + StateSnapshot { + project_version: 0, + documents, + assets: Default::default(), + config: Arc::new(config), + resolver, + } } #[tokio::test] async fn test_enabled_then_disabled_specifier() { - let temp_dir = TempDir::new(); - let (snapshot, cache_location) = setup( - &temp_dir, + let snapshot = setup( &[( "file:///a.ts", r#"import * as b from "./b.ts"; @@ -1677,9 +1657,7 @@ let c: number = "a"; ) .await; let snapshot = Arc::new(snapshot); - let cache = - Arc::new(GlobalHttpCache::new(cache_location, RealDenoCacheEnv)); - let ts_server = TsServer::new(Default::default(), cache); + let ts_server = TsServer::new(Default::default()); ts_server.start(None).unwrap(); // test enabled @@ -1757,9 +1735,7 @@ let c: number = "a"; #[tokio::test] async fn test_deno_diagnostics_with_import_map() { - let temp_dir = TempDir::new(); - let (snapshot, _) = setup( - &temp_dir, + let snapshot = setup( &[ ( "file:///std/assert/mod.ts", @@ -1895,9 +1871,7 @@ let c: number = "a"; #[tokio::test] async fn duplicate_diagnostics_for_duplicate_imports() { - let temp_dir = TempDir::new(); - let (snapshot, _) = setup( - &temp_dir, + let snapshot = setup( &[( "file:///a.ts", r#" @@ -1973,9 +1947,7 @@ let c: number = "a"; #[tokio::test] async fn unable_to_load_a_local_module() { - let temp_dir = TempDir::new(); - let (snapshot, _) = setup( - &temp_dir, + let snapshot = setup( &[( "file:///a.ts", r#" diff --git a/cli/lsp/documents.rs b/cli/lsp/documents.rs index 42c67c45d825e..6c7f8433fd849 100644 --- a/cli/lsp/documents.rs +++ b/cli/lsp/documents.rs @@ -1,6 +1,7 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. use super::cache::calculate_fs_version; +use super::cache::LspCache; use super::cache::LSP_DISALLOW_GLOBAL_TO_LOCAL_COPY; use super::config::Config; use super::resolver::LspResolver; @@ -10,7 +11,6 @@ use super::text::LineIndex; use super::tsc; use super::tsc::AssetDocument; -use crate::cache::HttpCache; use crate::graph_util::CliJsrUrlProvider; use crate::lsp::logging::lsp_warn; use deno_graph::source::Resolver; @@ -287,7 +287,7 @@ impl Document { maybe_headers: Option>, resolver: Arc, config: Arc, - cache: &Arc, + cache: &Arc, ) -> Arc { let text_info = SourceTextInfo::new(content); let media_type = resolve_media_type( @@ -507,7 +507,7 @@ impl Document { })) } - pub fn closed(&self, cache: &Arc) -> Arc { + pub fn closed(&self, cache: &Arc) -> Arc { Arc::new(Self { config: self.config.clone(), specifier: self.specifier.clone(), @@ -528,7 +528,7 @@ impl Document { }) } - pub fn saved(&self, cache: &Arc) -> Arc { + pub fn saved(&self, cache: &Arc) -> Arc { Arc::new(Self { config: self.config.clone(), specifier: self.specifier.clone(), @@ -565,6 +565,10 @@ impl Document { self.line_index.clone() } + pub fn maybe_headers(&self) -> Option<&HashMap> { + self.maybe_headers.as_ref() + } + fn maybe_fs_version(&self) -> Option<&str> { self.maybe_fs_version.as_deref() } @@ -712,7 +716,7 @@ impl FileSystemDocuments { specifier: &ModuleSpecifier, resolver: &Arc, config: &Arc, - cache: &Arc, + cache: &Arc, ) -> Option> { let new_fs_version = calculate_fs_version(cache, specifier); let old_doc = self.docs.get(specifier).map(|v| v.value().clone()); @@ -742,7 +746,7 @@ impl FileSystemDocuments { specifier: &ModuleSpecifier, resolver: &Arc, config: &Arc, - cache: &Arc, + cache: &Arc, ) -> Option> { let doc = if specifier.scheme() == "file" { let path = specifier_to_file_path(specifier).ok()?; @@ -775,11 +779,12 @@ impl FileSystemDocuments { cache, ) } else { - let cache_key = cache.cache_item_key(specifier).ok()?; - let bytes = cache + let http_cache = cache.root_vendor_or_global(); + let cache_key = http_cache.cache_item_key(specifier).ok()?; + let bytes = http_cache .read_file_bytes(&cache_key, None, LSP_DISALLOW_GLOBAL_TO_LOCAL_COPY) .ok()??; - let specifier_headers = cache.read_headers(&cache_key).ok()??; + let specifier_headers = http_cache.read_headers(&cache_key).ok()??; let (_, maybe_charset) = deno_graph::source::resolve_media_type_and_charset_from_headers( specifier, @@ -832,10 +837,10 @@ pub enum DocumentsFilter { OpenDiagnosable, } -#[derive(Debug, Clone)] +#[derive(Debug, Default, Clone)] pub struct Documents { /// The DENO_DIR that the documents looks for non-file based modules. - cache: Arc, + cache: Arc, config: Arc, /// A flag that indicates that stated data is potentially invalid and needs to /// be recalculated before being considered valid. @@ -855,19 +860,6 @@ pub struct Documents { } impl Documents { - pub fn new(cache: Arc) -> Self { - Self { - cache: cache.clone(), - config: Default::default(), - dirty: true, - open_docs: HashMap::default(), - file_system_docs: Default::default(), - resolver: Default::default(), - npm_specifier_reqs: Default::default(), - has_injected_types_node_package: false, - } - } - /// "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 @@ -1019,7 +1011,7 @@ impl Documents { .map(|p| p.is_file()) .unwrap_or(false); } - if self.cache.contains(&specifier) { + if self.cache.root_vendor_or_global().contains(&specifier) { return true; } } @@ -1179,11 +1171,11 @@ impl Documents { &mut self, config: &Config, resolver: &Arc, - cache: Arc, + cache: &LspCache, workspace_files: &BTreeSet, ) { self.config = Arc::new(config.clone()); - self.cache = cache; + self.cache = Arc::new(cache.clone()); self.resolver = resolver.clone(); { let fs_docs = &self.file_system_docs; @@ -1461,31 +1453,29 @@ fn analyze_module( #[cfg(test)] mod tests { - use crate::cache::GlobalHttpCache; - use crate::cache::RealDenoCacheEnv; - use super::*; + use crate::lsp::cache::LspCache; use deno_config::ConfigFile; use deno_core::serde_json; use deno_core::serde_json::json; use pretty_assertions::assert_eq; - use test_util::PathRef; use test_util::TempDir; - 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.clone()); - (documents, location, cache) + async fn setup() -> (Documents, LspCache, TempDir) { + let temp_dir = TempDir::new(); + let cache = LspCache::new(Some(temp_dir.uri())); + let config = Config::default(); + let resolver = LspResolver::default() + .with_new_config(&config, &cache, None) + .await; + let mut documents = Documents::default(); + documents.update_config(&config, &resolver, &cache, &Default::default()); + (documents, cache, temp_dir) } - #[test] - fn test_documents_open_close() { - let temp_dir = TempDir::new(); - let (mut documents, _, _) = setup(&temp_dir); + #[tokio::test] + async fn test_documents_open_close() { + let (mut documents, _, _) = setup().await; let specifier = ModuleSpecifier::parse("file:///a.ts").unwrap(); let content = r#"import * as b from "./b.ts"; console.log(b); @@ -1508,10 +1498,9 @@ console.log(b); assert!(document.maybe_lsp_version().is_none()); } - #[test] - fn test_documents_change() { - let temp_dir = TempDir::new(); - let (mut documents, _, _) = setup(&temp_dir); + #[tokio::test] + async fn test_documents_change() { + let (mut documents, _, _) = setup().await; let specifier = ModuleSpecifier::parse("file:///a.ts").unwrap(); let content = r#"import * as b from "./b.ts"; console.log(b); @@ -1550,15 +1539,13 @@ console.log(b, "hello deno"); ); } - #[test] - fn test_documents_ensure_no_duplicates() { + #[tokio::test] + async fn test_documents_ensure_no_duplicates() { // 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 file_path = documents_path.join("file.ts"); - let file_specifier = ModuleSpecifier::from_file_path(&file_path).unwrap(); - documents_path.create_dir_all(); + let (mut documents, _, temp_dir) = setup().await; + let file_path = temp_dir.path().join("file.ts"); + let file_specifier = temp_dir.uri().join("file.ts").unwrap(); file_path.write(""); // open the document @@ -1582,27 +1569,21 @@ console.log(b, "hello deno"); async fn test_documents_refresh_dependencies_config_change() { // 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, cache) = setup(&temp_dir); - fs::create_dir_all(&documents_path).unwrap(); + let (mut documents, cache, temp_dir) = setup().await; - let file1_path = documents_path.join("file1.ts"); - let file1_specifier = ModuleSpecifier::from_file_path(&file1_path).unwrap(); + let file1_path = temp_dir.path().join("file1.ts"); + let file1_specifier = temp_dir.uri().join("file1.ts").unwrap(); fs::write(&file1_path, "").unwrap(); - let file2_path = documents_path.join("file2.ts"); - let file2_specifier = ModuleSpecifier::from_file_path(&file2_path).unwrap(); + let file2_path = temp_dir.path().join("file2.ts"); + let file2_specifier = temp_dir.uri().join("file2.ts").unwrap(); fs::write(&file2_path, "").unwrap(); - let file3_path = documents_path.join("file3.ts"); - let file3_specifier = ModuleSpecifier::from_file_path(&file3_path).unwrap(); + let file3_path = temp_dir.path().join("file3.ts"); + let file3_specifier = temp_dir.uri().join("file3.ts").unwrap(); fs::write(&file3_path, "").unwrap(); - let mut config = - Config::new_with_roots(vec![ModuleSpecifier::from_directory_path( - &documents_path, - ) - .unwrap()]); + let mut config = Config::new_with_roots([temp_dir.uri()]); let workspace_settings = serde_json::from_str(r#"{ "enable": true }"#).unwrap(); config.set_workspace_settings(workspace_settings, vec![]); @@ -1632,14 +1613,9 @@ console.log(b, "hello deno"); .await; let resolver = LspResolver::default() - .with_new_config(&config, cache.clone(), None, None) + .with_new_config(&config, &cache, None) .await; - documents.update_config( - &config, - &resolver, - cache.clone(), - &workspace_files, - ); + documents.update_config(&config, &resolver, &cache, &workspace_files); // open the document let document = documents.open( @@ -1681,9 +1657,9 @@ console.log(b, "hello deno"); .await; let resolver = LspResolver::default() - .with_new_config(&config, cache.clone(), None, None) + .with_new_config(&config, &cache, None) .await; - documents.update_config(&config, &resolver, cache, &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 3d8ee7e8d6731..0c327929b3828 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -42,7 +42,7 @@ use super::analysis::ts_changes_to_edit; use super::analysis::CodeActionCollection; use super::analysis::CodeActionData; use super::analysis::TsResponseImportMapper; -use super::cache; +use super::cache::LspCache; use super::capabilities; use super::client::Client; use super::code_lens; @@ -88,10 +88,6 @@ use crate::args::CaData; use crate::args::CacheSetting; use crate::args::CliOptions; use crate::args::Flags; -use crate::cache::DenoDir; -use crate::cache::GlobalHttpCache; -use crate::cache::HttpCache; -use crate::cache::LocalLspHttpCache; use crate::factory::CliFactory; use crate::file_fetcher::FileFetcher; use crate::graph_util; @@ -121,11 +117,10 @@ impl RootCertStoreProvider for LspRootCertStoreProvider { pub struct LanguageServer(Arc>, CancellationToken); /// Snapshot of the state used by TSC. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub struct StateSnapshot { pub project_version: usize, pub assets: AssetsSnapshot, - pub cache_metadata: cache::CacheMetadata, pub config: Arc, pub documents: Documents, pub resolver: Arc, @@ -174,12 +169,7 @@ pub struct Inner { /// Cached versions of "fixed" assets that can either be inlined in Rust or /// are part of the TypeScript snapshot and have to be fetched out. assets: Assets, - /// This may be a copy of `self.global_cache`, or a vendor dir if one is - /// configured. - cache: Arc, - /// A representation of metadata associated with specifiers in the DENO_DIR - /// or vendor dir which is used by the language server. - cache_metadata: cache::CacheMetadata, + cache: LspCache, /// The LSP client that this LSP server is connected to. pub client: Client, /// Configuration information. @@ -189,16 +179,11 @@ pub struct Inner { /// The collection of documents that the server is currently handling, either /// on disk or "open" within the client. pub documents: Documents, - global_cache: Arc, + http_client: Arc, initial_cwd: PathBuf, jsr_search_api: CliJsrSearchApi, - http_client: Arc, - task_queue: LanguageServerTaskQueue, /// Handles module registries, which allow discovery of modules module_registry: ModuleRegistry, - /// An optional path to the DENO_DIR which has been specified in the client - /// options. - maybe_global_cache_path: Option, /// A lazily create "server" for handling test run requests. maybe_testing_server: Option, npm_search_api: CliNpmSearchApi, @@ -206,6 +191,7 @@ pub struct Inner { /// A collection of measurements which instrument that performance of the LSP. performance: Arc, resolver: Arc, + task_queue: LanguageServerTaskQueue, /// A memoized version of fixable diagnostic codes retrieved from TypeScript. ts_fixable_diagnostics: Vec, /// An abstraction that handles interactions with TypeScript. @@ -450,24 +436,20 @@ impl LanguageServer { impl Inner { fn new(client: Client) -> Self { - let dir = DenoDir::new(None).expect("could not access DENO_DIR"); + let cache = LspCache::default(); let http_client = Arc::new(HttpClient::new(None, None)); - let module_registry = - ModuleRegistry::new(dir.registries_folder_path(), http_client.clone()); + let module_registry = ModuleRegistry::new( + cache.deno_dir().registries_folder_path(), + http_client.clone(), + ); let jsr_search_api = CliJsrSearchApi::new(module_registry.file_fetcher.clone()); let npm_search_api = CliNpmSearchApi::new(module_registry.file_fetcher.clone()); - let global_cache = Arc::new(GlobalHttpCache::new( - dir.deps_folder_path(), - crate::cache::RealDenoCacheEnv, - )); - let cache = global_cache.clone(); - let documents = Documents::new(cache.clone()); - let cache_metadata = cache::CacheMetadata::new(cache.clone()); + let documents = Documents::default(); let performance = Arc::new(Performance::default()); let config = Config::default(); - let ts_server = Arc::new(TsServer::new(performance.clone(), cache.clone())); + let ts_server = Arc::new(TsServer::new(performance.clone())); let diagnostics_state = Arc::new(DiagnosticsState::default()); let diagnostics_server = DiagnosticsServer::new( client.clone(), @@ -483,17 +465,14 @@ impl Inner { Self { assets, cache, - cache_metadata, client, config, diagnostics_state, diagnostics_server, documents, - global_cache, http_client, initial_cwd: initial_cwd.clone(), jsr_search_api, - maybe_global_cache_path: None, project_version: 0, task_queue: Default::default(), maybe_testing_server: None, @@ -598,7 +577,6 @@ impl Inner { Arc::new(StateSnapshot { project_version: self.project_version, assets: self.assets.snapshot(), - cache_metadata: self.cache_metadata.clone(), config: Arc::new(self.config.clone()), documents: self.documents.clone(), resolver: self.resolver.snapshot(), @@ -607,36 +585,21 @@ impl Inner { pub async fn update_global_cache(&mut self) { let mark = self.performance.mark("lsp.update_global_cache"); - let maybe_cache = &self.config.workspace_settings().cache; - self.maybe_global_cache_path = if let Some(cache_str) = maybe_cache { - let cache_url = if let Ok(url) = Url::from_file_path(cache_str) { - Ok(url) + let maybe_cache = self.config.workspace_settings().cache.as_ref(); + let global_cache_url = maybe_cache.and_then(|cache_str| { + if let Ok(url) = Url::from_file_path(cache_str) { + Some(url) } else if let Some(root_uri) = self.config.root_uri() { - root_uri.join(cache_str).map_err(|e| e.into()) + root_uri.join(cache_str).inspect_err(|err| lsp_warn!("Failed to resolve custom cache path: {err}")).ok() } else { - Err(anyhow!( + lsp_warn!( "The configured cache path \"{cache_str}\" is not resolvable outside of a workspace.", - )) - }; - cache_url - .and_then(|s| specifier_to_file_path(&s)) - .inspect(|p| { - lsp_log!("Resolved global cache path: \"{}\"", p.to_string_lossy()); - }) - .inspect_err(|err| { - lsp_warn!("Failed to resolve custom cache path: {err}"); - }) - .ok() - } else { - None - }; - let deno_dir = match DenoDir::new(self.maybe_global_cache_path.clone()) { - Ok(d) => d, - Err(err) => { - lsp_warn!("Couldn't access DENO_DIR: {err}"); - return; + ); + None } - }; + }); + self.cache = LspCache::new(global_cache_url); + let deno_dir = self.cache.deno_dir(); let workspace_settings = self.config.workspace_settings(); let maybe_root_path = self .config @@ -674,28 +637,13 @@ impl Inner { CliJsrSearchApi::new(self.module_registry.file_fetcher.clone()); self.npm_search_api = CliNpmSearchApi::new(self.module_registry.file_fetcher.clone()); - self.global_cache = Arc::new(GlobalHttpCache::new( - deno_dir.deps_folder_path(), - crate::cache::RealDenoCacheEnv, - )); self.performance.measure(mark); } pub fn update_cache(&mut self) { let mark = self.performance.mark("lsp.update_cache"); - let maybe_local_cache = - self.config.tree.root_vendor_dir().map(|local_path| { - Arc::new(LocalLspHttpCache::new( - local_path.clone(), - self.global_cache.clone(), - )) - }); - self.url_map.set_cache(maybe_local_cache.clone()); - self.cache = maybe_local_cache - .clone() - .map(|c| c as Arc) - .unwrap_or(self.global_cache.clone()); - self.cache_metadata.set_cache(self.cache.clone()); + self.cache.update_config(&self.config); + self.url_map.set_cache(self.cache.root_vendor().cloned()); self.performance.measure(mark); } @@ -950,7 +898,7 @@ impl Inner { async fn refresh_config_tree(&mut self) { let mut file_fetcher = FileFetcher::new( - self.global_cache.clone(), + self.cache.global().clone(), CacheSetting::RespectHeaders, true, self.http_client.clone(), @@ -995,12 +943,7 @@ 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), - ) + .with_new_config(&self.config, &self.cache, Some(&self.http_client)) .await; } @@ -1008,7 +951,7 @@ impl Inner { self.documents.update_config( &self.config, &self.resolver, - self.cache.clone(), + &self.cache, &self.workspace_files, ); @@ -3304,7 +3247,7 @@ impl Inner { let workspace_settings = self.config.workspace_settings(); let cli_options = CliOptions::new( Flags { - cache_path: self.maybe_global_cache_path.clone(), + cache_path: Some(self.cache.deno_dir().root.clone()), ca_stores: workspace_settings.certificate_stores.clone(), ca_data: workspace_settings.tls_certificate.clone().map(CaData::File), unsafely_ignore_certificate_errors: workspace_settings diff --git a/cli/lsp/resolver.rs b/cli/lsp/resolver.rs index f336c6b036a04..b42f253c4c434 100644 --- a/cli/lsp/resolver.rs +++ b/cli/lsp/resolver.rs @@ -2,14 +2,12 @@ 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; use crate::npm::create_cli_npm_resolver_for_lsp; use crate::npm::CliNpmResolver; use crate::npm::CliNpmResolverByonmCreateOptions; @@ -25,9 +23,10 @@ use crate::resolver::SloppyImportsFsEntry; use crate::resolver::SloppyImportsResolver; use crate::util::progress_bar::ProgressBar; use crate::util::progress_bar::ProgressBarStyle; +use dashmap::DashMap; 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::NpmResolver; use deno_graph::source::Resolver; use deno_graph::GraphImport; @@ -47,11 +46,14 @@ use deno_semver::package::PackageNv; use deno_semver::package::PackageReq; use indexmap::IndexMap; use package_json::PackageJsonDepsProvider; +use std::borrow::Cow; use std::collections::HashMap; -use std::path::Path; +use std::collections::HashSet; use std::rc::Rc; use std::sync::Arc; +use super::cache::LspCache; + #[derive(Debug, Clone)] pub struct LspResolver { graph_resolver: Arc, @@ -85,11 +87,10 @@ impl LspResolver { pub async fn with_new_config( &self, config: &Config, - cache: Arc, - global_cache_path: Option<&Path>, + cache: &LspCache, http_client: Option<&Arc>, ) -> Arc { - let npm_config_hash = LspNpmConfigHash::new(config, global_cache_path); + let npm_config_hash = LspNpmConfigHash::new(config, cache); let config_data = config.tree.root_data(); let mut npm_resolver = None; let mut node_resolver = None; @@ -97,8 +98,7 @@ impl LspResolver { if let (Some(http_client), Some(config_data)) = (http_client, config_data) { npm_resolver = - create_npm_resolver(config_data, global_cache_path, http_client) - .await; + create_npm_resolver(config_data, cache, http_client).await; node_resolver = create_node_resolver(npm_resolver.as_ref()); } } else { @@ -111,10 +111,12 @@ impl LspResolver { node_resolver.as_ref(), ); let jsr_resolver = Some(Arc::new(JsrCacheResolver::new( - cache.clone(), + cache.root_vendor_or_global(), config_data.and_then(|d| d.lockfile.clone()), ))); - let redirect_resolver = Some(Arc::new(RedirectResolver::new(cache))); + let redirect_resolver = Some(Arc::new(RedirectResolver::new( + cache.root_vendor_or_global(), + ))); let graph_imports = config_data .and_then(|d| d.config_file.as_ref()) .and_then(|cf| cf.to_maybe_imports().ok()) @@ -317,6 +319,20 @@ impl LspResolver { }; redirect_resolver.resolve(specifier) } + + pub fn redirect_chain_headers( + &self, + specifier: &ModuleSpecifier, + ) -> Vec<(ModuleSpecifier, Arc>)> { + let Some(redirect_resolver) = self.redirect_resolver.as_ref() else { + return vec![]; + }; + redirect_resolver + .chain(specifier) + .into_iter() + .map(|(s, e)| (s, e.headers.clone())) + .collect() + } } #[derive(Debug)] @@ -383,14 +399,9 @@ impl<'a> Resolver for LspGraphResolver<'a> { async fn create_npm_resolver( config_data: &ConfigData, - global_cache_path: Option<&Path>, + cache: &LspCache, http_client: &Arc, ) -> Option> { - let deno_dir = DenoDir::new(global_cache_path.map(|p| p.to_owned())) - .inspect_err(|err| { - lsp_warn!("Error getting deno dir: {:#}", err); - }) - .ok()?; let node_modules_dir = config_data .node_modules_dir .clone() @@ -415,7 +426,7 @@ async fn create_npm_resolver( // updating it. Only the cache request should update the lockfile. maybe_lockfile: None, fs: Arc::new(deno_fs::RealFs), - npm_global_cache_dir: deno_dir.npm_folder_path(), + npm_global_cache_dir: cache.deno_dir().npm_folder_path(), // Use an "only" cache setting in order to make the // user do an explicit "cache" command and prevent // the cache from being filled with lots of packages while @@ -482,7 +493,7 @@ fn create_graph_resolver( struct LspNpmConfigHash(u64); impl LspNpmConfigHash { - pub fn new(config: &Config, global_cache_path: Option<&Path>) -> Self { + pub fn new(config: &Config, cache: &LspCache) -> Self { let config_data = config.tree.root_data(); let scope = config_data.map(|d| &d.scope); let node_modules_dir = @@ -491,64 +502,195 @@ impl LspNpmConfigHash { let mut hasher = FastInsecureHasher::new(); hasher.write_hashable(scope); hasher.write_hashable(node_modules_dir); - hasher.write_hashable(global_cache_path); if let Some(lockfile) = lockfile { hasher.write_hashable(&*lockfile.lock()); } - hasher.write_hashable(global_cache_path); + hasher.write_hashable(cache.deno_dir().npm_folder_path()); Self(hasher.finish()) } } -#[derive(Debug)] +#[derive(Debug, Eq, PartialEq)] +struct RedirectEntry { + headers: Arc>, + target: Url, + destination: Option, +} + +type GetHeadersFn = + Box Option> + Send + Sync>; + struct RedirectResolver { - cache: Arc, - redirects: Mutex>, + get_headers: GetHeadersFn, + entries: DashMap>>, +} + +impl std::fmt::Debug for RedirectResolver { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("RedirectResolver") + .field("get_headers", &"Box(|_| { ... })") + .field("entries", &self.entries) + .finish() + } } impl RedirectResolver { - pub fn new(cache: Arc) -> Self { + fn new(cache: Arc) -> Self { Self { - cache, - redirects: Mutex::new(HashMap::new()), + 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(), } } - 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()) + #[cfg(test)] + fn mock(get_headers: GetHeadersFn) -> Self { + Self { + get_headers, + entries: Default::default(), } } - 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()?; + fn resolve(&self, specifier: &Url) -> Option { + if !matches!(specifier.scheme(), "http" | "https") { + return Some(specifier.clone()); + } + let mut current = specifier.clone(); + let mut chain = vec![]; + let destination = loop { + if let Some(maybe_entry) = self.entries.get(¤t) { + break match maybe_entry.as_ref() { + Some(entry) => entry.destination.clone(), + None => Some(current), + }; + } + let Some(headers) = (self.get_headers)(¤t) else { + break None; + }; + let headers = Arc::new(headers); 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) + if chain.len() > 10 { + break None; + } + let Ok(target) = + deno_core::resolve_import(location, specifier.as_str()) + else { + break None; + }; + chain.push(( + current.clone(), + RedirectEntry { + headers, + target: target.clone(), + destination: None, + }, + )); + current = target; } else { - Some(specifier.clone()) + self.entries.insert(current.clone(), None); + break Some(current); } - } else { - None + }; + for (specifier, mut entry) in chain { + entry.destination = destination.clone(); + self.entries.insert(specifier, Some(Arc::new(entry))); + } + destination + } + + fn chain(&self, specifier: &Url) -> Vec<(Url, Arc)> { + self.resolve(specifier); + let mut result = vec![]; + let mut seen = HashSet::new(); + let mut current = Cow::Borrowed(specifier); + loop { + let Some(maybe_entry) = self.entries.get(¤t) else { + break; + }; + let Some(entry) = maybe_entry.as_ref() else { + break; + }; + result.push((current.as_ref().clone(), entry.clone())); + seen.insert(current.as_ref().clone()); + if seen.contains(&entry.target) { + break; + } + current = Cow::Owned(entry.target.clone()) } + result + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_redirect_resolver() { + let redirect_resolver = + RedirectResolver::mock(Box::new(|specifier| match specifier.as_str() { + "https://foo/redirect_2.js" => Some( + [("location".to_string(), "./redirect_1.js".to_string())] + .into_iter() + .collect(), + ), + "https://foo/redirect_1.js" => Some( + [("location".to_string(), "./file.js".to_string())] + .into_iter() + .collect(), + ), + "https://foo/file.js" => Some([].into_iter().collect()), + _ => None, + })); + assert_eq!( + redirect_resolver.resolve(&Url::parse("https://foo/file.js").unwrap()), + Some(Url::parse("https://foo/file.js").unwrap()) + ); + assert_eq!( + redirect_resolver + .resolve(&Url::parse("https://foo/redirect_1.js").unwrap()), + Some(Url::parse("https://foo/file.js").unwrap()) + ); + assert_eq!( + redirect_resolver + .resolve(&Url::parse("https://foo/redirect_2.js").unwrap()), + Some(Url::parse("https://foo/file.js").unwrap()) + ); + assert_eq!( + redirect_resolver.resolve(&Url::parse("https://foo/unknown").unwrap()), + None + ); + assert_eq!( + redirect_resolver + .chain(&Url::parse("https://foo/redirect_2.js").unwrap()), + vec![ + ( + Url::parse("https://foo/redirect_2.js").unwrap(), + Arc::new(RedirectEntry { + headers: Arc::new( + [("location".to_string(), "./redirect_1.js".to_string())] + .into_iter() + .collect() + ), + target: Url::parse("https://foo/redirect_1.js").unwrap(), + destination: Some(Url::parse("https://foo/file.js").unwrap()), + }) + ), + ( + Url::parse("https://foo/redirect_1.js").unwrap(), + Arc::new(RedirectEntry { + headers: Arc::new( + [("location".to_string(), "./file.js".to_string())] + .into_iter() + .collect() + ), + target: Url::parse("https://foo/file.js").unwrap(), + destination: Some(Url::parse("https://foo/file.js").unwrap()), + }) + ), + ] + ); } } diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs index ec1eb29fa7e88..fa35f63bd12e7 100644 --- a/cli/lsp/tsc.rs +++ b/cli/lsp/tsc.rs @@ -22,9 +22,6 @@ use super::urls::INVALID_SPECIFIER; use crate::args::jsr_url; use crate::args::FmtOptionsConfig; -use crate::cache::HttpCache; -use crate::lsp::cache::CacheMetadata; -use crate::lsp::documents::Documents; use crate::lsp::logging::lsp_warn; use crate::tsc; use crate::tsc::ResolveArgs; @@ -220,7 +217,6 @@ fn normalize_diagnostic( pub struct TsServer { performance: Arc, - cache: Arc, sender: mpsc::UnboundedSender, receiver: Mutex>>, specifier_map: Arc, @@ -232,7 +228,6 @@ impl std::fmt::Debug for TsServer { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("TsServer") .field("performance", &self.performance) - .field("cache", &self.cache) .field("sender", &self.sender) .field("receiver", &self.receiver) .field("specifier_map", &self.specifier_map) @@ -331,11 +326,10 @@ impl PendingChange { } impl TsServer { - pub fn new(performance: Arc, cache: Arc) -> Self { + pub fn new(performance: Arc) -> Self { let (tx, request_rx) = mpsc::unbounded_channel::(); Self { performance, - cache, sender: tx, receiver: Mutex::new(Some(request_rx)), specifier_map: Arc::new(TscSpecifierMap::new()), @@ -363,13 +357,11 @@ impl TsServer { // on the `TsServer` struct. let receiver = self.receiver.lock().take().unwrap(); let performance = self.performance.clone(); - let cache = self.cache.clone(); let specifier_map = self.specifier_map.clone(); let _join_handle = thread::spawn(move || { run_tsc_thread( receiver, performance.clone(), - cache.clone(), specifier_map.clone(), maybe_inspector_server, ) @@ -4340,7 +4332,6 @@ impl TscRuntime { fn run_tsc_thread( mut request_rx: UnboundedReceiver, performance: Arc, - cache: Arc, specifier_map: Arc, maybe_inspector_server: Option>, ) { @@ -4349,7 +4340,7 @@ fn run_tsc_thread( // supplied snapshot is an isolate that contains the TypeScript language // server. let mut tsc_runtime = JsRuntime::new(RuntimeOptions { - extensions: vec![deno_tsc::init_ops(performance, cache, specifier_map)], + extensions: vec![deno_tsc::init_ops(performance, specifier_map)], startup_snapshot: Some(tsc::compiler_snapshot()), inspector: maybe_inspector_server.is_some(), ..Default::default() @@ -4422,19 +4413,11 @@ deno_core::extension!(deno_tsc, ], options = { performance: Arc, - cache: Arc, specifier_map: Arc, }, state = |state, options| { state.put(State::new( - Arc::new(StateSnapshot { - project_version: 0, - assets: Default::default(), - cache_metadata: CacheMetadata::new(options.cache.clone()), - config: Default::default(), - documents: Documents::new(options.cache.clone()), - resolver: Default::default(), - }), + Default::default(), options.specifier_map, options.performance, )); @@ -5078,11 +5061,9 @@ impl TscRequest { #[cfg(test)] mod tests { use super::*; - use crate::cache::GlobalHttpCache; use crate::cache::HttpCache; - use crate::cache::RealDenoCacheEnv; use crate::http_util::HeadersMap; - use crate::lsp::cache::CacheMetadata; + use crate::lsp::cache::LspCache; use crate::lsp::config::Config; use crate::lsp::config::WorkspaceSettings; use crate::lsp::documents::Documents; @@ -5090,29 +5071,14 @@ mod tests { use crate::lsp::resolver::LspResolver; use crate::lsp::text::LineIndex; use pretty_assertions::assert_eq; - use std::path::Path; use test_util::TempDir; - async fn mock_state_snapshot( - fixtures: &[(&str, &str, i32, LanguageId)], - location: &Path, + async fn setup( ts_config: Value, - ) -> StateSnapshot { - let cache = Arc::new(GlobalHttpCache::new( - location.to_path_buf(), - RealDenoCacheEnv, - )); - 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"); - documents.open( - specifier.clone(), - *version, - *language_id, - (*source).into(), - ); - } + sources: &[(&str, &str, i32, LanguageId)], + ) -> (TsServer, Arc, LspCache) { + let temp_dir = TempDir::new(); + let cache = LspCache::new(Some(temp_dir.uri())); let mut config = Config::default(); config .tree @@ -5129,30 +5095,29 @@ mod tests { ) .await; let resolver = LspResolver::default() - .with_new_config(&config, cache.clone(), None, None) + .with_new_config(&config, &cache, None) .await; - StateSnapshot { + let mut documents = Documents::default(); + documents.update_config(&config, &resolver, &cache, &Default::default()); + for (specifier, source, version, language_id) in sources { + let specifier = + resolve_url(specifier).expect("failed to create specifier"); + documents.open( + specifier.clone(), + *version, + *language_id, + (*source).into(), + ); + } + let snapshot = Arc::new(StateSnapshot { project_version: 0, documents, assets: Default::default(), - cache_metadata: CacheMetadata::new(cache), config: Arc::new(config), resolver, - } - } - - async fn setup( - temp_dir: &TempDir, - config: Value, - sources: &[(&str, &str, i32, LanguageId)], - ) -> (TsServer, Arc, Arc) { - let location = temp_dir.path().join("deps").to_path_buf(); - let cache = - Arc::new(GlobalHttpCache::new(location.clone(), RealDenoCacheEnv)); - let snapshot = - Arc::new(mock_state_snapshot(sources, &location, config).await); + }); let performance = Arc::new(Performance::default()); - let ts_server = TsServer::new(performance, cache.clone()); + let ts_server = TsServer::new(performance); ts_server.start(None).unwrap(); (ts_server, snapshot, cache) } @@ -5182,9 +5147,7 @@ mod tests { #[tokio::test] async fn test_get_diagnostics() { - let temp_dir = TempDir::new(); let (ts_server, snapshot, _) = setup( - &temp_dir, json!({ "target": "esnext", "module": "esnext", @@ -5230,9 +5193,7 @@ mod tests { #[tokio::test] async fn test_get_diagnostics_lib() { - let temp_dir = TempDir::new(); let (ts_server, snapshot, _) = setup( - &temp_dir, json!({ "target": "esnext", "module": "esnext", @@ -5258,9 +5219,7 @@ mod tests { #[tokio::test] async fn test_module_resolution() { - let temp_dir = TempDir::new(); let (ts_server, snapshot, _) = setup( - &temp_dir, json!({ "target": "esnext", "module": "esnext", @@ -5291,9 +5250,7 @@ mod tests { #[tokio::test] async fn test_bad_module_specifiers() { - let temp_dir = TempDir::new(); let (ts_server, snapshot, _) = setup( - &temp_dir, json!({ "target": "esnext", "module": "esnext", @@ -5339,9 +5296,7 @@ mod tests { #[tokio::test] async fn test_remote_modules() { - let temp_dir = TempDir::new(); let (ts_server, snapshot, _) = setup( - &temp_dir, json!({ "target": "esnext", "module": "esnext", @@ -5372,9 +5327,7 @@ mod tests { #[tokio::test] async fn test_partial_modules() { - let temp_dir = TempDir::new(); let (ts_server, snapshot, _) = setup( - &temp_dir, json!({ "target": "esnext", "module": "esnext", @@ -5441,9 +5394,7 @@ mod tests { #[tokio::test] async fn test_no_debug_failure() { - let temp_dir = TempDir::new(); let (ts_server, snapshot, _) = setup( - &temp_dir, json!({ "target": "esnext", "module": "esnext", @@ -5489,8 +5440,7 @@ mod tests { #[tokio::test] async fn test_request_assets() { - let temp_dir = TempDir::new(); - let (ts_server, snapshot, _) = setup(&temp_dir, json!({}), &[]).await; + let (ts_server, snapshot, _) = setup(json!({}), &[]).await; let assets = get_isolate_assets(&ts_server, snapshot).await; let mut asset_names = assets .iter() @@ -5522,9 +5472,7 @@ mod tests { #[tokio::test] async fn test_modify_sources() { - let temp_dir = TempDir::new(); let (ts_server, snapshot, cache) = setup( - &temp_dir, json!({ "target": "esnext", "module": "esnext", @@ -5547,6 +5495,7 @@ mod tests { let specifier_dep = resolve_url("https://deno.land/x/example/a.ts").unwrap(); cache + .global() .set( &specifier_dep, HeadersMap::default(), @@ -5581,6 +5530,7 @@ mod tests { }) ); cache + .global() .set( &specifier_dep, HeadersMap::default(), @@ -5656,9 +5606,7 @@ mod tests { character: 16, }) .unwrap(); - let temp_dir = TempDir::new(); let (ts_server, snapshot, _) = setup( - &temp_dir, json!({ "target": "esnext", "module": "esnext", @@ -5807,9 +5755,7 @@ mod tests { character: 33, }) .unwrap(); - let temp_dir = TempDir::new(); let (ts_server, snapshot, _) = setup( - &temp_dir, json!({ "target": "esnext", "module": "esnext", @@ -5916,9 +5862,7 @@ mod tests { #[tokio::test] async fn test_get_edits_for_file_rename() { - let temp_dir = TempDir::new(); let (ts_server, snapshot, _) = setup( - &temp_dir, json!({ "target": "esnext", "module": "esnext", @@ -5994,9 +5938,7 @@ mod tests { #[tokio::test] async fn resolve_unknown_dependency() { - let temp_dir = TempDir::new(); let (_, snapshot, _) = setup( - &temp_dir, json!({ "target": "esnext", "module": "esnext",