mirror of
https://github.com/denoland/deno.git
synced 2024-11-21 15:04:11 -05:00
feat(lsp): jsr specifier completions (#22612)
This commit is contained in:
parent
814eb42060
commit
3a43568481
9 changed files with 891 additions and 210 deletions
|
@ -693,6 +693,7 @@ impl CliOptions {
|
||||||
maybe_config_file: Option<ConfigFile>,
|
maybe_config_file: Option<ConfigFile>,
|
||||||
maybe_lockfile: Option<Arc<Mutex<Lockfile>>>,
|
maybe_lockfile: Option<Arc<Mutex<Lockfile>>>,
|
||||||
maybe_package_json: Option<PackageJson>,
|
maybe_package_json: Option<PackageJson>,
|
||||||
|
force_global_cache: bool,
|
||||||
) -> Result<Self, AnyError> {
|
) -> Result<Self, AnyError> {
|
||||||
if let Some(insecure_allowlist) =
|
if let Some(insecure_allowlist) =
|
||||||
flags.unsafely_ignore_certificate_errors.as_ref()
|
flags.unsafely_ignore_certificate_errors.as_ref()
|
||||||
|
@ -708,6 +709,7 @@ impl CliOptions {
|
||||||
eprintln!("{}", colors::yellow(msg));
|
eprintln!("{}", colors::yellow(msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let maybe_lockfile = maybe_lockfile.filter(|_| !force_global_cache);
|
||||||
let maybe_node_modules_folder = resolve_node_modules_folder(
|
let maybe_node_modules_folder = resolve_node_modules_folder(
|
||||||
&initial_cwd,
|
&initial_cwd,
|
||||||
&flags,
|
&flags,
|
||||||
|
@ -715,8 +717,11 @@ impl CliOptions {
|
||||||
maybe_package_json.as_ref(),
|
maybe_package_json.as_ref(),
|
||||||
)
|
)
|
||||||
.with_context(|| "Resolving node_modules folder.")?;
|
.with_context(|| "Resolving node_modules folder.")?;
|
||||||
let maybe_vendor_folder =
|
let maybe_vendor_folder = if force_global_cache {
|
||||||
resolve_vendor_folder(&initial_cwd, &flags, maybe_config_file.as_ref());
|
None
|
||||||
|
} else {
|
||||||
|
resolve_vendor_folder(&initial_cwd, &flags, maybe_config_file.as_ref())
|
||||||
|
};
|
||||||
let maybe_workspace_config =
|
let maybe_workspace_config =
|
||||||
if let Some(config_file) = maybe_config_file.as_ref() {
|
if let Some(config_file) = maybe_config_file.as_ref() {
|
||||||
config_file.to_workspace_config()?
|
config_file.to_workspace_config()?
|
||||||
|
@ -802,6 +807,7 @@ impl CliOptions {
|
||||||
maybe_config_file,
|
maybe_config_file,
|
||||||
maybe_lock_file.map(|l| Arc::new(Mutex::new(l))),
|
maybe_lock_file.map(|l| Arc::new(Mutex::new(l))),
|
||||||
maybe_package_json,
|
maybe_package_json,
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -186,6 +186,10 @@ impl FileFetcher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn http_cache(&self) -> &Arc<dyn HttpCache> {
|
||||||
|
&self.http_cache
|
||||||
|
}
|
||||||
|
|
||||||
pub fn cache_setting(&self) -> &CacheSetting {
|
pub fn cache_setting(&self) -> &CacheSetting {
|
||||||
&self.cache_setting
|
&self.cache_setting
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,12 @@ use super::config::ConfigSnapshot;
|
||||||
use super::config::WorkspaceSettings;
|
use super::config::WorkspaceSettings;
|
||||||
use super::documents::Documents;
|
use super::documents::Documents;
|
||||||
use super::documents::DocumentsFilter;
|
use super::documents::DocumentsFilter;
|
||||||
|
use super::jsr::CliJsrSearchApi;
|
||||||
|
use super::jsr::JsrResolver;
|
||||||
use super::lsp_custom;
|
use super::lsp_custom;
|
||||||
use super::npm::CliNpmSearchApi;
|
use super::npm::CliNpmSearchApi;
|
||||||
use super::npm::NpmSearchApi;
|
|
||||||
use super::registries::ModuleRegistry;
|
use super::registries::ModuleRegistry;
|
||||||
|
use super::search::PackageSearchApi;
|
||||||
use super::tsc;
|
use super::tsc;
|
||||||
|
|
||||||
use crate::util::path::is_importable_ext;
|
use crate::util::path::is_importable_ext;
|
||||||
|
@ -25,6 +27,8 @@ use deno_core::serde::Serialize;
|
||||||
use deno_core::serde_json::json;
|
use deno_core::serde_json::json;
|
||||||
use deno_core::url::Position;
|
use deno_core::url::Position;
|
||||||
use deno_core::ModuleSpecifier;
|
use deno_core::ModuleSpecifier;
|
||||||
|
use deno_semver::jsr::JsrPackageReqReference;
|
||||||
|
use deno_semver::package::PackageNv;
|
||||||
use import_map::ImportMap;
|
use import_map::ImportMap;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
@ -148,6 +152,7 @@ pub async fn get_import_completions(
|
||||||
config: &ConfigSnapshot,
|
config: &ConfigSnapshot,
|
||||||
client: &Client,
|
client: &Client,
|
||||||
module_registries: &ModuleRegistry,
|
module_registries: &ModuleRegistry,
|
||||||
|
jsr_search_api: &CliJsrSearchApi,
|
||||||
npm_search_api: &CliNpmSearchApi,
|
npm_search_api: &CliNpmSearchApi,
|
||||||
documents: &Documents,
|
documents: &Documents,
|
||||||
maybe_import_map: Option<Arc<ImportMap>>,
|
maybe_import_map: Option<Arc<ImportMap>>,
|
||||||
|
@ -170,6 +175,19 @@ pub async fn get_import_completions(
|
||||||
is_incomplete: false,
|
is_incomplete: false,
|
||||||
items: get_local_completions(specifier, &text, &range)?,
|
items: get_local_completions(specifier, &text, &range)?,
|
||||||
}))
|
}))
|
||||||
|
} else if text.starts_with("jsr:") {
|
||||||
|
let items = get_jsr_completions(
|
||||||
|
specifier,
|
||||||
|
&text,
|
||||||
|
&range,
|
||||||
|
jsr_search_api,
|
||||||
|
jsr_search_api.get_resolver(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Some(lsp::CompletionResponse::List(lsp::CompletionList {
|
||||||
|
is_incomplete: !items.is_empty(),
|
||||||
|
items,
|
||||||
|
}))
|
||||||
} else if text.starts_with("npm:") {
|
} else if text.starts_with("npm:") {
|
||||||
let items =
|
let items =
|
||||||
get_npm_completions(specifier, &text, &range, npm_search_api).await?;
|
get_npm_completions(specifier, &text, &range, npm_search_api).await?;
|
||||||
|
@ -475,8 +493,7 @@ fn get_relative_specifiers(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find the index of the '@' delimiting the package name and version, if any.
|
/// Find the index of the '@' delimiting the package name and version, if any.
|
||||||
fn parse_npm_specifier_version_index(specifier: &str) -> Option<usize> {
|
fn parse_bare_specifier_version_index(bare_specifier: &str) -> Option<usize> {
|
||||||
let bare_specifier = specifier.strip_prefix("npm:")?;
|
|
||||||
if bare_specifier.starts_with('@') {
|
if bare_specifier.starts_with('@') {
|
||||||
bare_specifier
|
bare_specifier
|
||||||
.find('/')
|
.find('/')
|
||||||
|
@ -486,49 +503,188 @@ fn parse_npm_specifier_version_index(specifier: &str) -> Option<usize> {
|
||||||
.find('@')
|
.find('@')
|
||||||
.filter(|idx2| !bare_specifier[idx..][1..*idx2].is_empty())
|
.filter(|idx2| !bare_specifier[idx..][1..*idx2].is_empty())
|
||||||
.filter(|idx2| !bare_specifier[idx..][1..*idx2].contains('/'))
|
.filter(|idx2| !bare_specifier[idx..][1..*idx2].contains('/'))
|
||||||
.map(|idx2| 4 + idx + idx2)
|
.map(|idx2| idx + idx2)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
bare_specifier
|
bare_specifier
|
||||||
.find('@')
|
.find('@')
|
||||||
.filter(|idx| !bare_specifier[1..*idx].is_empty())
|
.filter(|idx| !bare_specifier[1..*idx].is_empty())
|
||||||
.filter(|idx| !bare_specifier[1..*idx].contains('/'))
|
.filter(|idx| !bare_specifier[1..*idx].contains('/'))
|
||||||
.map(|idx| 4 + idx)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_jsr_completions(
|
||||||
|
referrer: &ModuleSpecifier,
|
||||||
|
specifier: &str,
|
||||||
|
range: &lsp::Range,
|
||||||
|
jsr_search_api: &impl PackageSearchApi,
|
||||||
|
jsr_resolver: &JsrResolver,
|
||||||
|
) -> Option<Vec<lsp::CompletionItem>> {
|
||||||
|
// First try to match `jsr:some-package@some-version/<export-to-complete>`.
|
||||||
|
if let Ok(req_ref) = JsrPackageReqReference::from_str(specifier) {
|
||||||
|
let sub_path = req_ref.sub_path();
|
||||||
|
if sub_path.is_some() || specifier.ends_with('/') {
|
||||||
|
let export_prefix = sub_path.unwrap_or("");
|
||||||
|
let req = req_ref.req();
|
||||||
|
let nv = jsr_resolver.req_to_nv(req);
|
||||||
|
let nv = nv.or_else(|| PackageNv::from_str(&req.to_string()).ok())?;
|
||||||
|
let exports = jsr_search_api.exports(&nv).await.ok()?;
|
||||||
|
let items = exports
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter_map(|(idx, export)| {
|
||||||
|
if export == "." {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let export = export.strip_prefix("./").unwrap_or(export.as_str());
|
||||||
|
if !export.starts_with(export_prefix) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let specifier = format!("jsr:{}/{}", req_ref.req(), export);
|
||||||
|
let command = Some(lsp::Command {
|
||||||
|
title: "".to_string(),
|
||||||
|
command: "deno.cache".to_string(),
|
||||||
|
arguments: Some(vec![
|
||||||
|
json!([&specifier]),
|
||||||
|
json!(referrer),
|
||||||
|
json!({ "forceGlobalCache": true }),
|
||||||
|
]),
|
||||||
|
});
|
||||||
|
let text_edit = Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
||||||
|
range: *range,
|
||||||
|
new_text: specifier.clone(),
|
||||||
|
}));
|
||||||
|
Some(lsp::CompletionItem {
|
||||||
|
label: specifier,
|
||||||
|
kind: Some(lsp::CompletionItemKind::FILE),
|
||||||
|
detail: Some("(jsr)".to_string()),
|
||||||
|
sort_text: Some(format!("{:0>10}", idx + 1)),
|
||||||
|
text_edit,
|
||||||
|
command,
|
||||||
|
commit_characters: Some(
|
||||||
|
IMPORT_COMMIT_CHARS.iter().map(|&c| c.into()).collect(),
|
||||||
|
),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
return Some(items);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then try to match `jsr:some-package@<version-to-complete>`.
|
||||||
|
let bare_specifier = specifier.strip_prefix("jsr:")?;
|
||||||
|
if let Some(v_index) = parse_bare_specifier_version_index(bare_specifier) {
|
||||||
|
let package_name = &bare_specifier[..v_index];
|
||||||
|
let v_prefix = &bare_specifier[(v_index + 1)..];
|
||||||
|
|
||||||
|
let versions = jsr_search_api.versions(package_name).await.ok()?;
|
||||||
|
let items = versions
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter_map(|(idx, version)| {
|
||||||
|
let version = version.to_string();
|
||||||
|
if !version.starts_with(v_prefix) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let specifier = format!("jsr:{}@{}", package_name, version);
|
||||||
|
let command = Some(lsp::Command {
|
||||||
|
title: "".to_string(),
|
||||||
|
command: "deno.cache".to_string(),
|
||||||
|
arguments: Some(vec![
|
||||||
|
json!([&specifier]),
|
||||||
|
json!(referrer),
|
||||||
|
json!({ "forceGlobalCache": true }),
|
||||||
|
]),
|
||||||
|
});
|
||||||
|
let text_edit = Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
||||||
|
range: *range,
|
||||||
|
new_text: specifier.clone(),
|
||||||
|
}));
|
||||||
|
Some(lsp::CompletionItem {
|
||||||
|
label: specifier,
|
||||||
|
kind: Some(lsp::CompletionItemKind::FILE),
|
||||||
|
detail: Some("(jsr)".to_string()),
|
||||||
|
sort_text: Some(format!("{:0>10}", idx + 1)),
|
||||||
|
text_edit,
|
||||||
|
command,
|
||||||
|
commit_characters: Some(
|
||||||
|
IMPORT_COMMIT_CHARS.iter().map(|&c| c.into()).collect(),
|
||||||
|
),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
return Some(items);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise match `jsr:<package-to-complete>`.
|
||||||
|
let names = jsr_search_api.search(bare_specifier).await.ok()?;
|
||||||
|
let items = names
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(idx, name)| {
|
||||||
|
let specifier = format!("jsr:{}", name);
|
||||||
|
let command = Some(lsp::Command {
|
||||||
|
title: "".to_string(),
|
||||||
|
command: "deno.cache".to_string(),
|
||||||
|
arguments: Some(vec![
|
||||||
|
json!([&specifier]),
|
||||||
|
json!(referrer),
|
||||||
|
json!({ "forceGlobalCache": true }),
|
||||||
|
]),
|
||||||
|
});
|
||||||
|
let text_edit = Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
||||||
|
range: *range,
|
||||||
|
new_text: specifier.clone(),
|
||||||
|
}));
|
||||||
|
lsp::CompletionItem {
|
||||||
|
label: specifier,
|
||||||
|
kind: Some(lsp::CompletionItemKind::FILE),
|
||||||
|
detail: Some("(jsr)".to_string()),
|
||||||
|
sort_text: Some(format!("{:0>10}", idx + 1)),
|
||||||
|
text_edit,
|
||||||
|
command,
|
||||||
|
commit_characters: Some(
|
||||||
|
IMPORT_COMMIT_CHARS.iter().map(|&c| c.into()).collect(),
|
||||||
|
),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
Some(items)
|
||||||
|
}
|
||||||
|
|
||||||
/// Get completions for `npm:` specifiers.
|
/// Get completions for `npm:` specifiers.
|
||||||
async fn get_npm_completions(
|
async fn get_npm_completions(
|
||||||
referrer: &ModuleSpecifier,
|
referrer: &ModuleSpecifier,
|
||||||
specifier: &str,
|
specifier: &str,
|
||||||
range: &lsp::Range,
|
range: &lsp::Range,
|
||||||
npm_search_api: &impl NpmSearchApi,
|
npm_search_api: &impl PackageSearchApi,
|
||||||
) -> Option<Vec<lsp::CompletionItem>> {
|
) -> Option<Vec<lsp::CompletionItem>> {
|
||||||
// First try to match `npm:some-package@<version-to-complete>`.
|
// First try to match `npm:some-package@<version-to-complete>`.
|
||||||
if let Some(v_index) = parse_npm_specifier_version_index(specifier) {
|
let bare_specifier = specifier.strip_prefix("npm:")?;
|
||||||
let package_name = &specifier[..v_index].strip_prefix("npm:")?;
|
if let Some(v_index) = parse_bare_specifier_version_index(bare_specifier) {
|
||||||
let v_prefix = &specifier[(v_index + 1)..];
|
let package_name = &bare_specifier[..v_index];
|
||||||
let versions = &npm_search_api
|
let v_prefix = &bare_specifier[(v_index + 1)..];
|
||||||
.package_info(package_name)
|
let versions = npm_search_api.versions(package_name).await.ok()?;
|
||||||
.await
|
|
||||||
.ok()?
|
|
||||||
.versions;
|
|
||||||
let mut versions = versions.keys().collect::<Vec<_>>();
|
|
||||||
versions.sort();
|
|
||||||
let items = versions
|
let items = versions
|
||||||
.into_iter()
|
.iter()
|
||||||
.rev()
|
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter_map(|(idx, version)| {
|
.filter_map(|(idx, version)| {
|
||||||
let version = version.to_string();
|
let version = version.to_string();
|
||||||
if !version.starts_with(v_prefix) {
|
if !version.starts_with(v_prefix) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let specifier = format!("npm:{}@{}", package_name, &version);
|
let specifier = format!("npm:{}@{}", package_name, version);
|
||||||
let command = Some(lsp::Command {
|
let command = Some(lsp::Command {
|
||||||
title: "".to_string(),
|
title: "".to_string(),
|
||||||
command: "deno.cache".to_string(),
|
command: "deno.cache".to_string(),
|
||||||
arguments: Some(vec![json!([&specifier]), json!(referrer)]),
|
arguments: Some(vec![
|
||||||
|
json!([&specifier]),
|
||||||
|
json!(referrer),
|
||||||
|
json!({ "forceGlobalCache": true }),
|
||||||
|
]),
|
||||||
});
|
});
|
||||||
let text_edit = Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
let text_edit = Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
||||||
range: *range,
|
range: *range,
|
||||||
|
@ -552,8 +708,7 @@ async fn get_npm_completions(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise match `npm:<package-to-complete>`.
|
// Otherwise match `npm:<package-to-complete>`.
|
||||||
let package_name_prefix = specifier.strip_prefix("npm:")?;
|
let names = npm_search_api.search(bare_specifier).await.ok()?;
|
||||||
let names = npm_search_api.search(package_name_prefix).await.ok()?;
|
|
||||||
let items = names
|
let items = names
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
|
@ -562,7 +717,11 @@ async fn get_npm_completions(
|
||||||
let command = Some(lsp::Command {
|
let command = Some(lsp::Command {
|
||||||
title: "".to_string(),
|
title: "".to_string(),
|
||||||
command: "deno.cache".to_string(),
|
command: "deno.cache".to_string(),
|
||||||
arguments: Some(vec![json!([&specifier]), json!(referrer)]),
|
arguments: Some(vec![
|
||||||
|
json!([&specifier]),
|
||||||
|
json!(referrer),
|
||||||
|
json!({ "forceGlobalCache": true }),
|
||||||
|
]),
|
||||||
});
|
});
|
||||||
let text_edit = Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
let text_edit = Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
||||||
range: *range,
|
range: *range,
|
||||||
|
@ -640,43 +799,16 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::cache::GlobalHttpCache;
|
use crate::cache::GlobalHttpCache;
|
||||||
use crate::cache::HttpCache;
|
use crate::cache::HttpCache;
|
||||||
|
use crate::cache::RealDenoCacheEnv;
|
||||||
use crate::lsp::documents::Documents;
|
use crate::lsp::documents::Documents;
|
||||||
use crate::lsp::documents::LanguageId;
|
use crate::lsp::documents::LanguageId;
|
||||||
use crate::lsp::npm::NpmSearchApi;
|
use crate::lsp::search::tests::TestPackageSearchApi;
|
||||||
use crate::AnyError;
|
|
||||||
use async_trait::async_trait;
|
|
||||||
use deno_core::resolve_url;
|
use deno_core::resolve_url;
|
||||||
use deno_graph::Range;
|
use deno_graph::Range;
|
||||||
use deno_npm::registry::NpmPackageInfo;
|
|
||||||
use deno_npm::registry::NpmRegistryApi;
|
|
||||||
use deno_npm::registry::TestNpmRegistryApi;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use test_util::TempDir;
|
use test_util::TempDir;
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct TestNpmSearchApi(
|
|
||||||
HashMap<String, Arc<Vec<String>>>,
|
|
||||||
TestNpmRegistryApi,
|
|
||||||
);
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl NpmSearchApi for TestNpmSearchApi {
|
|
||||||
async fn search(&self, query: &str) -> Result<Arc<Vec<String>>, AnyError> {
|
|
||||||
match self.0.get(query) {
|
|
||||||
Some(names) => Ok(names.clone()),
|
|
||||||
None => Ok(Arc::new(vec![])),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn package_info(
|
|
||||||
&self,
|
|
||||||
name: &str,
|
|
||||||
) -> Result<Arc<NpmPackageInfo>, AnyError> {
|
|
||||||
self.1.package_info(name).await.map_err(|e| e.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mock_documents(
|
fn mock_documents(
|
||||||
fixtures: &[(&str, &str, i32, LanguageId)],
|
fixtures: &[(&str, &str, i32, LanguageId)],
|
||||||
source_fixtures: &[(&str, &str)],
|
source_fixtures: &[(&str, &str)],
|
||||||
|
@ -846,52 +978,326 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_npm_specifier_version_index() {
|
fn test_parse_bare_specifier_version_index() {
|
||||||
assert_eq!(parse_npm_specifier_version_index("npm:"), None);
|
assert_eq!(parse_bare_specifier_version_index(""), None);
|
||||||
assert_eq!(parse_npm_specifier_version_index("npm:/"), None);
|
assert_eq!(parse_bare_specifier_version_index("/"), None);
|
||||||
assert_eq!(parse_npm_specifier_version_index("npm:/@"), None);
|
assert_eq!(parse_bare_specifier_version_index("/@"), None);
|
||||||
assert_eq!(parse_npm_specifier_version_index("npm:@"), None);
|
assert_eq!(parse_bare_specifier_version_index("@"), None);
|
||||||
assert_eq!(parse_npm_specifier_version_index("npm:@/"), None);
|
assert_eq!(parse_bare_specifier_version_index("@/"), None);
|
||||||
assert_eq!(parse_npm_specifier_version_index("npm:@/@"), None);
|
assert_eq!(parse_bare_specifier_version_index("@/@"), None);
|
||||||
assert_eq!(parse_npm_specifier_version_index("npm:foo"), None);
|
assert_eq!(parse_bare_specifier_version_index("foo"), None);
|
||||||
assert_eq!(parse_npm_specifier_version_index("npm:foo/bar"), None);
|
assert_eq!(parse_bare_specifier_version_index("foo/bar"), None);
|
||||||
assert_eq!(parse_npm_specifier_version_index("npm:foo/bar@"), None);
|
assert_eq!(parse_bare_specifier_version_index("foo/bar@"), None);
|
||||||
assert_eq!(parse_npm_specifier_version_index("npm:@org/foo/bar"), None);
|
assert_eq!(parse_bare_specifier_version_index("@org/foo/bar"), None);
|
||||||
assert_eq!(parse_npm_specifier_version_index("npm:@org/foo/bar@"), None);
|
assert_eq!(parse_bare_specifier_version_index("@org/foo/bar@"), None);
|
||||||
|
|
||||||
assert_eq!(parse_npm_specifier_version_index("npm:foo@"), Some(7));
|
assert_eq!(parse_bare_specifier_version_index("foo@"), Some(3));
|
||||||
assert_eq!(parse_npm_specifier_version_index("npm:foo@1."), Some(7));
|
assert_eq!(parse_bare_specifier_version_index("foo@1."), Some(3));
|
||||||
assert_eq!(parse_npm_specifier_version_index("npm:@org/foo@"), Some(12));
|
assert_eq!(parse_bare_specifier_version_index("@org/foo@"), Some(8));
|
||||||
assert_eq!(
|
assert_eq!(parse_bare_specifier_version_index("@org/foo@1."), Some(8));
|
||||||
parse_npm_specifier_version_index("npm:@org/foo@1."),
|
|
||||||
Some(12)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Regression test for https://github.com/denoland/deno/issues/22325.
|
// Regression test for https://github.com/denoland/deno/issues/22325.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_npm_specifier_version_index(
|
parse_bare_specifier_version_index(
|
||||||
"npm:@longer_than_right_one/arbitrary_string@"
|
"@longer_than_right_one/arbitrary_string@"
|
||||||
),
|
),
|
||||||
Some(43)
|
Some(39)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_get_jsr_completions() {
|
||||||
|
let temp_dir = TempDir::default();
|
||||||
|
let jsr_resolver = JsrResolver::from_cache_and_lockfile(
|
||||||
|
Arc::new(GlobalHttpCache::new(
|
||||||
|
temp_dir.path().to_path_buf(),
|
||||||
|
RealDenoCacheEnv,
|
||||||
|
)),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
let jsr_search_api = TestPackageSearchApi::default()
|
||||||
|
.with_package_version("@std/archive", "1.0.0", &[])
|
||||||
|
.with_package_version("@std/assert", "1.0.0", &[])
|
||||||
|
.with_package_version("@std/async", "1.0.0", &[])
|
||||||
|
.with_package_version("@std/bytes", "1.0.0", &[]);
|
||||||
|
let range = lsp::Range {
|
||||||
|
start: lsp::Position {
|
||||||
|
line: 0,
|
||||||
|
character: 23,
|
||||||
|
},
|
||||||
|
end: lsp::Position {
|
||||||
|
line: 0,
|
||||||
|
character: 29,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let referrer = ModuleSpecifier::parse("file:///referrer.ts").unwrap();
|
||||||
|
let actual = get_jsr_completions(
|
||||||
|
&referrer,
|
||||||
|
"jsr:as",
|
||||||
|
&range,
|
||||||
|
&jsr_search_api,
|
||||||
|
&jsr_resolver,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
actual,
|
||||||
|
vec![
|
||||||
|
lsp::CompletionItem {
|
||||||
|
label: "jsr:@std/assert".to_string(),
|
||||||
|
kind: Some(lsp::CompletionItemKind::FILE),
|
||||||
|
detail: Some("(jsr)".to_string()),
|
||||||
|
sort_text: Some("0000000001".to_string()),
|
||||||
|
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
||||||
|
range,
|
||||||
|
new_text: "jsr:@std/assert".to_string(),
|
||||||
|
})),
|
||||||
|
command: Some(lsp::Command {
|
||||||
|
title: "".to_string(),
|
||||||
|
command: "deno.cache".to_string(),
|
||||||
|
arguments: Some(vec![
|
||||||
|
json!(["jsr:@std/assert"]),
|
||||||
|
json!(&referrer),
|
||||||
|
json!({ "forceGlobalCache": true })
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
commit_characters: Some(
|
||||||
|
IMPORT_COMMIT_CHARS.iter().map(|&c| c.into()).collect()
|
||||||
|
),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
lsp::CompletionItem {
|
||||||
|
label: "jsr:@std/async".to_string(),
|
||||||
|
kind: Some(lsp::CompletionItemKind::FILE),
|
||||||
|
detail: Some("(jsr)".to_string()),
|
||||||
|
sort_text: Some("0000000002".to_string()),
|
||||||
|
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
||||||
|
range,
|
||||||
|
new_text: "jsr:@std/async".to_string(),
|
||||||
|
})),
|
||||||
|
command: Some(lsp::Command {
|
||||||
|
title: "".to_string(),
|
||||||
|
command: "deno.cache".to_string(),
|
||||||
|
arguments: Some(vec![
|
||||||
|
json!(["jsr:@std/async"]),
|
||||||
|
json!(&referrer),
|
||||||
|
json!({ "forceGlobalCache": true })
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
commit_characters: Some(
|
||||||
|
IMPORT_COMMIT_CHARS.iter().map(|&c| c.into()).collect()
|
||||||
|
),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_get_jsr_completions_for_versions() {
|
||||||
|
let temp_dir = TempDir::default();
|
||||||
|
let jsr_resolver = JsrResolver::from_cache_and_lockfile(
|
||||||
|
Arc::new(GlobalHttpCache::new(
|
||||||
|
temp_dir.path().to_path_buf(),
|
||||||
|
RealDenoCacheEnv,
|
||||||
|
)),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
let jsr_search_api = TestPackageSearchApi::default()
|
||||||
|
.with_package_version("@std/assert", "0.3.0", &[])
|
||||||
|
.with_package_version("@std/assert", "0.4.0", &[])
|
||||||
|
.with_package_version("@std/assert", "0.5.0", &[]);
|
||||||
|
let range = lsp::Range {
|
||||||
|
start: lsp::Position {
|
||||||
|
line: 0,
|
||||||
|
character: 23,
|
||||||
|
},
|
||||||
|
end: lsp::Position {
|
||||||
|
line: 0,
|
||||||
|
character: 39,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let referrer = ModuleSpecifier::parse("file:///referrer.ts").unwrap();
|
||||||
|
let actual = get_jsr_completions(
|
||||||
|
&referrer,
|
||||||
|
"jsr:@std/assert@",
|
||||||
|
&range,
|
||||||
|
&jsr_search_api,
|
||||||
|
&jsr_resolver,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
actual,
|
||||||
|
vec![
|
||||||
|
lsp::CompletionItem {
|
||||||
|
label: "jsr:@std/assert@0.5.0".to_string(),
|
||||||
|
kind: Some(lsp::CompletionItemKind::FILE),
|
||||||
|
detail: Some("(jsr)".to_string()),
|
||||||
|
sort_text: Some("0000000001".to_string()),
|
||||||
|
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
||||||
|
range,
|
||||||
|
new_text: "jsr:@std/assert@0.5.0".to_string(),
|
||||||
|
})),
|
||||||
|
command: Some(lsp::Command {
|
||||||
|
title: "".to_string(),
|
||||||
|
command: "deno.cache".to_string(),
|
||||||
|
arguments: Some(vec![
|
||||||
|
json!(["jsr:@std/assert@0.5.0"]),
|
||||||
|
json!(&referrer),
|
||||||
|
json!({ "forceGlobalCache": true }),
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
commit_characters: Some(
|
||||||
|
IMPORT_COMMIT_CHARS.iter().map(|&c| c.into()).collect()
|
||||||
|
),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
lsp::CompletionItem {
|
||||||
|
label: "jsr:@std/assert@0.4.0".to_string(),
|
||||||
|
kind: Some(lsp::CompletionItemKind::FILE),
|
||||||
|
detail: Some("(jsr)".to_string()),
|
||||||
|
sort_text: Some("0000000002".to_string()),
|
||||||
|
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
||||||
|
range,
|
||||||
|
new_text: "jsr:@std/assert@0.4.0".to_string(),
|
||||||
|
})),
|
||||||
|
command: Some(lsp::Command {
|
||||||
|
title: "".to_string(),
|
||||||
|
command: "deno.cache".to_string(),
|
||||||
|
arguments: Some(vec![
|
||||||
|
json!(["jsr:@std/assert@0.4.0"]),
|
||||||
|
json!(&referrer),
|
||||||
|
json!({ "forceGlobalCache": true }),
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
commit_characters: Some(
|
||||||
|
IMPORT_COMMIT_CHARS.iter().map(|&c| c.into()).collect()
|
||||||
|
),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
lsp::CompletionItem {
|
||||||
|
label: "jsr:@std/assert@0.3.0".to_string(),
|
||||||
|
kind: Some(lsp::CompletionItemKind::FILE),
|
||||||
|
detail: Some("(jsr)".to_string()),
|
||||||
|
sort_text: Some("0000000003".to_string()),
|
||||||
|
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
||||||
|
range,
|
||||||
|
new_text: "jsr:@std/assert@0.3.0".to_string(),
|
||||||
|
})),
|
||||||
|
command: Some(lsp::Command {
|
||||||
|
title: "".to_string(),
|
||||||
|
command: "deno.cache".to_string(),
|
||||||
|
arguments: Some(vec![
|
||||||
|
json!(["jsr:@std/assert@0.3.0"]),
|
||||||
|
json!(&referrer),
|
||||||
|
json!({ "forceGlobalCache": true }),
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
commit_characters: Some(
|
||||||
|
IMPORT_COMMIT_CHARS.iter().map(|&c| c.into()).collect()
|
||||||
|
),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_get_jsr_completions_for_exports() {
|
||||||
|
let temp_dir = TempDir::default();
|
||||||
|
let jsr_resolver = JsrResolver::from_cache_and_lockfile(
|
||||||
|
Arc::new(GlobalHttpCache::new(
|
||||||
|
temp_dir.path().to_path_buf(),
|
||||||
|
RealDenoCacheEnv,
|
||||||
|
)),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
let jsr_search_api = TestPackageSearchApi::default().with_package_version(
|
||||||
|
"@std/path",
|
||||||
|
"0.1.0",
|
||||||
|
&[".", "./basename", "./common", "./constants", "./dirname"],
|
||||||
|
);
|
||||||
|
let range = lsp::Range {
|
||||||
|
start: lsp::Position {
|
||||||
|
line: 0,
|
||||||
|
character: 23,
|
||||||
|
},
|
||||||
|
end: lsp::Position {
|
||||||
|
line: 0,
|
||||||
|
character: 45,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let referrer = ModuleSpecifier::parse("file:///referrer.ts").unwrap();
|
||||||
|
let actual = get_jsr_completions(
|
||||||
|
&referrer,
|
||||||
|
"jsr:@std/path@0.1.0/co",
|
||||||
|
&range,
|
||||||
|
&jsr_search_api,
|
||||||
|
&jsr_resolver,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
actual,
|
||||||
|
vec![
|
||||||
|
lsp::CompletionItem {
|
||||||
|
label: "jsr:@std/path@0.1.0/common".to_string(),
|
||||||
|
kind: Some(lsp::CompletionItemKind::FILE),
|
||||||
|
detail: Some("(jsr)".to_string()),
|
||||||
|
sort_text: Some("0000000003".to_string()),
|
||||||
|
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
||||||
|
range,
|
||||||
|
new_text: "jsr:@std/path@0.1.0/common".to_string(),
|
||||||
|
})),
|
||||||
|
command: Some(lsp::Command {
|
||||||
|
title: "".to_string(),
|
||||||
|
command: "deno.cache".to_string(),
|
||||||
|
arguments: Some(vec![
|
||||||
|
json!(["jsr:@std/path@0.1.0/common"]),
|
||||||
|
json!(&referrer),
|
||||||
|
json!({ "forceGlobalCache": true }),
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
commit_characters: Some(
|
||||||
|
IMPORT_COMMIT_CHARS.iter().map(|&c| c.into()).collect()
|
||||||
|
),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
lsp::CompletionItem {
|
||||||
|
label: "jsr:@std/path@0.1.0/constants".to_string(),
|
||||||
|
kind: Some(lsp::CompletionItemKind::FILE),
|
||||||
|
detail: Some("(jsr)".to_string()),
|
||||||
|
sort_text: Some("0000000004".to_string()),
|
||||||
|
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
||||||
|
range,
|
||||||
|
new_text: "jsr:@std/path@0.1.0/constants".to_string(),
|
||||||
|
})),
|
||||||
|
command: Some(lsp::Command {
|
||||||
|
title: "".to_string(),
|
||||||
|
command: "deno.cache".to_string(),
|
||||||
|
arguments: Some(vec![
|
||||||
|
json!(["jsr:@std/path@0.1.0/constants"]),
|
||||||
|
json!(&referrer),
|
||||||
|
json!({ "forceGlobalCache": true }),
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
commit_characters: Some(
|
||||||
|
IMPORT_COMMIT_CHARS.iter().map(|&c| c.into()).collect()
|
||||||
|
),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_get_npm_completions() {
|
async fn test_get_npm_completions() {
|
||||||
let npm_search_api = TestNpmSearchApi(
|
let npm_search_api = TestPackageSearchApi::default()
|
||||||
vec![(
|
.with_package_version("puppeteer", "1.0.0", &[])
|
||||||
"puppe".to_string(),
|
.with_package_version("puppeteer-core", "1.0.0", &[])
|
||||||
Arc::new(vec![
|
.with_package_version("puppeteer-extra-plugin", "1.0.0", &[])
|
||||||
"puppeteer".to_string(),
|
.with_package_version("puppeteer-extra-plugin-stealth", "1.0.0", &[]);
|
||||||
"puppeteer-core".to_string(),
|
|
||||||
"puppeteer-extra-plugin-stealth".to_string(),
|
|
||||||
"puppeteer-extra-plugin".to_string(),
|
|
||||||
]),
|
|
||||||
)]
|
|
||||||
.into_iter()
|
|
||||||
.collect(),
|
|
||||||
Default::default(),
|
|
||||||
);
|
|
||||||
let range = lsp::Range {
|
let range = lsp::Range {
|
||||||
start: lsp::Position {
|
start: lsp::Position {
|
||||||
line: 0,
|
line: 0,
|
||||||
|
@ -922,7 +1328,11 @@ mod tests {
|
||||||
command: Some(lsp::Command {
|
command: Some(lsp::Command {
|
||||||
title: "".to_string(),
|
title: "".to_string(),
|
||||||
command: "deno.cache".to_string(),
|
command: "deno.cache".to_string(),
|
||||||
arguments: Some(vec![json!(["npm:puppeteer"]), json!(&referrer)])
|
arguments: Some(vec![
|
||||||
|
json!(["npm:puppeteer"]),
|
||||||
|
json!(&referrer),
|
||||||
|
json!({ "forceGlobalCache": true }),
|
||||||
|
])
|
||||||
}),
|
}),
|
||||||
commit_characters: Some(
|
commit_characters: Some(
|
||||||
IMPORT_COMMIT_CHARS.iter().map(|&c| c.into()).collect()
|
IMPORT_COMMIT_CHARS.iter().map(|&c| c.into()).collect()
|
||||||
|
@ -943,29 +1353,8 @@ mod tests {
|
||||||
command: "deno.cache".to_string(),
|
command: "deno.cache".to_string(),
|
||||||
arguments: Some(vec![
|
arguments: Some(vec![
|
||||||
json!(["npm:puppeteer-core"]),
|
json!(["npm:puppeteer-core"]),
|
||||||
json!(&referrer)
|
json!(&referrer),
|
||||||
])
|
json!({ "forceGlobalCache": true }),
|
||||||
}),
|
|
||||||
commit_characters: Some(
|
|
||||||
IMPORT_COMMIT_CHARS.iter().map(|&c| c.into()).collect()
|
|
||||||
),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
lsp::CompletionItem {
|
|
||||||
label: "npm:puppeteer-extra-plugin-stealth".to_string(),
|
|
||||||
kind: Some(lsp::CompletionItemKind::FILE),
|
|
||||||
detail: Some("(npm)".to_string()),
|
|
||||||
sort_text: Some("0000000003".to_string()),
|
|
||||||
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
|
||||||
range,
|
|
||||||
new_text: "npm:puppeteer-extra-plugin-stealth".to_string(),
|
|
||||||
})),
|
|
||||||
command: Some(lsp::Command {
|
|
||||||
title: "".to_string(),
|
|
||||||
command: "deno.cache".to_string(),
|
|
||||||
arguments: Some(vec![
|
|
||||||
json!(["npm:puppeteer-extra-plugin-stealth"]),
|
|
||||||
json!(&referrer)
|
|
||||||
])
|
])
|
||||||
}),
|
}),
|
||||||
commit_characters: Some(
|
commit_characters: Some(
|
||||||
|
@ -977,7 +1366,7 @@ mod tests {
|
||||||
label: "npm:puppeteer-extra-plugin".to_string(),
|
label: "npm:puppeteer-extra-plugin".to_string(),
|
||||||
kind: Some(lsp::CompletionItemKind::FILE),
|
kind: Some(lsp::CompletionItemKind::FILE),
|
||||||
detail: Some("(npm)".to_string()),
|
detail: Some("(npm)".to_string()),
|
||||||
sort_text: Some("0000000004".to_string()),
|
sort_text: Some("0000000003".to_string()),
|
||||||
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
||||||
range,
|
range,
|
||||||
new_text: "npm:puppeteer-extra-plugin".to_string(),
|
new_text: "npm:puppeteer-extra-plugin".to_string(),
|
||||||
|
@ -987,7 +1376,31 @@ mod tests {
|
||||||
command: "deno.cache".to_string(),
|
command: "deno.cache".to_string(),
|
||||||
arguments: Some(vec![
|
arguments: Some(vec![
|
||||||
json!(["npm:puppeteer-extra-plugin"]),
|
json!(["npm:puppeteer-extra-plugin"]),
|
||||||
json!(&referrer)
|
json!(&referrer),
|
||||||
|
json!({ "forceGlobalCache": true }),
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
commit_characters: Some(
|
||||||
|
IMPORT_COMMIT_CHARS.iter().map(|&c| c.into()).collect()
|
||||||
|
),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
lsp::CompletionItem {
|
||||||
|
label: "npm:puppeteer-extra-plugin-stealth".to_string(),
|
||||||
|
kind: Some(lsp::CompletionItemKind::FILE),
|
||||||
|
detail: Some("(npm)".to_string()),
|
||||||
|
sort_text: Some("0000000004".to_string()),
|
||||||
|
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
||||||
|
range,
|
||||||
|
new_text: "npm:puppeteer-extra-plugin-stealth".to_string(),
|
||||||
|
})),
|
||||||
|
command: Some(lsp::Command {
|
||||||
|
title: "".to_string(),
|
||||||
|
command: "deno.cache".to_string(),
|
||||||
|
arguments: Some(vec![
|
||||||
|
json!(["npm:puppeteer-extra-plugin-stealth"]),
|
||||||
|
json!(&referrer),
|
||||||
|
json!({ "forceGlobalCache": true }),
|
||||||
])
|
])
|
||||||
}),
|
}),
|
||||||
commit_characters: Some(
|
commit_characters: Some(
|
||||||
|
@ -1001,19 +1414,11 @@ mod tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_get_npm_completions_for_versions() {
|
async fn test_get_npm_completions_for_versions() {
|
||||||
let npm_search_api = TestNpmSearchApi::default();
|
let npm_search_api = TestPackageSearchApi::default()
|
||||||
npm_search_api
|
.with_package_version("puppeteer", "20.9.0", &[])
|
||||||
.1
|
.with_package_version("puppeteer", "21.0.0", &[])
|
||||||
.ensure_package_version("puppeteer", "20.9.0");
|
.with_package_version("puppeteer", "21.0.1", &[])
|
||||||
npm_search_api
|
.with_package_version("puppeteer", "21.0.2", &[]);
|
||||||
.1
|
|
||||||
.ensure_package_version("puppeteer", "21.0.0");
|
|
||||||
npm_search_api
|
|
||||||
.1
|
|
||||||
.ensure_package_version("puppeteer", "21.0.1");
|
|
||||||
npm_search_api
|
|
||||||
.1
|
|
||||||
.ensure_package_version("puppeteer", "21.0.2");
|
|
||||||
let range = lsp::Range {
|
let range = lsp::Range {
|
||||||
start: lsp::Position {
|
start: lsp::Position {
|
||||||
line: 0,
|
line: 0,
|
||||||
|
@ -1046,7 +1451,8 @@ mod tests {
|
||||||
command: "deno.cache".to_string(),
|
command: "deno.cache".to_string(),
|
||||||
arguments: Some(vec![
|
arguments: Some(vec![
|
||||||
json!(["npm:puppeteer@21.0.2"]),
|
json!(["npm:puppeteer@21.0.2"]),
|
||||||
json!(&referrer)
|
json!(&referrer),
|
||||||
|
json!({ "forceGlobalCache": true }),
|
||||||
])
|
])
|
||||||
}),
|
}),
|
||||||
commit_characters: Some(
|
commit_characters: Some(
|
||||||
|
@ -1068,7 +1474,8 @@ mod tests {
|
||||||
command: "deno.cache".to_string(),
|
command: "deno.cache".to_string(),
|
||||||
arguments: Some(vec![
|
arguments: Some(vec![
|
||||||
json!(["npm:puppeteer@21.0.1"]),
|
json!(["npm:puppeteer@21.0.1"]),
|
||||||
json!(&referrer)
|
json!(&referrer),
|
||||||
|
json!({ "forceGlobalCache": true }),
|
||||||
])
|
])
|
||||||
}),
|
}),
|
||||||
commit_characters: Some(
|
commit_characters: Some(
|
||||||
|
@ -1090,7 +1497,8 @@ mod tests {
|
||||||
command: "deno.cache".to_string(),
|
command: "deno.cache".to_string(),
|
||||||
arguments: Some(vec![
|
arguments: Some(vec![
|
||||||
json!(["npm:puppeteer@21.0.0"]),
|
json!(["npm:puppeteer@21.0.0"]),
|
||||||
json!(&referrer)
|
json!(&referrer),
|
||||||
|
json!({ "forceGlobalCache": true }),
|
||||||
])
|
])
|
||||||
}),
|
}),
|
||||||
commit_characters: Some(
|
commit_characters: Some(
|
||||||
|
@ -1112,7 +1520,8 @@ mod tests {
|
||||||
command: "deno.cache".to_string(),
|
command: "deno.cache".to_string(),
|
||||||
arguments: Some(vec![
|
arguments: Some(vec![
|
||||||
json!(["npm:puppeteer@20.9.0"]),
|
json!(["npm:puppeteer@20.9.0"]),
|
||||||
json!(&referrer)
|
json!(&referrer),
|
||||||
|
json!({ "forceGlobalCache": true }),
|
||||||
])
|
])
|
||||||
}),
|
}),
|
||||||
commit_characters: Some(
|
commit_characters: Some(
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
use super::cache::calculate_fs_version;
|
use super::cache::calculate_fs_version;
|
||||||
use super::cache::calculate_fs_version_at_path;
|
use super::cache::calculate_fs_version_at_path;
|
||||||
use super::cache::LSP_DISALLOW_GLOBAL_TO_LOCAL_COPY;
|
use super::cache::LSP_DISALLOW_GLOBAL_TO_LOCAL_COPY;
|
||||||
use super::jsr_resolver::JsrResolver;
|
use super::jsr::JsrResolver;
|
||||||
use super::language_server::StateNpmSnapshot;
|
use super::language_server::StateNpmSnapshot;
|
||||||
use super::text::LineIndex;
|
use super::text::LineIndex;
|
||||||
use super::tsc;
|
use super::tsc;
|
||||||
|
|
|
@ -1,21 +1,29 @@
|
||||||
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
use crate::args::jsr_api_url;
|
||||||
use crate::args::jsr_url;
|
use crate::args::jsr_url;
|
||||||
|
use crate::file_fetcher::FileFetcher;
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use deno_cache_dir::HttpCache;
|
use deno_cache_dir::HttpCache;
|
||||||
|
use deno_core::anyhow::anyhow;
|
||||||
|
use deno_core::error::AnyError;
|
||||||
use deno_core::parking_lot::Mutex;
|
use deno_core::parking_lot::Mutex;
|
||||||
use deno_core::serde_json;
|
use deno_core::serde_json;
|
||||||
use deno_core::ModuleSpecifier;
|
use deno_core::ModuleSpecifier;
|
||||||
use deno_graph::packages::JsrPackageInfo;
|
use deno_graph::packages::JsrPackageInfo;
|
||||||
use deno_graph::packages::JsrPackageVersionInfo;
|
use deno_graph::packages::JsrPackageVersionInfo;
|
||||||
use deno_lockfile::Lockfile;
|
use deno_lockfile::Lockfile;
|
||||||
|
use deno_runtime::permissions::PermissionsContainer;
|
||||||
use deno_semver::jsr::JsrPackageReqReference;
|
use deno_semver::jsr::JsrPackageReqReference;
|
||||||
use deno_semver::package::PackageNv;
|
use deno_semver::package::PackageNv;
|
||||||
use deno_semver::package::PackageReq;
|
use deno_semver::package::PackageReq;
|
||||||
|
use deno_semver::Version;
|
||||||
|
use serde::Deserialize;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use super::cache::LSP_DISALLOW_GLOBAL_TO_LOCAL_COPY;
|
use super::cache::LSP_DISALLOW_GLOBAL_TO_LOCAL_COPY;
|
||||||
|
use super::search::PackageSearchApi;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct JsrResolver {
|
pub struct JsrResolver {
|
||||||
|
@ -58,13 +66,8 @@ impl JsrResolver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn jsr_to_registry_url(
|
pub fn req_to_nv(&self, req: &PackageReq) -> Option<PackageNv> {
|
||||||
&self,
|
let nv = self.nv_by_req.entry(req.clone()).or_insert_with(|| {
|
||||||
specifier: &ModuleSpecifier,
|
|
||||||
) -> Option<ModuleSpecifier> {
|
|
||||||
let req_ref = JsrPackageReqReference::from_str(specifier.as_str()).ok()?;
|
|
||||||
let req = req_ref.req().clone();
|
|
||||||
let maybe_nv = self.nv_by_req.entry(req.clone()).or_insert_with(|| {
|
|
||||||
let name = req.name.clone();
|
let name = req.name.clone();
|
||||||
let maybe_package_info = self
|
let maybe_package_info = self
|
||||||
.info_by_name
|
.info_by_name
|
||||||
|
@ -72,9 +75,11 @@ impl JsrResolver {
|
||||||
.or_insert_with(|| read_cached_package_info(&name, &self.cache));
|
.or_insert_with(|| read_cached_package_info(&name, &self.cache));
|
||||||
let package_info = maybe_package_info.as_ref()?;
|
let package_info = maybe_package_info.as_ref()?;
|
||||||
// Find the first matching version of the package which is cached.
|
// Find the first matching version of the package which is cached.
|
||||||
let version = package_info
|
let mut versions = package_info.versions.keys().collect::<Vec<_>>();
|
||||||
.versions
|
versions.sort();
|
||||||
.keys()
|
let version = versions
|
||||||
|
.into_iter()
|
||||||
|
.rev()
|
||||||
.find(|v| {
|
.find(|v| {
|
||||||
if req.version_req.tag().is_some() || !req.version_req.matches(v) {
|
if req.version_req.tag().is_some() || !req.version_req.matches(v) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -94,6 +99,16 @@ impl JsrResolver {
|
||||||
.cloned()?;
|
.cloned()?;
|
||||||
Some(PackageNv { name, version })
|
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 nv = maybe_nv.as_ref()?;
|
||||||
let maybe_info = self
|
let maybe_info = self
|
||||||
.info_by_nv
|
.info_by_nv
|
||||||
|
@ -169,15 +184,117 @@ fn read_cached_package_version_info(
|
||||||
LSP_DISALLOW_GLOBAL_TO_LOCAL_COPY,
|
LSP_DISALLOW_GLOBAL_TO_LOCAL_COPY,
|
||||||
)
|
)
|
||||||
.ok()??;
|
.ok()??;
|
||||||
// This is a roundabout way of deserializing `JsrPackageVersionInfo`,
|
partial_jsr_package_version_info_from_slice(&meta_bytes).ok()
|
||||||
// because we only want the `exports` field and `module_graph` is large.
|
}
|
||||||
let mut info =
|
|
||||||
serde_json::from_slice::<serde_json::Value>(&meta_bytes).ok()?;
|
#[derive(Debug, Clone)]
|
||||||
Some(JsrPackageVersionInfo {
|
pub struct CliJsrSearchApi {
|
||||||
manifest: Default::default(), // not used by the LSP (only caching checks this in deno_graph)
|
file_fetcher: FileFetcher,
|
||||||
exports: info.as_object_mut()?.remove("exports")?,
|
/// We only store this here so the completion system has access to a resolver
|
||||||
module_graph: None,
|
/// that always uses the global cache.
|
||||||
})
|
resolver: Arc<JsrResolver>,
|
||||||
|
search_cache: Arc<DashMap<String, Arc<Vec<String>>>>,
|
||||||
|
versions_cache: Arc<DashMap<String, Arc<Vec<Version>>>>,
|
||||||
|
exports_cache: Arc<DashMap<PackageNv, Arc<Vec<String>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CliJsrSearchApi {
|
||||||
|
pub fn new(file_fetcher: FileFetcher) -> Self {
|
||||||
|
let resolver = Arc::new(JsrResolver::from_cache_and_lockfile(
|
||||||
|
file_fetcher.http_cache().clone(),
|
||||||
|
None,
|
||||||
|
));
|
||||||
|
Self {
|
||||||
|
file_fetcher,
|
||||||
|
resolver,
|
||||||
|
search_cache: Default::default(),
|
||||||
|
versions_cache: Default::default(),
|
||||||
|
exports_cache: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_resolver(&self) -> &Arc<JsrResolver> {
|
||||||
|
&self.resolver
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl PackageSearchApi for CliJsrSearchApi {
|
||||||
|
async fn search(&self, query: &str) -> Result<Arc<Vec<String>>, AnyError> {
|
||||||
|
if let Some(names) = self.search_cache.get(query) {
|
||||||
|
return Ok(names.clone());
|
||||||
|
}
|
||||||
|
let mut search_url = jsr_api_url().clone();
|
||||||
|
search_url
|
||||||
|
.path_segments_mut()
|
||||||
|
.map_err(|_| anyhow!("Custom jsr URL cannot be a base."))?
|
||||||
|
.pop_if_empty()
|
||||||
|
.push("packages");
|
||||||
|
search_url.query_pairs_mut().append_pair("query", query);
|
||||||
|
let file = self
|
||||||
|
.file_fetcher
|
||||||
|
.fetch(&search_url, PermissionsContainer::allow_all())
|
||||||
|
.await?
|
||||||
|
.into_text_decoded()?;
|
||||||
|
let names = Arc::new(parse_jsr_search_response(&file.source)?);
|
||||||
|
self.search_cache.insert(query.to_string(), names.clone());
|
||||||
|
Ok(names)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn versions(&self, name: &str) -> Result<Arc<Vec<Version>>, AnyError> {
|
||||||
|
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<_>>();
|
||||||
|
versions.sort();
|
||||||
|
versions.reverse();
|
||||||
|
let versions = Arc::new(versions);
|
||||||
|
self
|
||||||
|
.versions_cache
|
||||||
|
.insert(name.to_string(), versions.clone());
|
||||||
|
Ok(versions)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn exports(
|
||||||
|
&self,
|
||||||
|
nv: &PackageNv,
|
||||||
|
) -> Result<Arc<Vec<String>>, AnyError> {
|
||||||
|
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 mut exports = info
|
||||||
|
.exports()
|
||||||
|
.map(|(n, _)| n.to_string())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
exports.sort();
|
||||||
|
let exports = Arc::new(exports);
|
||||||
|
self.exports_cache.insert(nv.clone(), exports.clone());
|
||||||
|
Ok(exports)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(nayeemrmn): This is duplicated from a private function in deno_graph
|
// TODO(nayeemrmn): This is duplicated from a private function in deno_graph
|
||||||
|
@ -203,3 +320,42 @@ fn normalize_export_name(sub_path: Option<&str>) -> Cow<str> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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")]
|
||||||
|
struct Item {
|
||||||
|
scope: String,
|
||||||
|
name: String,
|
||||||
|
version_count: usize,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct Response {
|
||||||
|
items: Vec<Item>,
|
||||||
|
}
|
||||||
|
let items = serde_json::from_str::<Response>(source)?.items;
|
||||||
|
Ok(
|
||||||
|
items
|
||||||
|
.into_iter()
|
||||||
|
.filter(|i| i.version_count > 0)
|
||||||
|
.map(|i| format!("@{}/{}", i.scope, i.name))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
|
@ -23,6 +23,7 @@ use deno_runtime::deno_tls::RootCertStoreProvider;
|
||||||
use import_map::ImportMap;
|
use import_map::ImportMap;
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
use log::error;
|
use log::error;
|
||||||
|
use serde::Deserialize;
|
||||||
use serde_json::from_value;
|
use serde_json::from_value;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
@ -69,6 +70,7 @@ use super::documents::Documents;
|
||||||
use super::documents::DocumentsFilter;
|
use super::documents::DocumentsFilter;
|
||||||
use super::documents::LanguageId;
|
use super::documents::LanguageId;
|
||||||
use super::documents::UpdateDocumentConfigOptions;
|
use super::documents::UpdateDocumentConfigOptions;
|
||||||
|
use super::jsr::CliJsrSearchApi;
|
||||||
use super::logging::lsp_log;
|
use super::logging::lsp_log;
|
||||||
use super::logging::lsp_warn;
|
use super::logging::lsp_warn;
|
||||||
use super::lsp_custom;
|
use super::lsp_custom;
|
||||||
|
@ -239,6 +241,7 @@ pub struct Inner {
|
||||||
/// on disk or "open" within the client.
|
/// on disk or "open" within the client.
|
||||||
pub documents: Documents,
|
pub documents: Documents,
|
||||||
initial_cwd: PathBuf,
|
initial_cwd: PathBuf,
|
||||||
|
jsr_search_api: CliJsrSearchApi,
|
||||||
http_client: Arc<HttpClient>,
|
http_client: Arc<HttpClient>,
|
||||||
task_queue: LanguageServerTaskQueue,
|
task_queue: LanguageServerTaskQueue,
|
||||||
/// Handles module registries, which allow discovery of modules
|
/// Handles module registries, which allow discovery of modules
|
||||||
|
@ -280,10 +283,11 @@ impl LanguageServer {
|
||||||
|
|
||||||
/// Similar to `deno cache` on the command line, where modules will be cached
|
/// Similar to `deno cache` on the command line, where modules will be cached
|
||||||
/// in the Deno cache, including any of their dependencies.
|
/// in the Deno cache, including any of their dependencies.
|
||||||
pub async fn cache_request(
|
pub async fn cache(
|
||||||
&self,
|
&self,
|
||||||
specifiers: Vec<ModuleSpecifier>,
|
specifiers: Vec<ModuleSpecifier>,
|
||||||
referrer: ModuleSpecifier,
|
referrer: ModuleSpecifier,
|
||||||
|
force_global_cache: bool,
|
||||||
) -> LspResult<Option<Value>> {
|
) -> LspResult<Option<Value>> {
|
||||||
async fn create_graph_for_caching(
|
async fn create_graph_for_caching(
|
||||||
cli_options: CliOptions,
|
cli_options: CliOptions,
|
||||||
|
@ -333,7 +337,7 @@ impl LanguageServer {
|
||||||
// do as much as possible in a read, then do a write outside
|
// do as much as possible in a read, then do a write outside
|
||||||
let maybe_prepare_cache_result = {
|
let maybe_prepare_cache_result = {
|
||||||
let inner = self.0.read().await; // ensure dropped
|
let inner = self.0.read().await; // ensure dropped
|
||||||
match inner.prepare_cache(specifiers, referrer) {
|
match inner.prepare_cache(specifiers, referrer, force_global_cache) {
|
||||||
Ok(maybe_cache_result) => maybe_cache_result,
|
Ok(maybe_cache_result) => maybe_cache_result,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
self
|
self
|
||||||
|
@ -499,13 +503,23 @@ impl Inner {
|
||||||
module_registries_location.clone(),
|
module_registries_location.clone(),
|
||||||
http_client.clone(),
|
http_client.clone(),
|
||||||
);
|
);
|
||||||
let npm_search_api =
|
|
||||||
CliNpmSearchApi::new(module_registries.file_fetcher.clone(), None);
|
|
||||||
let location = dir.deps_folder_path();
|
let location = dir.deps_folder_path();
|
||||||
let deps_http_cache = Arc::new(GlobalHttpCache::new(
|
let deps_http_cache = Arc::new(GlobalHttpCache::new(
|
||||||
location,
|
location,
|
||||||
crate::cache::RealDenoCacheEnv,
|
crate::cache::RealDenoCacheEnv,
|
||||||
));
|
));
|
||||||
|
let mut deps_file_fetcher = FileFetcher::new(
|
||||||
|
deps_http_cache.clone(),
|
||||||
|
CacheSetting::RespectHeaders,
|
||||||
|
true,
|
||||||
|
http_client.clone(),
|
||||||
|
Default::default(),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
deps_file_fetcher.set_download_log_level(super::logging::lsp_log_level());
|
||||||
|
let jsr_search_api = CliJsrSearchApi::new(deps_file_fetcher);
|
||||||
|
let npm_search_api =
|
||||||
|
CliNpmSearchApi::new(module_registries.file_fetcher.clone());
|
||||||
let documents = Documents::new(deps_http_cache.clone());
|
let documents = Documents::new(deps_http_cache.clone());
|
||||||
let cache_metadata = cache::CacheMetadata::new(deps_http_cache.clone());
|
let cache_metadata = cache::CacheMetadata::new(deps_http_cache.clone());
|
||||||
let performance = Arc::new(Performance::default());
|
let performance = Arc::new(Performance::default());
|
||||||
|
@ -535,6 +549,7 @@ impl Inner {
|
||||||
documents,
|
documents,
|
||||||
http_client,
|
http_client,
|
||||||
initial_cwd: initial_cwd.clone(),
|
initial_cwd: initial_cwd.clone(),
|
||||||
|
jsr_search_api,
|
||||||
maybe_global_cache_path: None,
|
maybe_global_cache_path: None,
|
||||||
maybe_import_map: None,
|
maybe_import_map: None,
|
||||||
maybe_package_json: None,
|
maybe_package_json: None,
|
||||||
|
@ -832,14 +847,24 @@ impl Inner {
|
||||||
module_registries_location.clone(),
|
module_registries_location.clone(),
|
||||||
self.http_client.clone(),
|
self.http_client.clone(),
|
||||||
);
|
);
|
||||||
self.npm.search_api =
|
|
||||||
CliNpmSearchApi::new(self.module_registries.file_fetcher.clone(), None);
|
|
||||||
self.module_registries_location = module_registries_location;
|
self.module_registries_location = module_registries_location;
|
||||||
// update the cache path
|
// update the cache path
|
||||||
let global_cache = Arc::new(GlobalHttpCache::new(
|
let global_cache = Arc::new(GlobalHttpCache::new(
|
||||||
dir.deps_folder_path(),
|
dir.deps_folder_path(),
|
||||||
crate::cache::RealDenoCacheEnv,
|
crate::cache::RealDenoCacheEnv,
|
||||||
));
|
));
|
||||||
|
let mut deps_file_fetcher = FileFetcher::new(
|
||||||
|
global_cache.clone(),
|
||||||
|
CacheSetting::RespectHeaders,
|
||||||
|
true,
|
||||||
|
self.http_client.clone(),
|
||||||
|
Default::default(),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
deps_file_fetcher.set_download_log_level(super::logging::lsp_log_level());
|
||||||
|
self.jsr_search_api = CliJsrSearchApi::new(deps_file_fetcher);
|
||||||
|
self.npm.search_api =
|
||||||
|
CliNpmSearchApi::new(self.module_registries.file_fetcher.clone());
|
||||||
let maybe_local_cache =
|
let maybe_local_cache =
|
||||||
self.config.maybe_vendor_dir_path().map(|local_path| {
|
self.config.maybe_vendor_dir_path().map(|local_path| {
|
||||||
Arc::new(LocalLspHttpCache::new(local_path, global_cache.clone()))
|
Arc::new(LocalLspHttpCache::new(local_path, global_cache.clone()))
|
||||||
|
@ -1040,7 +1065,7 @@ impl Inner {
|
||||||
self.task_queue.queue_task(Box::new(|ls: LanguageServer| {
|
self.task_queue.queue_task(Box::new(|ls: LanguageServer| {
|
||||||
spawn(async move {
|
spawn(async move {
|
||||||
if let Err(err) =
|
if let Err(err) =
|
||||||
ls.cache_request(specifiers, referrer).await
|
ls.cache(specifiers, referrer, false).await
|
||||||
{
|
{
|
||||||
lsp_warn!("{}", err);
|
lsp_warn!("{}", err);
|
||||||
}
|
}
|
||||||
|
@ -2477,6 +2502,7 @@ impl Inner {
|
||||||
&self.config.snapshot(),
|
&self.config.snapshot(),
|
||||||
&self.client,
|
&self.client,
|
||||||
&self.module_registries,
|
&self.module_registries,
|
||||||
|
&self.jsr_search_api,
|
||||||
&self.npm.search_api,
|
&self.npm.search_api,
|
||||||
&self.documents,
|
&self.documents,
|
||||||
self.maybe_import_map.clone(),
|
self.maybe_import_map.clone(),
|
||||||
|
@ -3166,14 +3192,20 @@ impl tower_lsp::LanguageServer for LanguageServer {
|
||||||
params: ExecuteCommandParams,
|
params: ExecuteCommandParams,
|
||||||
) -> LspResult<Option<Value>> {
|
) -> LspResult<Option<Value>> {
|
||||||
if params.command == "deno.cache" {
|
if params.command == "deno.cache" {
|
||||||
let mut arguments = params.arguments.into_iter();
|
#[derive(Default, Deserialize)]
|
||||||
let specifiers = serde_json::to_value(arguments.next()).unwrap();
|
#[serde(rename_all = "camelCase")]
|
||||||
let specifiers: Vec<Url> = serde_json::from_value(specifiers)
|
struct Options {
|
||||||
.map_err(|err| LspError::invalid_params(err.to_string()))?;
|
#[serde(default)]
|
||||||
let referrer = serde_json::to_value(arguments.next()).unwrap();
|
force_global_cache: bool,
|
||||||
let referrer: Url = serde_json::from_value(referrer)
|
}
|
||||||
.map_err(|err| LspError::invalid_params(err.to_string()))?;
|
#[derive(Deserialize)]
|
||||||
self.cache_request(specifiers, referrer).await
|
struct Arguments(Vec<Url>, Url, #[serde(default)] Options);
|
||||||
|
let Arguments(specifiers, referrer, options) =
|
||||||
|
serde_json::from_value(json!(params.arguments))
|
||||||
|
.map_err(|err| LspError::invalid_params(err.to_string()))?;
|
||||||
|
self
|
||||||
|
.cache(specifiers, referrer, options.force_global_cache)
|
||||||
|
.await
|
||||||
} else if params.command == "deno.reloadImportRegistries" {
|
} else if params.command == "deno.reloadImportRegistries" {
|
||||||
self.0.write().await.reload_import_registries().await
|
self.0.write().await.reload_import_registries().await
|
||||||
} else {
|
} else {
|
||||||
|
@ -3374,7 +3406,7 @@ impl tower_lsp::LanguageServer for LanguageServer {
|
||||||
}
|
}
|
||||||
specifier
|
specifier
|
||||||
};
|
};
|
||||||
if let Err(err) = self.cache_request(vec![], specifier.clone()).await {
|
if let Err(err) = self.cache(vec![], specifier.clone(), false).await {
|
||||||
lsp_warn!("Failed to cache \"{}\" on save: {}", &specifier, err);
|
lsp_warn!("Failed to cache \"{}\" on save: {}", &specifier, err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3621,6 +3653,7 @@ impl Inner {
|
||||||
&self,
|
&self,
|
||||||
specifiers: Vec<ModuleSpecifier>,
|
specifiers: Vec<ModuleSpecifier>,
|
||||||
referrer: ModuleSpecifier,
|
referrer: ModuleSpecifier,
|
||||||
|
force_global_cache: bool,
|
||||||
) -> Result<Option<PrepareCacheResult>, AnyError> {
|
) -> Result<Option<PrepareCacheResult>, AnyError> {
|
||||||
let mark = self
|
let mark = self
|
||||||
.performance
|
.performance
|
||||||
|
@ -3650,6 +3683,7 @@ impl Inner {
|
||||||
self.config.maybe_config_file().cloned(),
|
self.config.maybe_config_file().cloned(),
|
||||||
self.config.maybe_lockfile().cloned(),
|
self.config.maybe_lockfile().cloned(),
|
||||||
self.maybe_package_json.clone(),
|
self.maybe_package_json.clone(),
|
||||||
|
force_global_cache,
|
||||||
)?;
|
)?;
|
||||||
cli_options.set_import_map_specifier(
|
cli_options.set_import_map_specifier(
|
||||||
self.maybe_import_map.as_ref().map(|m| m.base_url().clone()),
|
self.maybe_import_map.as_ref().map(|m| m.base_url().clone()),
|
||||||
|
|
|
@ -21,7 +21,7 @@ mod completions;
|
||||||
mod config;
|
mod config;
|
||||||
mod diagnostics;
|
mod diagnostics;
|
||||||
mod documents;
|
mod documents;
|
||||||
mod jsr_resolver;
|
mod jsr;
|
||||||
pub mod language_server;
|
pub mod language_server;
|
||||||
mod logging;
|
mod logging;
|
||||||
mod lsp_custom;
|
mod lsp_custom;
|
||||||
|
@ -32,6 +32,7 @@ mod performance;
|
||||||
mod refactor;
|
mod refactor;
|
||||||
mod registries;
|
mod registries;
|
||||||
mod repl;
|
mod repl;
|
||||||
|
mod search;
|
||||||
mod semantic_tokens;
|
mod semantic_tokens;
|
||||||
mod testing;
|
mod testing;
|
||||||
mod text;
|
mod text;
|
||||||
|
|
|
@ -1,56 +1,45 @@
|
||||||
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use dashmap::DashMap;
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use deno_core::anyhow::anyhow;
|
use deno_core::anyhow::anyhow;
|
||||||
use deno_core::error::AnyError;
|
use deno_core::error::AnyError;
|
||||||
use deno_core::parking_lot::Mutex;
|
|
||||||
use deno_core::serde_json;
|
use deno_core::serde_json;
|
||||||
use deno_core::url::Url;
|
|
||||||
use deno_npm::registry::NpmPackageInfo;
|
use deno_npm::registry::NpmPackageInfo;
|
||||||
use deno_runtime::permissions::PermissionsContainer;
|
use deno_runtime::permissions::PermissionsContainer;
|
||||||
|
use deno_semver::package::PackageNv;
|
||||||
|
use deno_semver::Version;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::args::npm_registry_default_url;
|
use crate::args::npm_registry_default_url;
|
||||||
use crate::file_fetcher::FileFetcher;
|
use crate::file_fetcher::FileFetcher;
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
use super::search::PackageSearchApi;
|
||||||
pub trait NpmSearchApi {
|
|
||||||
async fn search(&self, query: &str) -> Result<Arc<Vec<String>>, AnyError>;
|
|
||||||
async fn package_info(
|
|
||||||
&self,
|
|
||||||
name: &str,
|
|
||||||
) -> Result<Arc<NpmPackageInfo>, AnyError>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct CliNpmSearchApi {
|
pub struct CliNpmSearchApi {
|
||||||
base_url: Url,
|
|
||||||
file_fetcher: FileFetcher,
|
file_fetcher: FileFetcher,
|
||||||
info_cache: Arc<Mutex<HashMap<String, Arc<NpmPackageInfo>>>>,
|
search_cache: Arc<DashMap<String, Arc<Vec<String>>>>,
|
||||||
search_cache: Arc<Mutex<HashMap<String, Arc<Vec<String>>>>>,
|
versions_cache: Arc<DashMap<String, Arc<Vec<Version>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CliNpmSearchApi {
|
impl CliNpmSearchApi {
|
||||||
pub fn new(file_fetcher: FileFetcher, custom_base_url: Option<Url>) -> Self {
|
pub fn new(file_fetcher: FileFetcher) -> Self {
|
||||||
Self {
|
Self {
|
||||||
base_url: custom_base_url
|
|
||||||
.unwrap_or_else(|| npm_registry_default_url().clone()),
|
|
||||||
file_fetcher,
|
file_fetcher,
|
||||||
info_cache: Default::default(),
|
|
||||||
search_cache: Default::default(),
|
search_cache: Default::default(),
|
||||||
|
versions_cache: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl NpmSearchApi for CliNpmSearchApi {
|
impl PackageSearchApi for CliNpmSearchApi {
|
||||||
async fn search(&self, query: &str) -> Result<Arc<Vec<String>>, AnyError> {
|
async fn search(&self, query: &str) -> Result<Arc<Vec<String>>, AnyError> {
|
||||||
if let Some(names) = self.search_cache.lock().get(query) {
|
if let Some(names) = self.search_cache.get(query) {
|
||||||
return Ok(names.clone());
|
return Ok(names.clone());
|
||||||
}
|
}
|
||||||
let mut search_url = self.base_url.clone();
|
let mut search_url = npm_registry_default_url().clone();
|
||||||
search_url
|
search_url
|
||||||
.path_segments_mut()
|
.path_segments_mut()
|
||||||
.map_err(|_| anyhow!("Custom npm registry URL cannot be a base."))?
|
.map_err(|_| anyhow!("Custom npm registry URL cannot be a base."))?
|
||||||
|
@ -65,21 +54,15 @@ impl NpmSearchApi for CliNpmSearchApi {
|
||||||
.await?
|
.await?
|
||||||
.into_text_decoded()?;
|
.into_text_decoded()?;
|
||||||
let names = Arc::new(parse_npm_search_response(&file.source)?);
|
let names = Arc::new(parse_npm_search_response(&file.source)?);
|
||||||
self
|
self.search_cache.insert(query.to_string(), names.clone());
|
||||||
.search_cache
|
|
||||||
.lock()
|
|
||||||
.insert(query.to_string(), names.clone());
|
|
||||||
Ok(names)
|
Ok(names)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn package_info(
|
async fn versions(&self, name: &str) -> Result<Arc<Vec<Version>>, AnyError> {
|
||||||
&self,
|
if let Some(versions) = self.versions_cache.get(name) {
|
||||||
name: &str,
|
return Ok(versions.clone());
|
||||||
) -> Result<Arc<NpmPackageInfo>, AnyError> {
|
|
||||||
if let Some(info) = self.info_cache.lock().get(name) {
|
|
||||||
return Ok(info.clone());
|
|
||||||
}
|
}
|
||||||
let mut info_url = self.base_url.clone();
|
let mut info_url = npm_registry_default_url().clone();
|
||||||
info_url
|
info_url
|
||||||
.path_segments_mut()
|
.path_segments_mut()
|
||||||
.map_err(|_| anyhow!("Custom npm registry URL cannot be a base."))?
|
.map_err(|_| anyhow!("Custom npm registry URL cannot be a base."))?
|
||||||
|
@ -89,13 +72,22 @@ impl NpmSearchApi for CliNpmSearchApi {
|
||||||
.file_fetcher
|
.file_fetcher
|
||||||
.fetch(&info_url, PermissionsContainer::allow_all())
|
.fetch(&info_url, PermissionsContainer::allow_all())
|
||||||
.await?;
|
.await?;
|
||||||
let info =
|
let info = serde_json::from_slice::<NpmPackageInfo>(&file.source)?;
|
||||||
Arc::new(serde_json::from_slice::<NpmPackageInfo>(&file.source)?);
|
let mut versions = info.versions.into_keys().collect::<Vec<_>>();
|
||||||
|
versions.sort();
|
||||||
|
versions.reverse();
|
||||||
|
let versions = Arc::new(versions);
|
||||||
self
|
self
|
||||||
.info_cache
|
.versions_cache
|
||||||
.lock()
|
.insert(name.to_string(), versions.clone());
|
||||||
.insert(name.to_string(), info.clone());
|
Ok(versions)
|
||||||
Ok(info)
|
}
|
||||||
|
|
||||||
|
async fn exports(
|
||||||
|
&self,
|
||||||
|
_nv: &PackageNv,
|
||||||
|
) -> Result<Arc<Vec<String>>, AnyError> {
|
||||||
|
Ok(Default::default())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
79
cli/lsp/search.rs
Normal file
79
cli/lsp/search.rs
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
use deno_core::error::AnyError;
|
||||||
|
use deno_semver::package::PackageNv;
|
||||||
|
use deno_semver::Version;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
pub trait PackageSearchApi {
|
||||||
|
async fn search(&self, query: &str) -> Result<Arc<Vec<String>>, AnyError>;
|
||||||
|
async fn versions(&self, name: &str) -> Result<Arc<Vec<Version>>, AnyError>;
|
||||||
|
async fn exports(&self, nv: &PackageNv)
|
||||||
|
-> Result<Arc<Vec<String>>, AnyError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub mod tests {
|
||||||
|
use super::*;
|
||||||
|
use deno_core::anyhow::anyhow;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct TestPackageSearchApi {
|
||||||
|
/// [(name -> [(version -> [export])])]
|
||||||
|
package_versions: BTreeMap<String, BTreeMap<Version, Vec<String>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestPackageSearchApi {
|
||||||
|
pub fn with_package_version(
|
||||||
|
mut self,
|
||||||
|
name: &str,
|
||||||
|
version: &str,
|
||||||
|
exports: &[&str],
|
||||||
|
) -> Self {
|
||||||
|
let exports_by_version =
|
||||||
|
self.package_versions.entry(name.to_string()).or_default();
|
||||||
|
exports_by_version.insert(
|
||||||
|
Version::parse_standard(version).unwrap(),
|
||||||
|
exports.iter().map(|s| s.to_string()).collect(),
|
||||||
|
);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl PackageSearchApi for TestPackageSearchApi {
|
||||||
|
async fn search(&self, query: &str) -> Result<Arc<Vec<String>>, AnyError> {
|
||||||
|
let names = self
|
||||||
|
.package_versions
|
||||||
|
.keys()
|
||||||
|
.filter_map(|n| n.contains(query).then(|| n.clone()))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
Ok(Arc::new(names))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn versions(
|
||||||
|
&self,
|
||||||
|
name: &str,
|
||||||
|
) -> Result<Arc<Vec<Version>>, AnyError> {
|
||||||
|
let Some(exports_by_version) = self.package_versions.get(name) else {
|
||||||
|
return Err(anyhow!("Package not found."));
|
||||||
|
};
|
||||||
|
Ok(Arc::new(exports_by_version.keys().rev().cloned().collect()))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn exports(
|
||||||
|
&self,
|
||||||
|
nv: &PackageNv,
|
||||||
|
) -> Result<Arc<Vec<String>>, AnyError> {
|
||||||
|
let Some(exports_by_version) = self.package_versions.get(&nv.name) else {
|
||||||
|
return Err(anyhow!("Package not found."));
|
||||||
|
};
|
||||||
|
let Some(exports) = exports_by_version.get(&nv.version) else {
|
||||||
|
return Err(anyhow!("Package version not found."));
|
||||||
|
};
|
||||||
|
Ok(Arc::new(exports.clone()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue