summaryrefslogtreecommitdiff
path: root/cli/lsp/jsr.rs
diff options
context:
space:
mode:
authorNayeem Rahman <nayeemrmn99@gmail.com>2024-03-01 21:34:13 +0000
committerGitHub <noreply@github.com>2024-03-01 21:34:13 +0000
commit15f5f74eb745915e68b3bc16b9ec048b94e26b97 (patch)
tree2cf1b5a4febadffa77fb0a82fb2e273093034545 /cli/lsp/jsr.rs
parent2e4a1fc3e89d00e060ca9d747b6887dae215f0eb (diff)
feat(unstable/pm): support version contraints in 'deno add' (#22646)
Diffstat (limited to 'cli/lsp/jsr.rs')
-rw-r--r--cli/lsp/jsr.rs259
1 files changed, 15 insertions, 244 deletions
diff --git a/cli/lsp/jsr.rs b/cli/lsp/jsr.rs
index 47a2c1e84..29ecec60b 100644
--- a/cli/lsp/jsr.rs
+++ b/cli/lsp/jsr.rs
@@ -1,198 +1,26 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use crate::args::jsr_api_url;
-use crate::args::jsr_url;
use crate::file_fetcher::FileFetcher;
+use crate::jsr::JsrFetchResolver;
use dashmap::DashMap;
-use deno_cache_dir::HttpCache;
use deno_core::anyhow::anyhow;
use deno_core::error::AnyError;
-use deno_core::parking_lot::Mutex;
use deno_core::serde_json;
-use deno_core::ModuleSpecifier;
-use deno_graph::packages::JsrPackageInfo;
-use deno_graph::packages::JsrPackageVersionInfo;
-use deno_lockfile::Lockfile;
use deno_runtime::permissions::PermissionsContainer;
-use deno_semver::jsr::JsrPackageReqReference;
use deno_semver::package::PackageNv;
-use deno_semver::package::PackageReq;
use deno_semver::Version;
use serde::Deserialize;
-use std::borrow::Cow;
use std::sync::Arc;
-use super::cache::LSP_DISALLOW_GLOBAL_TO_LOCAL_COPY;
use super::search::PackageSearchApi;
-#[derive(Debug)]
-pub struct JsrResolver {
- nv_by_req: DashMap<PackageReq, Option<PackageNv>>,
- /// The `module_graph` field of the version infos should be forcibly absent.
- /// It can be large and we don't want to store it.
- info_by_nv: DashMap<PackageNv, Option<JsrPackageVersionInfo>>,
- info_by_name: DashMap<String, Option<JsrPackageInfo>>,
- cache: Arc<dyn HttpCache>,
-}
-
-impl JsrResolver {
- pub fn from_cache_and_lockfile(
- cache: Arc<dyn HttpCache>,
- lockfile: Option<Arc<Mutex<Lockfile>>>,
- ) -> Self {
- let nv_by_req = DashMap::new();
- if let Some(lockfile) = lockfile {
- for (req_url, nv_url) in &lockfile.lock().content.packages.specifiers {
- let Some(req) = req_url.strip_prefix("jsr:") else {
- continue;
- };
- let Some(nv) = nv_url.strip_prefix("jsr:") else {
- continue;
- };
- let Ok(req) = PackageReq::from_str(req) else {
- continue;
- };
- let Ok(nv) = PackageNv::from_str(nv) else {
- continue;
- };
- nv_by_req.insert(req, Some(nv));
- }
- }
- Self {
- nv_by_req,
- info_by_nv: Default::default(),
- info_by_name: Default::default(),
- cache: cache.clone(),
- }
- }
-
- pub fn req_to_nv(&self, req: &PackageReq) -> Option<PackageNv> {
- let nv = self.nv_by_req.entry(req.clone()).or_insert_with(|| {
- let name = req.name.clone();
- let maybe_package_info = self
- .info_by_name
- .entry(name.clone())
- .or_insert_with(|| read_cached_package_info(&name, &self.cache));
- let package_info = maybe_package_info.as_ref()?;
- // Find the first matching version of the package which is cached.
- let mut versions = package_info.versions.keys().collect::<Vec<_>>();
- versions.sort();
- let version = versions
- .into_iter()
- .rev()
- .find(|v| {
- if req.version_req.tag().is_some() || !req.version_req.matches(v) {
- return false;
- }
- let nv = PackageNv {
- name: name.clone(),
- version: (*v).clone(),
- };
- self
- .info_by_nv
- .entry(nv.clone())
- .or_insert_with(|| {
- read_cached_package_version_info(&nv, &self.cache)
- })
- .is_some()
- })
- .cloned()?;
- Some(PackageNv { name, version })
- });
- nv.value().clone()
- }
-
- pub fn jsr_to_registry_url(
- &self,
- specifier: &ModuleSpecifier,
- ) -> Option<ModuleSpecifier> {
- let req_ref = JsrPackageReqReference::from_str(specifier.as_str()).ok()?;
- let req = req_ref.req().clone();
- let maybe_nv = self.req_to_nv(&req);
- let nv = maybe_nv.as_ref()?;
- let maybe_info = self
- .info_by_nv
- .entry(nv.clone())
- .or_insert_with(|| read_cached_package_version_info(nv, &self.cache));
- let info = maybe_info.as_ref()?;
- let path = info.export(&normalize_export_name(req_ref.sub_path()))?;
- jsr_url()
- .join(&format!("{}/{}/{}", &nv.name, &nv.version, &path))
- .ok()
- }
-
- pub fn lookup_export_for_path(
- &self,
- nv: &PackageNv,
- path: &str,
- ) -> Option<String> {
- let maybe_info = self
- .info_by_nv
- .entry(nv.clone())
- .or_insert_with(|| read_cached_package_version_info(nv, &self.cache));
- let info = maybe_info.as_ref()?;
- let path = path.strip_prefix("./").unwrap_or(path);
- for (export, path_) in info.exports() {
- if path_.strip_prefix("./").unwrap_or(path_) == path {
- return Some(export.strip_prefix("./").unwrap_or(export).to_string());
- }
- }
- None
- }
-
- pub fn lookup_req_for_nv(&self, nv: &PackageNv) -> Option<PackageReq> {
- for entry in self.nv_by_req.iter() {
- let Some(nv_) = entry.value() else {
- continue;
- };
- if nv_ == nv {
- return Some(entry.key().clone());
- }
- }
- None
- }
-}
-
-fn read_cached_package_info(
- name: &str,
- cache: &Arc<dyn HttpCache>,
-) -> Option<JsrPackageInfo> {
- let meta_url = jsr_url().join(&format!("{}/meta.json", name)).ok()?;
- let meta_cache_item_key = cache.cache_item_key(&meta_url).ok()?;
- let meta_bytes = cache
- .read_file_bytes(
- &meta_cache_item_key,
- None,
- LSP_DISALLOW_GLOBAL_TO_LOCAL_COPY,
- )
- .ok()??;
- serde_json::from_slice::<JsrPackageInfo>(&meta_bytes).ok()
-}
-
-fn read_cached_package_version_info(
- nv: &PackageNv,
- cache: &Arc<dyn HttpCache>,
-) -> Option<JsrPackageVersionInfo> {
- let meta_url = jsr_url()
- .join(&format!("{}/{}_meta.json", &nv.name, &nv.version))
- .ok()?;
- let meta_cache_item_key = cache.cache_item_key(&meta_url).ok()?;
- let meta_bytes = cache
- .read_file_bytes(
- &meta_cache_item_key,
- None,
- LSP_DISALLOW_GLOBAL_TO_LOCAL_COPY,
- )
- .ok()??;
- partial_jsr_package_version_info_from_slice(&meta_bytes).ok()
-}
-
#[derive(Debug, Clone)]
pub struct CliJsrSearchApi {
file_fetcher: FileFetcher,
/// We only store this here so the completion system has access to a resolver
/// that always uses the global cache.
- resolver: Arc<JsrResolver>,
+ resolver: Arc<JsrFetchResolver>,
search_cache: Arc<DashMap<String, Arc<Vec<String>>>>,
versions_cache: Arc<DashMap<String, Arc<Vec<Version>>>>,
exports_cache: Arc<DashMap<PackageNv, Arc<Vec<String>>>>,
@@ -200,10 +28,7 @@ pub struct CliJsrSearchApi {
impl CliJsrSearchApi {
pub fn new(file_fetcher: FileFetcher) -> Self {
- let resolver = Arc::new(JsrResolver::from_cache_and_lockfile(
- file_fetcher.http_cache().clone(),
- None,
- ));
+ let resolver = Arc::new(JsrFetchResolver::new(file_fetcher.clone()));
Self {
file_fetcher,
resolver,
@@ -213,7 +38,7 @@ impl CliJsrSearchApi {
}
}
- pub fn get_resolver(&self) -> &Arc<JsrResolver> {
+ pub fn get_resolver(&self) -> &Arc<JsrFetchResolver> {
&self.resolver
}
}
@@ -245,19 +70,12 @@ impl PackageSearchApi for CliJsrSearchApi {
if let Some(versions) = self.versions_cache.get(name) {
return Ok(versions.clone());
}
- let mut meta_url = jsr_url().clone();
- meta_url
- .path_segments_mut()
- .map_err(|_| anyhow!("Custom jsr URL cannot be a base."))?
- .pop_if_empty()
- .push(name)
- .push("meta.json");
- let file = self
- .file_fetcher
- .fetch(&meta_url, PermissionsContainer::allow_all())
- .await?;
- let info = serde_json::from_slice::<JsrPackageInfo>(&file.source)?;
- let mut versions = info.versions.into_keys().collect::<Vec<_>>();
+ let info = self
+ .resolver
+ .package_info(name)
+ .await
+ .ok_or_else(|| anyhow!("JSR package info not found: {}", name))?;
+ let mut versions = info.versions.keys().cloned().collect::<Vec<_>>();
versions.sort();
versions.reverse();
let versions = Arc::new(versions);
@@ -274,18 +92,11 @@ impl PackageSearchApi for CliJsrSearchApi {
if let Some(exports) = self.exports_cache.get(nv) {
return Ok(exports.clone());
}
- let mut meta_url = jsr_url().clone();
- meta_url
- .path_segments_mut()
- .map_err(|_| anyhow!("Custom jsr URL cannot be a base."))?
- .pop_if_empty()
- .push(&nv.name)
- .push(&format!("{}_meta.json", &nv.version));
- let file = self
- .file_fetcher
- .fetch(&meta_url, PermissionsContainer::allow_all())
- .await?;
- let info = partial_jsr_package_version_info_from_slice(&file.source)?;
+ let info = self
+ .resolver
+ .package_version_info(nv)
+ .await
+ .ok_or_else(|| anyhow!("JSR package version info not found: {}", nv))?;
let mut exports = info
.exports()
.map(|(n, _)| n.to_string())
@@ -297,46 +108,6 @@ impl PackageSearchApi for CliJsrSearchApi {
}
}
-// TODO(nayeemrmn): This is duplicated from a private function in deno_graph
-// 0.65.1. Make it public or cleanup otherwise.
-fn normalize_export_name(sub_path: Option<&str>) -> Cow<str> {
- let Some(sub_path) = sub_path else {
- return Cow::Borrowed(".");
- };
- if sub_path.is_empty() || matches!(sub_path, "/" | ".") {
- Cow::Borrowed(".")
- } else {
- let sub_path = if sub_path.starts_with('/') {
- Cow::Owned(format!(".{}", sub_path))
- } else if !sub_path.starts_with("./") {
- Cow::Owned(format!("./{}", sub_path))
- } else {
- Cow::Borrowed(sub_path)
- };
- if let Some(prefix) = sub_path.strip_suffix('/') {
- Cow::Owned(prefix.to_string())
- } else {
- sub_path
- }
- }
-}
-
-/// This is a roundabout way of deserializing `JsrPackageVersionInfo`,
-/// because we only want the `exports` field and `module_graph` is large.
-fn partial_jsr_package_version_info_from_slice(
- slice: &[u8],
-) -> serde_json::Result<JsrPackageVersionInfo> {
- let mut info = serde_json::from_slice::<serde_json::Value>(slice)?;
- Ok(JsrPackageVersionInfo {
- manifest: Default::default(), // not used by the LSP (only caching checks this in deno_graph)
- exports: info
- .as_object_mut()
- .and_then(|o| o.remove("exports"))
- .unwrap_or_default(),
- module_graph: None,
- })
-}
-
fn parse_jsr_search_response(source: &str) -> Result<Vec<String>, AnyError> {
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]