summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNayeem Rahman <nayeemrmn99@gmail.com>2024-05-09 20:22:27 +0100
committerGitHub <noreply@github.com>2024-05-09 20:22:27 +0100
commit439b3b8db97106be44bb5fde9573632c20ed4e95 (patch)
tree22c54102ccddecaa0209d128f71fb33fc891fe4e
parentf0e8ec01461519dddcb61f1b91b4455a354e38e6 (diff)
refactor(lsp): unify caching into LspCache (#23746)
-rw-r--r--cli/cache/deno_dir.rs5
-rw-r--r--cli/cache/disk_cache.rs2
-rw-r--r--cli/lsp/cache.rs136
-rw-r--r--cli/lsp/completions.rs47
-rw-r--r--cli/lsp/config.rs4
-rw-r--r--cli/lsp/diagnostics.rs132
-rw-r--r--cli/lsp/documents.rs130
-rw-r--r--cli/lsp/language_server.rs115
-rw-r--r--cli/lsp/resolver.rs254
-rw-r--r--cli/lsp/tsc.rs114
10 files changed, 445 insertions, 494 deletions
diff --git a/cli/cache/deno_dir.rs b/cli/cache/deno_dir.rs
index b56dfbc89..9f2911f71 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 cd44dd17a..3aeebbc6d 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 a1048dace..d899cd796 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<dyn HttpCache>,
+ cache: &LspCache,
specifier: &ModuleSpecifier,
) -> Option<String> {
match specifier.scheme() {
@@ -49,13 +54,14 @@ pub fn calculate_fs_version_at_path(path: &Path) -> Option<String> {
}
fn calculate_fs_version_in_cache(
- cache: &Arc<dyn HttpCache>,
+ cache: &LspCache,
specifier: &ModuleSpecifier,
) -> Option<String> {
- 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<String, String>,
-) -> HashMap<MetadataKey, String> {
- 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<HashMap<MetadataKey, String>>,
- version: Option<String>,
+pub struct LspCache {
+ deno_dir: DenoDir,
+ global: Arc<GlobalHttpCache>,
+ root_vendor: Option<Arc<LocalLspHttpCache>>,
}
-#[derive(Debug, Clone)]
-pub struct CacheMetadata {
- cache: Arc<dyn HttpCache>,
- metadata: Arc<Mutex<HashMap<ModuleSpecifier, Metadata>>>,
+impl Default for LspCache {
+ fn default() -> Self {
+ Self::new(None)
+ }
}
-impl CacheMetadata {
- pub fn new(cache: Arc<dyn HttpCache>) -> Self {
+impl LspCache {
+ pub fn new(global_cache_url: Option<Url>) -> 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<Arc<HashMap<MetadataKey, String>>> {
- 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<Metadata> {
- 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<GlobalHttpCache> {
+ &self.global
+ }
+
+ pub fn root_vendor(&self) -> Option<&Arc<LocalLspHttpCache>> {
+ self.root_vendor.as_ref()
}
- pub fn set_cache(&mut self, cache: Arc<dyn HttpCache>) {
- self.cache = cache;
- self.metadata.lock().clear();
+ pub fn root_vendor_or_global(&self) -> Arc<dyn HttpCache> {
+ 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 f9d2316ae..3f63d2857 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 e1fed5a54..597f45688 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<Mutex<Lockfile>>> {
self.root_data().and_then(|d| d.lockfile.as_ref())
}
diff --git a/cli/lsp/diagnostics.rs b/cli/lsp/diagnostics.rs
index ebd6338cd..edaf30e83 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 42c67c45d..6c7f8433f 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<HashMap<String, String>>,
resolver: Arc<LspResolver>,
config: Arc<Config>,
- cache: &Arc<dyn HttpCache>,
+ cache: &Arc<LspCache>,
) -> Arc<Self> {
let text_info = SourceTextInfo::new(content);
let media_type = resolve_media_type(
@@ -507,7 +507,7 @@ impl Document {
}))
}
- pub fn closed(&self, cache: &Arc<dyn HttpCache>) -> Arc<Self> {
+ pub fn closed(&self, cache: &Arc<LspCache>) -> Arc<Self> {
Arc::new(Self {
config: self.config.clone(),
specifier: self.specifier.clone(),
@@ -528,7 +528,7 @@ impl Document {
})
}
- pub fn saved(&self, cache: &Arc<dyn HttpCache>) -> Arc<Self> {
+ pub fn saved(&self, cache: &Arc<LspCache>) -> Arc<Self> {
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<String, String>> {
+ 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<LspResolver>,
config: &Arc<Config>,
- cache: &Arc<dyn HttpCache>,
+ cache: &Arc<LspCache>,
) -> Option<Arc<Document>> {
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<LspResolver>,
config: &Arc<Config>,
- cache: &Arc<dyn HttpCache>,
+ cache: &Arc<LspCache>,
) -> Option<Arc<Document>> {
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<dyn HttpCache>,
+ cache: Arc<LspCache>,
config: Arc<Config>,
/// 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<dyn HttpCache>) -> 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<LspResolver>,
- cache: Arc<dyn HttpCache>,
+ cache: &LspCache,
workspace_files: &BTreeSet<ModuleSpecifier>,
) {
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<dyn HttpCache>) {
- 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 3d8ee7e8d..0c327929b 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<tokio::sync::RwLock<Inner>>, 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<Config>,
pub documents: Documents,
pub resolver: Arc<LspResolver>,
@@ -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<dyn HttpCache>,
- /// 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<GlobalHttpCache>,
+ http_client: Arc<HttpClient>,
initial_cwd: PathBuf,
jsr_search_api: CliJsrSearchApi,
- http_client: Arc<HttpClient>,
- 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<PathBuf>,
/// A lazily create "server" for handling test run requests.
maybe_testing_server: Option<testing::TestServer>,
npm_search_api: CliNpmSearchApi,
@@ -206,6 +191,7 @@ pub struct Inner {
/// A collection of measurements which instrument that performance of the LSP.
performance: Arc<Performance>,
resolver: Arc<LspResolver>,
+ task_queue: LanguageServerTaskQueue,
/// A memoized version of fixable diagnostic codes retrieved from TypeScript.
ts_fixable_diagnostics: Vec<String>,
/// 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<dyn HttpCache>)
- .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 f336c6b03..b42f253c4 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<CliGraphResolver>,
@@ -85,11 +87,10 @@ impl LspResolver {
pub async fn with_new_config(
&self,
config: &Config,
- cache: Arc<dyn HttpCache>,
- global_cache_path: Option<&Path>,
+ cache: &LspCache,
http_client: Option<&Arc<HttpClient>>,
) -> Arc<Self> {
- 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<HashMap<String, String>>)> {
+ 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<HttpClient>,
) -> Option<Arc<dyn CliNpmResolver>> {
- 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<HashMap<String, String>>,
+ target: Url,
+ destination: Option<Url>,
+}
+
+type GetHeadersFn =
+ Box<dyn Fn(&Url) -> Option<HashMap<String, String>> + Send + Sync>;
+
struct RedirectResolver {
- cache: Arc<dyn HttpCache>,
- redirects: Mutex<HashMap<ModuleSpecifier, ModuleSpecifier>>,
+ get_headers: GetHeadersFn,
+ entries: DashMap<Url, Option<Arc<RedirectEntry>>>,
+}
+
+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<dyn HttpCache>) -> Self {
+ fn new(cache: Arc<dyn HttpCache>) -> 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<ModuleSpecifier> {
- 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<ModuleSpecifier> {
- 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<Url> {
+ 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(&current) {
+ break match maybe_entry.as_ref() {
+ Some(entry) => entry.destination.clone(),
+ None => Some(current),
+ };
+ }
+ let Some(headers) = (self.get_headers)(&current) 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<RedirectEntry>)> {
+ 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(&current) 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 ec1eb29fa..fa35f63bd 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<Performance>,
- cache: Arc<dyn HttpCache>,
sender: mpsc::UnboundedSender<Request>,
receiver: Mutex<Option<mpsc::UnboundedReceiver<Request>>>,
specifier_map: Arc<TscSpecifierMap>,
@@ -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<Performance>, cache: Arc<dyn HttpCache>) -> Self {
+ pub fn new(performance: Arc<Performance>) -> Self {
let (tx, request_rx) = mpsc::unbounded_channel::<Request>();
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<Request>,
performance: Arc<Performance>,
- cache: Arc<dyn HttpCache>,
specifier_map: Arc<TscSpecifierMap>,
maybe_inspector_server: Option<Arc<InspectorServer>>,
) {
@@ -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<Performance>,
- cache: Arc<dyn HttpCache>,
specifier_map: Arc<TscSpecifierMap>,
},
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<StateSnapshot>, 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<StateSnapshot>, Arc<GlobalHttpCache>) {
- 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",