summaryrefslogtreecommitdiff
path: root/cli/lsp/resolver.rs
diff options
context:
space:
mode:
Diffstat (limited to 'cli/lsp/resolver.rs')
-rw-r--r--cli/lsp/resolver.rs254
1 files changed, 198 insertions, 56 deletions
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()),
+ })
+ ),
+ ]
+ );
}
}