1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-24 15:19:26 -05:00

feat(lsp): multi deno.json resolver scopes (#24206)

This commit is contained in:
Nayeem Rahman 2024-06-17 21:54:23 +01:00 committed by Bartek Iwańczuk
parent 10828cd62c
commit b318d51822
No known key found for this signature in database
GPG key ID: 0C6BCDDC3B3AD750
10 changed files with 762 additions and 255 deletions

View file

@ -11,6 +11,7 @@ use deno_runtime::fs_util::specifier_to_file_path;
use deno_core::url::Url;
use deno_core::ModuleSpecifier;
use std::collections::BTreeMap;
use std::fs;
use std::path::Path;
use std::sync::Arc;
@ -29,13 +30,14 @@ pub const LSP_DISALLOW_GLOBAL_TO_LOCAL_COPY: deno_cache_dir::GlobalToLocalCopy =
pub fn calculate_fs_version(
cache: &LspCache,
specifier: &ModuleSpecifier,
file_referrer: Option<&ModuleSpecifier>,
) -> Option<String> {
match specifier.scheme() {
"npm" | "node" | "data" | "blob" => None,
"file" => specifier_to_file_path(specifier)
.ok()
.and_then(|path| calculate_fs_version_at_path(&path)),
_ => calculate_fs_version_in_cache(cache, specifier),
_ => calculate_fs_version_in_cache(cache, specifier, file_referrer),
}
}
@ -56,8 +58,9 @@ pub fn calculate_fs_version_at_path(path: &Path) -> Option<String> {
fn calculate_fs_version_in_cache(
cache: &LspCache,
specifier: &ModuleSpecifier,
file_referrer: Option<&ModuleSpecifier>,
) -> Option<String> {
let http_cache = cache.root_vendor_or_global();
let http_cache = cache.for_specifier(file_referrer);
let Ok(cache_key) = http_cache.cache_item_key(specifier) else {
return Some("1".to_string());
};
@ -77,7 +80,7 @@ fn calculate_fs_version_in_cache(
pub struct LspCache {
deno_dir: DenoDir,
global: Arc<GlobalHttpCache>,
root_vendor: Option<Arc<LocalLspHttpCache>>,
vendors_by_scope: BTreeMap<ModuleSpecifier, Option<Arc<LocalLspHttpCache>>>,
}
impl Default for LspCache {
@ -107,18 +110,24 @@ impl LspCache {
Self {
deno_dir,
global,
root_vendor: None,
vendors_by_scope: Default::default(),
}
}
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(),
)))
});
self.vendors_by_scope = config
.tree
.data_by_scope()
.iter()
.map(|(scope, config_data)| {
(
scope.clone(),
config_data.vendor_dir.as_ref().map(|v| {
Arc::new(LocalLspHttpCache::new(v.clone(), self.global.clone()))
}),
)
})
.collect();
}
pub fn deno_dir(&self) -> &DenoDir {
@ -129,15 +138,50 @@ impl LspCache {
&self.global
}
pub fn root_vendor(&self) -> Option<&Arc<LocalLspHttpCache>> {
self.root_vendor.as_ref()
}
pub fn root_vendor_or_global(&self) -> Arc<dyn HttpCache> {
pub fn for_specifier(
&self,
file_referrer: Option<&ModuleSpecifier>,
) -> Arc<dyn HttpCache> {
let Some(file_referrer) = file_referrer else {
return self.global.clone();
};
self
.root_vendor
.as_ref()
.map(|v| v.clone() as _)
.vendors_by_scope
.iter()
.rfind(|(s, _)| file_referrer.as_str().starts_with(s.as_str()))
.and_then(|(_, v)| v.clone().map(|v| v as _))
.unwrap_or(self.global.clone() as _)
}
pub fn vendored_specifier(
&self,
specifier: &ModuleSpecifier,
file_referrer: Option<&ModuleSpecifier>,
) -> Option<ModuleSpecifier> {
let file_referrer = file_referrer?;
if !matches!(specifier.scheme(), "http" | "https") {
return None;
}
let vendor = self
.vendors_by_scope
.iter()
.rfind(|(s, _)| file_referrer.as_str().starts_with(s.as_str()))?
.1
.as_ref()?;
vendor.get_file_url(specifier)
}
pub fn unvendored_specifier(
&self,
specifier: &ModuleSpecifier,
) -> Option<ModuleSpecifier> {
let path = specifier_to_file_path(specifier).ok()?;
let vendor = self
.vendors_by_scope
.iter()
.rfind(|(s, _)| specifier.as_str().starts_with(s.as_str()))?
.1
.as_ref()?;
vendor.get_remote_url(&path)
}
}

View file

@ -340,7 +340,7 @@ async fn resolve_references_code_lens(
locations.push(
reference
.entry
.to_location(asset_or_doc.line_index(), &language_server.url_map),
.to_location(asset_or_doc.line_index(), language_server),
);
}
Ok(locations)

View file

@ -1568,27 +1568,13 @@ impl ConfigData {
#[derive(Clone, Debug, Default)]
pub struct ConfigTree {
first_folder: Option<ModuleSpecifier>,
scopes: Arc<BTreeMap<ModuleSpecifier, ConfigData>>,
scopes: Arc<BTreeMap<ModuleSpecifier, Arc<ConfigData>>>,
}
impl ConfigTree {
pub fn root_scope(&self) -> Option<&ModuleSpecifier> {
self.first_folder.as_ref()
}
pub fn root_data(&self) -> Option<&ConfigData> {
self.first_folder.as_ref().and_then(|s| self.scopes.get(s))
}
pub fn root_ts_config(&self) -> Arc<LspTsConfig> {
self
.root_data()
.map(|d| d.ts_config.clone())
.unwrap_or_default()
}
pub fn root_import_map(&self) -> Option<&Arc<ImportMap>> {
self.root_data().and_then(|d| d.import_map.as_ref())
let root_data = self.first_folder.as_ref().and_then(|s| self.scopes.get(s));
root_data.map(|d| d.ts_config.clone()).unwrap_or_default()
}
pub fn scope_for_specifier(
@ -1599,19 +1585,20 @@ impl ConfigTree {
.scopes
.keys()
.rfind(|s| specifier.as_str().starts_with(s.as_str()))
.or(self.first_folder.as_ref())
}
pub fn data_for_specifier(
&self,
specifier: &ModuleSpecifier,
) -> Option<&ConfigData> {
) -> Option<&Arc<ConfigData>> {
self
.scope_for_specifier(specifier)
.and_then(|s| self.scopes.get(s))
}
pub fn data_by_scope(&self) -> &Arc<BTreeMap<ModuleSpecifier, ConfigData>> {
pub fn data_by_scope(
&self,
) -> &Arc<BTreeMap<ModuleSpecifier, Arc<ConfigData>>> {
&self.scopes
}
@ -1694,6 +1681,7 @@ impl ConfigTree {
if let Ok(config_uri) = folder_uri.join(config_path) {
scopes.insert(
folder_uri.clone(),
Arc::new(
ConfigData::load(
Some(&config_uri),
folder_uri,
@ -1702,6 +1690,7 @@ impl ConfigTree {
Some(file_fetcher),
)
.await,
),
);
}
}
@ -1756,10 +1745,10 @@ impl ConfigTree {
Some(file_fetcher),
)
.await;
scopes.insert(member_scope.clone(), member_data);
scopes.insert(member_scope.clone(), Arc::new(member_data));
}
}
scopes.insert(scope, data);
scopes.insert(scope, Arc::new(data));
}
for folder_uri in settings.by_workspace_folder.keys() {
@ -1769,6 +1758,7 @@ impl ConfigTree {
{
scopes.insert(
folder_uri.clone(),
Arc::new(
ConfigData::load(
None,
folder_uri,
@ -1777,6 +1767,7 @@ impl ConfigTree {
Some(file_fetcher),
)
.await,
),
);
}
}
@ -1787,14 +1778,16 @@ impl ConfigTree {
#[cfg(test)]
pub async fn inject_config_file(&mut self, config_file: ConfigFile) {
let scope = config_file.specifier.join(".").unwrap();
let data = ConfigData::load_inner(
let data = Arc::new(
ConfigData::load_inner(
Some(config_file),
&scope,
None,
&Default::default(),
None,
)
.await;
.await,
);
self.first_folder = Some(scope.clone());
self.scopes = Arc::new([(scope, data)].into_iter().collect());
}

View file

@ -5,6 +5,7 @@ use super::client::Client;
use super::config::Config;
use super::documents;
use super::documents::Document;
use super::documents::Documents;
use super::documents::DocumentsFilter;
use super::language_server;
use super::language_server::StateSnapshot;
@ -120,6 +121,7 @@ impl DiagnosticsPublisher {
source: DiagnosticSource,
diagnostics: DiagnosticVec,
url_map: &LspUrlMap,
documents: &Documents,
token: &CancellationToken,
) -> usize {
let mut diagnostics_by_specifier =
@ -153,11 +155,12 @@ impl DiagnosticsPublisher {
self
.state
.update(&record.specifier, version, &all_specifier_diagnostics);
let file_referrer = documents.get_file_referrer(&record.specifier);
self
.client
.publish_diagnostics(
url_map
.normalize_specifier(&record.specifier)
.normalize_specifier(&record.specifier, file_referrer.as_deref())
.unwrap_or(LspClientUrl::new(record.specifier)),
all_specifier_diagnostics,
version,
@ -183,11 +186,12 @@ impl DiagnosticsPublisher {
if let Some(removed_value) = maybe_removed_value {
// clear out any diagnostics for this specifier
self.state.update(specifier, removed_value.version, &[]);
let file_referrer = documents.get_file_referrer(specifier);
self
.client
.publish_diagnostics(
url_map
.normalize_specifier(specifier)
.normalize_specifier(specifier, file_referrer.as_deref())
.unwrap_or_else(|_| LspClientUrl::new(specifier.clone())),
Vec::new(),
removed_value.version,
@ -519,6 +523,7 @@ impl DiagnosticsServer {
DiagnosticSource::Ts,
diagnostics,
&url_map,
snapshot.documents.as_ref(),
&token,
)
.await;
@ -556,6 +561,7 @@ impl DiagnosticsServer {
let mark = performance.mark("lsp.update_diagnostics_deps");
let diagnostics = spawn_blocking({
let token = token.clone();
let snapshot = snapshot.clone();
move || generate_deno_diagnostics(&snapshot, &config, token)
})
.await
@ -568,6 +574,7 @@ impl DiagnosticsServer {
DiagnosticSource::Deno,
diagnostics,
&url_map,
snapshot.documents.as_ref(),
&token,
)
.await;
@ -605,6 +612,7 @@ impl DiagnosticsServer {
let mark = performance.mark("lsp.update_diagnostics_lint");
let diagnostics = spawn_blocking({
let token = token.clone();
let snapshot = snapshot.clone();
move || generate_lint_diagnostics(&snapshot, &config, token)
})
.await
@ -617,6 +625,7 @@ impl DiagnosticsServer {
DiagnosticSource::Lint,
diagnostics,
&url_map,
snapshot.documents.as_ref(),
&token,
)
.await;
@ -1466,7 +1475,11 @@ fn diagnose_dependency(
return; // ignore, surface typescript errors instead
}
let import_map = snapshot.config.tree.root_import_map();
let import_map = snapshot
.config
.tree
.data_for_specifier(referrer_doc.file_referrer().unwrap_or(referrer))
.and_then(|d| d.import_map.as_ref());
if let Some(import_map) = import_map {
if let Resolution::Ok(resolved) = &dependency.maybe_code {
if let Some(to) = import_map.lookup(&resolved.specifier, referrer) {

View file

@ -303,6 +303,10 @@ impl Document {
cache: &Arc<LspCache>,
file_referrer: Option<ModuleSpecifier>,
) -> Arc<Self> {
let file_referrer = Some(&specifier)
.filter(|s| s.scheme() == "file")
.cloned()
.or(file_referrer);
let media_type = resolve_media_type(
&specifier,
maybe_headers.as_ref(),
@ -336,9 +340,13 @@ impl Document {
Arc::new(Self {
config,
dependencies,
file_referrer: file_referrer.filter(|_| specifier.scheme() != "file"),
maybe_fs_version: calculate_fs_version(
cache,
&specifier,
file_referrer.as_ref(),
),
file_referrer,
maybe_types_dependency,
maybe_fs_version: calculate_fs_version(cache, &specifier),
line_index,
maybe_language_id,
maybe_headers,
@ -540,7 +548,11 @@ impl Document {
config: self.config.clone(),
specifier: self.specifier.clone(),
file_referrer: self.file_referrer.clone(),
maybe_fs_version: calculate_fs_version(cache, &self.specifier),
maybe_fs_version: calculate_fs_version(
cache,
&self.specifier,
self.file_referrer.as_ref(),
),
maybe_language_id: self.maybe_language_id,
dependencies: self.dependencies.clone(),
maybe_types_dependency: self.maybe_types_dependency.clone(),
@ -563,7 +575,11 @@ impl Document {
config: self.config.clone(),
specifier: self.specifier.clone(),
file_referrer: self.file_referrer.clone(),
maybe_fs_version: calculate_fs_version(cache, &self.specifier),
maybe_fs_version: calculate_fs_version(
cache,
&self.specifier,
self.file_referrer.as_ref(),
),
maybe_language_id: self.maybe_language_id,
dependencies: self.dependencies.clone(),
maybe_types_dependency: self.maybe_types_dependency.clone(),
@ -766,7 +782,10 @@ impl FileSystemDocuments {
cache: &Arc<LspCache>,
file_referrer: Option<&ModuleSpecifier>,
) -> Option<Arc<Document>> {
let new_fs_version = calculate_fs_version(cache, specifier);
let file_referrer = Some(specifier)
.filter(|s| s.scheme() == "file")
.or(file_referrer);
let new_fs_version = calculate_fs_version(cache, specifier, file_referrer);
let old_doc = self.docs.get(specifier).map(|v| v.value().clone());
let dirty = match &old_doc {
None => true,
@ -830,7 +849,7 @@ impl FileSystemDocuments {
file_referrer.cloned(),
)
} else {
let http_cache = cache.root_vendor_or_global();
let http_cache = cache.for_specifier(file_referrer);
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)
@ -1089,7 +1108,7 @@ impl Documents {
.map(|p| p.is_file())
.unwrap_or(false);
}
if self.cache.root_vendor_or_global().contains(&specifier) {
if self.cache.for_specifier(file_referrer).contains(&specifier) {
return true;
}
}
@ -1335,8 +1354,7 @@ impl Documents {
let mut visit_doc = |doc: &Arc<Document>| {
let scope = doc
.file_referrer()
.and_then(|r| self.config.tree.scope_for_specifier(r))
.or(self.config.tree.root_scope());
.and_then(|r| self.config.tree.scope_for_specifier(r));
let reqs = npm_reqs_by_scope.entry(scope.cloned()).or_default();
for dependency in doc.dependencies().values() {
if let Some(dep) = dependency.get_code() {
@ -1367,16 +1385,9 @@ impl Documents {
}
// fill the reqs from the lockfile
// TODO(nayeemrmn): Iterate every lockfile here for multi-deno.json.
if let Some(lockfile) = self
.config
.tree
.root_data()
.and_then(|d| d.lockfile.as_ref())
{
let reqs = npm_reqs_by_scope
.entry(self.config.tree.root_scope().cloned())
.or_default();
for (scope, config_data) in self.config.tree.data_by_scope().as_ref() {
if let Some(lockfile) = config_data.lockfile.as_ref() {
let reqs = npm_reqs_by_scope.entry(Some(scope.clone())).or_default();
let lockfile = lockfile.lock();
for key in lockfile.content.packages.specifiers.keys() {
if let Some(key) = key.strip_prefix("npm:") {
@ -1386,6 +1397,7 @@ impl Documents {
}
}
}
}
// Ensure a @types/node package exists when any module uses a node: specifier.
// Unlike on the command line, here we just add @types/node to the npm package

View file

@ -682,7 +682,7 @@ impl Inner {
pub fn update_cache(&mut self) {
let mark = self.performance.mark("lsp.update_cache");
self.cache.update_config(&self.config);
self.url_map.set_cache(self.cache.root_vendor().cloned());
self.url_map.set_cache(&self.cache);
self.performance.measure(mark);
}
@ -1134,11 +1134,9 @@ impl Inner {
let package_reqs = self.documents.npm_reqs_by_scope();
let resolver = self.resolver.clone();
// spawn due to the lsp's `Send` requirement
let handle =
spawn(async move { resolver.set_npm_reqs(&package_reqs).await });
if let Err(err) = handle.await.unwrap() {
lsp_warn!("Could not set npm package requirements. {:#}", err);
}
spawn(async move { resolver.set_npm_reqs(&package_reqs).await })
.await
.ok();
}
async fn did_close(&mut self, params: DidCloseTextDocumentParams) {
@ -1818,11 +1816,15 @@ impl Inner {
pub fn get_ts_response_import_mapper(
&self,
_referrer: &ModuleSpecifier,
file_referrer: &ModuleSpecifier,
) -> TsResponseImportMapper {
TsResponseImportMapper::new(
&self.documents,
self.config.tree.root_import_map().map(|i| i.as_ref()),
self
.config
.tree
.data_for_specifier(file_referrer)
.and_then(|d| d.import_map.as_ref().map(|i| i.as_ref())),
self.resolver.as_ref(),
)
}
@ -1999,11 +2001,7 @@ impl Inner {
self.get_asset_or_document(&reference_specifier)?;
asset_or_doc.line_index()
};
results.push(
reference
.entry
.to_location(reference_line_index, &self.url_map),
);
results.push(reference.entry.to_location(reference_line_index, self));
}
self.performance.measure(mark);
@ -2125,6 +2123,10 @@ impl Inner {
.map(|s| s.suggest.include_completions_for_import_statements)
.unwrap_or(true)
{
let file_referrer = asset_or_doc
.document()
.and_then(|d| d.file_referrer())
.unwrap_or(&specifier);
response = completions::get_import_completions(
&specifier,
&params.text_document_position.position,
@ -2135,7 +2137,11 @@ impl Inner {
&self.npm_search_api,
&self.documents,
self.resolver.as_ref(),
self.config.tree.root_import_map().map(|i| i.as_ref()),
self
.config
.tree
.data_for_specifier(file_referrer)
.and_then(|d| d.import_map.as_ref().map(|i| i.as_ref())),
)
.await;
}
@ -3442,7 +3448,7 @@ impl Inner {
let mark = self
.performance
.mark_with_args("lsp.cache", (&specifiers, &referrer));
let config_data = self.config.tree.root_data();
let config_data = self.config.tree.data_for_specifier(&referrer);
let mut roots = if !specifiers.is_empty() {
specifiers
} else {
@ -3451,16 +3457,17 @@ impl Inner {
// always include the npm packages since resolution of one npm package
// might affect the resolution of other npm packages
roots.extend(
self
if let Some(npm_reqs) = self
.documents
.npm_reqs_by_scope()
.values()
.flatten()
.collect::<BTreeSet<_>>()
.get(&config_data.map(|d| d.scope.clone()))
{
roots.extend(
npm_reqs
.iter()
.map(|req| ModuleSpecifier::parse(&format!("npm:{}", req)).unwrap()),
);
}
let workspace_settings = self.config.workspace_settings();
let cli_options = CliOptions::new(

View file

@ -7,6 +7,7 @@ use crate::graph_util::CliJsrUrlProvider;
use crate::http_util::HttpClientProvider;
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;
@ -54,17 +55,17 @@ use super::cache::LspCache;
use super::jsr::JsrCacheResolver;
#[derive(Debug, Clone)]
pub struct LspResolver {
struct LspScopeResolver {
graph_resolver: Arc<CliGraphResolver>,
jsr_resolver: Option<Arc<JsrCacheResolver>>,
npm_resolver: Option<Arc<dyn CliNpmResolver>>,
node_resolver: Option<Arc<CliNodeResolver>>,
redirect_resolver: Option<Arc<RedirectResolver>>,
graph_imports: Arc<IndexMap<ModuleSpecifier, GraphImport>>,
config: Arc<Config>,
config_data: Option<Arc<ConfigData>>,
}
impl Default for LspResolver {
impl Default for LspScopeResolver {
fn default() -> Self {
Self {
graph_resolver: create_graph_resolver(None, None, None),
@ -73,38 +74,41 @@ impl Default for LspResolver {
node_resolver: None,
redirect_resolver: None,
graph_imports: Default::default(),
config: Default::default(),
config_data: None,
}
}
}
impl LspResolver {
pub async fn from_config(
impl LspScopeResolver {
async fn from_config_data(
config_data: Option<&Arc<ConfigData>>,
config: &Config,
cache: &LspCache,
http_client_provider: Option<&Arc<HttpClientProvider>>,
) -> Self {
let config_data = config.tree.root_data();
let mut npm_resolver = None;
let mut node_resolver = None;
if let (Some(http_client), Some(config_data)) =
(http_client_provider, config_data)
{
npm_resolver = create_npm_resolver(config_data, cache, http_client).await;
if let Some(http_client) = http_client_provider {
npm_resolver = create_npm_resolver(
config_data.map(|d| d.as_ref()),
cache,
http_client,
)
.await;
node_resolver = create_node_resolver(npm_resolver.as_ref());
}
let graph_resolver = create_graph_resolver(
config_data,
config_data.map(|d| d.as_ref()),
npm_resolver.as_ref(),
node_resolver.as_ref(),
);
let jsr_resolver = Some(Arc::new(JsrCacheResolver::new(
cache.root_vendor_or_global(),
config_data,
cache.for_specifier(config_data.map(|d| &d.scope)),
config_data.map(|d| d.as_ref()),
config,
)));
let redirect_resolver = Some(Arc::new(RedirectResolver::new(
cache.root_vendor_or_global(),
cache.for_specifier(config_data.map(|d| &d.scope)),
)));
let npm_graph_resolver = graph_resolver.create_graph_npm_resolver();
let graph_imports = config_data
@ -135,16 +139,16 @@ impl LspResolver {
node_resolver,
redirect_resolver,
graph_imports,
config: Arc::new(config.clone()),
config_data: config_data.cloned(),
}
}
pub fn snapshot(&self) -> Arc<Self> {
fn snapshot(&self) -> Arc<Self> {
let npm_resolver =
self.npm_resolver.as_ref().map(|r| r.clone_snapshotted());
let node_resolver = create_node_resolver(npm_resolver.as_ref());
let graph_resolver = create_graph_resolver(
self.config.tree.root_data(),
self.config_data.as_deref(),
npm_resolver.as_ref(),
node_resolver.as_ref(),
);
@ -155,61 +159,125 @@ impl LspResolver {
node_resolver,
redirect_resolver: self.redirect_resolver.clone(),
graph_imports: self.graph_imports.clone(),
config: self.config.clone(),
config_data: self.config_data.clone(),
})
}
}
#[derive(Debug, Default, Clone)]
pub struct LspResolver {
unscoped: Arc<LspScopeResolver>,
by_scope: BTreeMap<ModuleSpecifier, Arc<LspScopeResolver>>,
}
impl LspResolver {
pub async fn from_config(
config: &Config,
cache: &LspCache,
http_client_provider: Option<&Arc<HttpClientProvider>>,
) -> Self {
let mut by_scope = BTreeMap::new();
for (scope, config_data) in config.tree.data_by_scope().as_ref() {
by_scope.insert(
scope.clone(),
Arc::new(
LspScopeResolver::from_config_data(
Some(config_data),
config,
cache,
http_client_provider,
)
.await,
),
);
}
Self {
unscoped: Arc::new(
LspScopeResolver::from_config_data(
None,
config,
cache,
http_client_provider,
)
.await,
),
by_scope,
}
}
pub fn snapshot(&self) -> Arc<Self> {
Arc::new(Self {
unscoped: self.unscoped.snapshot(),
by_scope: self
.by_scope
.iter()
.map(|(s, r)| (s.clone(), r.snapshot()))
.collect(),
})
}
pub fn did_cache(&self) {
self.jsr_resolver.as_ref().inspect(|r| r.did_cache());
for resolver in
std::iter::once(&self.unscoped).chain(self.by_scope.values())
{
resolver.jsr_resolver.as_ref().inspect(|r| r.did_cache());
}
}
pub async fn set_npm_reqs(
&self,
reqs: &BTreeMap<Option<ModuleSpecifier>, BTreeSet<PackageReq>>,
) -> Result<(), AnyError> {
let reqs = reqs
.values()
.flatten()
.collect::<BTreeSet<_>>()
) {
for (scope, resolver) in [(None, &self.unscoped)]
.into_iter()
.cloned()
.collect::<Vec<_>>();
if let Some(npm_resolver) = self.npm_resolver.as_ref() {
.chain(self.by_scope.iter().map(|(s, r)| (Some(s), r)))
{
if let Some(npm_resolver) = resolver.npm_resolver.as_ref() {
if let Some(npm_resolver) = npm_resolver.as_managed() {
return npm_resolver.set_package_reqs(&reqs).await;
let reqs = reqs
.get(&scope.cloned())
.map(|reqs| reqs.iter().cloned().collect::<Vec<_>>())
.unwrap_or_default();
if let Err(err) = npm_resolver.set_package_reqs(&reqs).await {
lsp_warn!("Could not set npm package requirements: {:#}", err);
}
}
}
}
Ok(())
}
pub fn as_graph_resolver(
&self,
_file_referrer: Option<&ModuleSpecifier>,
file_referrer: Option<&ModuleSpecifier>,
) -> &dyn Resolver {
self.graph_resolver.as_ref()
let resolver = self.get_scope_resolver(file_referrer);
resolver.graph_resolver.as_ref()
}
pub fn create_graph_npm_resolver(
&self,
_file_referrer: Option<&ModuleSpecifier>,
file_referrer: Option<&ModuleSpecifier>,
) -> WorkerCliNpmGraphResolver {
self.graph_resolver.create_graph_npm_resolver()
let resolver = self.get_scope_resolver(file_referrer);
resolver.graph_resolver.create_graph_npm_resolver()
}
pub fn maybe_managed_npm_resolver(
&self,
_file_referrer: Option<&ModuleSpecifier>,
file_referrer: Option<&ModuleSpecifier>,
) -> Option<&ManagedCliNpmResolver> {
self.npm_resolver.as_ref().and_then(|r| r.as_managed())
let resolver = self.get_scope_resolver(file_referrer);
resolver.npm_resolver.as_ref().and_then(|r| r.as_managed())
}
pub fn graph_imports_by_referrer(
&self,
) -> IndexMap<&ModuleSpecifier, Vec<&ModuleSpecifier>> {
self
.graph_imports
.by_scope
.iter()
.map(|(s, i)| {
.flat_map(|(_, r)| {
r.graph_imports.iter().map(|(s, i)| {
(
s,
i.dependencies
@ -218,41 +286,49 @@ impl LspResolver {
.collect(),
)
})
})
.collect()
}
pub fn jsr_to_resource_url(
&self,
req_ref: &JsrPackageReqReference,
_file_referrer: Option<&ModuleSpecifier>,
file_referrer: Option<&ModuleSpecifier>,
) -> Option<ModuleSpecifier> {
self.jsr_resolver.as_ref()?.jsr_to_resource_url(req_ref)
let resolver = self.get_scope_resolver(file_referrer);
resolver.jsr_resolver.as_ref()?.jsr_to_resource_url(req_ref)
}
pub fn jsr_lookup_export_for_path(
&self,
nv: &PackageNv,
path: &str,
_file_referrer: Option<&ModuleSpecifier>,
file_referrer: Option<&ModuleSpecifier>,
) -> Option<String> {
self.jsr_resolver.as_ref()?.lookup_export_for_path(nv, path)
let resolver = self.get_scope_resolver(file_referrer);
resolver
.jsr_resolver
.as_ref()?
.lookup_export_for_path(nv, path)
}
pub fn jsr_lookup_req_for_nv(
&self,
nv: &PackageNv,
_file_referrer: Option<&ModuleSpecifier>,
file_referrer: Option<&ModuleSpecifier>,
) -> Option<PackageReq> {
self.jsr_resolver.as_ref()?.lookup_req_for_nv(nv)
let resolver = self.get_scope_resolver(file_referrer);
resolver.jsr_resolver.as_ref()?.lookup_req_for_nv(nv)
}
pub fn npm_to_file_url(
&self,
req_ref: &NpmPackageReqReference,
referrer: &ModuleSpecifier,
_file_referrer: Option<&ModuleSpecifier>,
file_referrer: Option<&ModuleSpecifier>,
) -> Option<(ModuleSpecifier, MediaType)> {
let node_resolver = self.node_resolver.as_ref()?;
let resolver = self.get_scope_resolver(file_referrer);
let node_resolver = resolver.node_resolver.as_ref()?;
Some(NodeResolution::into_specifier_and_media_type(
node_resolver
.resolve_req_reference(req_ref, referrer, NodeResolutionMode::Types)
@ -261,7 +337,8 @@ impl LspResolver {
}
pub fn in_node_modules(&self, specifier: &ModuleSpecifier) -> bool {
if let Some(npm_resolver) = &self.npm_resolver {
let resolver = self.get_scope_resolver(Some(specifier));
if let Some(npm_resolver) = &resolver.npm_resolver {
return npm_resolver.in_npm_package(specifier);
}
false
@ -271,7 +348,8 @@ impl LspResolver {
&self,
specifier: &ModuleSpecifier,
) -> Option<MediaType> {
let node_resolver = self.node_resolver.as_ref()?;
let resolver = self.get_scope_resolver(Some(specifier));
let node_resolver = resolver.node_resolver.as_ref()?;
let resolution = node_resolver
.url_to_node_resolution(specifier.clone())
.ok()?;
@ -282,7 +360,8 @@ impl LspResolver {
&self,
referrer: &ModuleSpecifier,
) -> Result<Option<Rc<PackageJson>>, AnyError> {
let Some(node_resolver) = self.node_resolver.as_ref() else {
let resolver = self.get_scope_resolver(Some(referrer));
let Some(node_resolver) = resolver.node_resolver.as_ref() else {
return Ok(None);
};
node_resolver.get_closest_package_json(
@ -294,9 +373,10 @@ impl LspResolver {
pub fn resolve_redirects(
&self,
specifier: &ModuleSpecifier,
_file_referrer: Option<&ModuleSpecifier>,
file_referrer: Option<&ModuleSpecifier>,
) -> Option<ModuleSpecifier> {
let Some(redirect_resolver) = self.redirect_resolver.as_ref() else {
let resolver = self.get_scope_resolver(file_referrer);
let Some(redirect_resolver) = resolver.redirect_resolver.as_ref() else {
return Some(specifier.clone());
};
redirect_resolver.resolve(specifier)
@ -305,9 +385,10 @@ impl LspResolver {
pub fn redirect_chain_headers(
&self,
specifier: &ModuleSpecifier,
_file_referrer: Option<&ModuleSpecifier>,
file_referrer: Option<&ModuleSpecifier>,
) -> Vec<(ModuleSpecifier, Arc<HashMap<String, String>>)> {
let Some(redirect_resolver) = self.redirect_resolver.as_ref() else {
let resolver = self.get_scope_resolver(file_referrer);
let Some(redirect_resolver) = resolver.redirect_resolver.as_ref() else {
return vec![];
};
redirect_resolver
@ -316,26 +397,47 @@ impl LspResolver {
.map(|(s, e)| (s, e.headers.clone()))
.collect()
}
fn get_scope_resolver(
&self,
file_referrer: Option<&ModuleSpecifier>,
) -> &LspScopeResolver {
let Some(file_referrer) = file_referrer else {
return self.unscoped.as_ref();
};
self
.by_scope
.iter()
.rfind(|(s, _)| file_referrer.as_str().starts_with(s.as_str()))
.map(|(_, r)| r.as_ref())
.unwrap_or(self.unscoped.as_ref())
}
}
async fn create_npm_resolver(
config_data: &ConfigData,
config_data: Option<&ConfigData>,
cache: &LspCache,
http_client_provider: &Arc<HttpClientProvider>,
) -> Option<Arc<dyn CliNpmResolver>> {
let node_modules_dir = config_data
.node_modules_dir
.clone()
.or_else(|| specifier_to_file_path(&config_data.scope).ok())?;
let options = if config_data.byonm {
let mut byonm_dir = None;
if let Some(config_data) = config_data {
if config_data.byonm {
byonm_dir = Some(config_data.node_modules_dir.clone().or_else(|| {
specifier_to_file_path(&config_data.scope)
.ok()
.map(|p| p.join("node_modules/"))
})?)
}
}
let options = if let Some(byonm_dir) = byonm_dir {
CliNpmResolverCreateOptions::Byonm(CliNpmResolverByonmCreateOptions {
fs: Arc::new(deno_fs::RealFs),
root_node_modules_dir: node_modules_dir.clone(),
root_node_modules_dir: byonm_dir,
})
} else {
CliNpmResolverCreateOptions::Managed(CliNpmResolverManagedCreateOptions {
http_client_provider: http_client_provider.clone(),
snapshot: match config_data.lockfile.as_ref() {
snapshot: match config_data.and_then(|d| d.lockfile.as_ref()) {
Some(lockfile) => {
CliNpmResolverManagedSnapshotOption::ResolveFromLockfile(
lockfile.clone(),
@ -354,15 +456,17 @@ async fn create_npm_resolver(
// the user is typing.
cache_setting: CacheSetting::Only,
text_only_progress_bar: ProgressBar::new(ProgressBarStyle::TextOnly),
maybe_node_modules_path: config_data.node_modules_dir.clone(),
maybe_node_modules_path: config_data
.and_then(|d| d.node_modules_dir.clone()),
package_json_deps_provider: Arc::new(PackageJsonDepsProvider::new(
config_data.package_json.as_ref().map(|package_json| {
config_data
.and_then(|d| d.package_json.as_ref())
.map(|package_json| {
package_json::get_local_package_json_version_reqs(package_json)
}),
)),
npmrc: config_data
.npmrc
.clone()
.and_then(|d| d.npmrc.clone())
.unwrap_or_else(create_default_npmrc),
npm_system_info: NpmSystemInfo::default(),
})

View file

@ -19,7 +19,6 @@ use super::semantic_tokens;
use super::semantic_tokens::SemanticTokensBuilder;
use super::text::LineIndex;
use super::urls::LspClientUrl;
use super::urls::LspUrlMap;
use super::urls::INVALID_SPECIFIER;
use crate::args::jsr_url;
@ -1844,9 +1843,12 @@ impl DocumentSpan {
let target_asset_or_doc =
language_server.get_maybe_asset_or_document(&target_specifier)?;
let target_line_index = target_asset_or_doc.line_index();
let file_referrer = language_server
.documents
.get_file_referrer(&target_specifier);
let target_uri = language_server
.url_map
.normalize_specifier(&target_specifier)
.normalize_specifier(&target_specifier, file_referrer.as_deref())
.ok()?;
let (target_range, target_selection_range) =
if let Some(context_span) = &self.context_span {
@ -1890,9 +1892,10 @@ impl DocumentSpan {
language_server.get_maybe_asset_or_document(&specifier)?;
let line_index = asset_or_doc.line_index();
let range = self.text_span.to_range(line_index);
let file_referrer = language_server.documents.get_file_referrer(&specifier);
let mut target = language_server
.url_map
.normalize_specifier(&specifier)
.normalize_specifier(&specifier, file_referrer.as_deref())
.ok()?
.into_url();
target.set_fragment(Some(&format!(
@ -1950,9 +1953,10 @@ impl NavigateToItem {
let asset_or_doc =
language_server.get_asset_or_document(&specifier).ok()?;
let line_index = asset_or_doc.line_index();
let file_referrer = language_server.documents.get_file_referrer(&specifier);
let uri = language_server
.url_map
.normalize_specifier(&specifier)
.normalize_specifier(&specifier, file_referrer.as_deref())
.ok()?;
let range = self.text_span.to_range(line_index);
let location = lsp::Location {
@ -2208,9 +2212,10 @@ impl ImplementationLocation {
) -> lsp::Location {
let specifier = resolve_url(&self.document_span.file_name)
.unwrap_or_else(|_| ModuleSpecifier::parse("deno://invalid").unwrap());
let file_referrer = language_server.documents.get_file_referrer(&specifier);
let uri = language_server
.url_map
.normalize_specifier(&specifier)
.normalize_specifier(&specifier, file_referrer.as_deref())
.unwrap_or_else(|_| {
LspClientUrl::new(ModuleSpecifier::parse("deno://invalid").unwrap())
});
@ -2270,7 +2275,11 @@ impl RenameLocations {
includes_non_files = true;
continue;
}
let uri = language_server.url_map.normalize_specifier(&specifier)?;
let file_referrer =
language_server.documents.get_file_referrer(&specifier);
let uri = language_server
.url_map
.normalize_specifier(&specifier, file_referrer.as_deref())?;
let asset_or_doc = language_server.get_asset_or_document(&specifier)?;
// ensure TextDocumentEdit for `location.file_name`.
@ -2916,12 +2925,14 @@ impl ReferenceEntry {
pub fn to_location(
&self,
line_index: Arc<LineIndex>,
url_map: &LspUrlMap,
language_server: &language_server::Inner,
) -> lsp::Location {
let specifier = resolve_url(&self.document_span.file_name)
.unwrap_or_else(|_| INVALID_SPECIFIER.clone());
let uri = url_map
.normalize_specifier(&specifier)
let file_referrer = language_server.documents.get_file_referrer(&specifier);
let uri = language_server
.url_map
.normalize_specifier(&specifier, file_referrer.as_deref())
.unwrap_or_else(|_| LspClientUrl::new(INVALID_SPECIFIER.clone()));
lsp::Location {
uri: uri.into_url(),
@ -2977,9 +2988,12 @@ impl CallHierarchyItem {
) -> lsp::CallHierarchyItem {
let target_specifier =
resolve_url(&self.file).unwrap_or_else(|_| INVALID_SPECIFIER.clone());
let file_referrer = language_server
.documents
.get_file_referrer(&target_specifier);
let uri = language_server
.url_map
.normalize_specifier(&target_specifier)
.normalize_specifier(&target_specifier, file_referrer.as_deref())
.unwrap_or_else(|_| LspClientUrl::new(INVALID_SPECIFIER.clone()));
let use_file_name = self.is_source_file_item();

View file

@ -1,7 +1,5 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use crate::cache::LocalLspHttpCache;
use deno_ast::MediaType;
use deno_core::error::AnyError;
use deno_core::parking_lot::Mutex;
@ -12,6 +10,8 @@ use once_cell::sync::Lazy;
use std::collections::HashMap;
use std::sync::Arc;
use super::cache::LspCache;
/// Used in situations where a default URL needs to be used where otherwise a
/// panic is undesired.
pub static INVALID_SPECIFIER: Lazy<ModuleSpecifier> =
@ -156,13 +156,13 @@ pub enum LspUrlKind {
/// to allow the Deno language server to manage these as virtual documents.
#[derive(Debug, Default, Clone)]
pub struct LspUrlMap {
local_http_cache: Option<Arc<LocalLspHttpCache>>,
cache: LspCache,
inner: Arc<Mutex<LspUrlMapInner>>,
}
impl LspUrlMap {
pub fn set_cache(&mut self, http_cache: Option<Arc<LocalLspHttpCache>>) {
self.local_http_cache = http_cache;
pub fn set_cache(&mut self, cache: &LspCache) {
self.cache = cache.clone();
}
/// Normalize a specifier that is used internally within Deno (or tsc) to a
@ -170,14 +170,13 @@ impl LspUrlMap {
pub fn normalize_specifier(
&self,
specifier: &ModuleSpecifier,
file_referrer: Option<&ModuleSpecifier>,
) -> Result<LspClientUrl, AnyError> {
if let Some(cache) = &self.local_http_cache {
if matches!(specifier.scheme(), "http" | "https") {
if let Some(file_url) = cache.get_file_url(specifier) {
if let Some(file_url) =
self.cache.vendored_specifier(specifier, file_referrer)
{
return Ok(LspClientUrl(file_url));
}
}
}
let mut inner = self.inner.lock();
if let Some(url) = inner.get_url(specifier).cloned() {
Ok(url)
@ -220,15 +219,9 @@ impl LspUrlMap {
/// so we need to force it to in the mapping and nee to explicitly state whether
/// this is a file or directory url.
pub fn normalize_url(&self, url: &Url, kind: LspUrlKind) -> ModuleSpecifier {
if let Some(cache) = &self.local_http_cache {
if url.scheme() == "file" {
if let Ok(path) = url.to_file_path() {
if let Some(remote_url) = cache.get_remote_url(&path) {
if let Some(remote_url) = self.cache.unvendored_specifier(url) {
return remote_url;
}
}
}
}
let mut inner = self.inner.lock();
if let Some(specifier) = inner.get_specifier(url).cloned() {
return specifier;
@ -296,7 +289,7 @@ mod tests {
let map = LspUrlMap::default();
let fixture = resolve_url("https://deno.land/x/pkg@1.0.0/mod.ts").unwrap();
let actual_url = map
.normalize_specifier(&fixture)
.normalize_specifier(&fixture, None)
.expect("could not handle specifier");
let expected_url =
Url::parse("deno:/https/deno.land/x/pkg%401.0.0/mod.ts").unwrap();
@ -318,7 +311,7 @@ mod tests {
assert_eq!(&actual_specifier, &expected_specifier);
let actual_url = map
.normalize_specifier(&actual_specifier)
.normalize_specifier(&actual_specifier, None)
.unwrap()
.as_url()
.clone();
@ -331,7 +324,7 @@ mod tests {
let map = LspUrlMap::default();
let fixture = resolve_url("https://cdn.skypack.dev/-/postcss@v8.2.9-E4SktPp9c0AtxrJHp8iV/dist=es2020,mode=types/lib/postcss.d.ts").unwrap();
let actual_url = map
.normalize_specifier(&fixture)
.normalize_specifier(&fixture, None)
.expect("could not handle specifier");
let expected_url = Url::parse("deno:/https/cdn.skypack.dev/-/postcss%40v8.2.9-E4SktPp9c0AtxrJHp8iV/dist%3Des2020%2Cmode%3Dtypes/lib/postcss.d.ts").unwrap();
assert_eq!(actual_url.as_url(), &expected_url);
@ -346,7 +339,7 @@ mod tests {
let map = LspUrlMap::default();
let fixture = resolve_url("data:application/typescript;base64,ZXhwb3J0IGNvbnN0IGEgPSAiYSI7CgpleHBvcnQgZW51bSBBIHsKICBBLAogIEIsCiAgQywKfQo=").unwrap();
let actual_url = map
.normalize_specifier(&fixture)
.normalize_specifier(&fixture, None)
.expect("could not handle specifier");
let expected_url = Url::parse("deno:/c21c7fc382b2b0553dc0864aa81a3acacfb7b3d1285ab5ae76da6abec213fb37/data_url.ts").unwrap();
assert_eq!(actual_url.as_url(), &expected_url);
@ -361,7 +354,7 @@ mod tests {
let map = LspUrlMap::default();
let fixture = resolve_url("http://localhost:8000/mod.ts").unwrap();
let actual_url = map
.normalize_specifier(&fixture)
.normalize_specifier(&fixture, None)
.expect("could not handle specifier");
let expected_url =
Url::parse("deno:/http/localhost%3A8000/mod.ts").unwrap();

View file

@ -280,13 +280,14 @@ fn lsp_import_map_remote() {
#[test]
fn lsp_import_map_data_url() {
let context = TestContextBuilder::new().use_temp_cwd().build();
let temp_dir = context.temp_dir();
let mut client = context.new_lsp_command().build();
client.initialize(|builder| {
builder.set_import_map("data:application/json;utf8,{\"imports\": { \"example\": \"https://deno.land/x/example/mod.ts\" }}");
});
let diagnostics = client.did_open(json!({
"textDocument": {
"uri": "file:///a/file.ts",
"uri": temp_dir.uri().join("file.ts").unwrap(),
"languageId": "typescript",
"version": 1,
"text": "import example from \"example\";\n"
@ -780,7 +781,7 @@ fn lsp_format_vendor_path() {
client.initialize_default();
let diagnostics = client.did_open(json!({
"textDocument": {
"uri": "file:///a/file.ts",
"uri": temp_dir.uri().join("file.ts").unwrap(),
"languageId": "typescript",
"version": 1,
"text": r#"import "http://localhost:4545/run/002_hello.ts";"#,
@ -802,7 +803,7 @@ fn lsp_format_vendor_path() {
"workspace/executeCommand",
json!({
"command": "deno.cache",
"arguments": [[], "file:///a/file.ts"],
"arguments": [[], temp_dir.uri().join("file.ts").unwrap()],
}),
);
assert!(temp_dir
@ -2622,7 +2623,7 @@ fn lsp_import_map_setting_with_deno_json() {
});
let diagnostics = client.did_open(json!({
"textDocument": {
"uri": "file:///a/file.ts",
"uri": temp_dir.uri().join("file.ts").unwrap(),
"languageId": "typescript",
"version": 1,
"text": "import \"file2\";\n",
@ -7585,7 +7586,7 @@ fn lsp_completions_auto_import_and_quick_fix_with_import_map() {
client.did_open(
json!({
"textDocument": {
"uri": "file:///a/file.ts",
"uri": temp_dir.uri().join("file.ts").unwrap(),
"languageId": "typescript",
"version": 1,
"text": concat!(
@ -7612,7 +7613,7 @@ fn lsp_completions_auto_import_and_quick_fix_with_import_map() {
"npm:chalk@~5",
"http://localhost:4545/subdir/print_hello.ts",
],
"file:///a/file.ts",
temp_dir.uri().join("file.ts").unwrap(),
],
}),
);
@ -7620,14 +7621,14 @@ fn lsp_completions_auto_import_and_quick_fix_with_import_map() {
// try auto-import with path
client.did_open(json!({
"textDocument": {
"uri": "file:///a/a.ts",
"uri": temp_dir.uri().join("a.ts").unwrap(),
"languageId": "typescript",
"version": 1,
"text": "getClie",
}
}));
let list = client.get_completion_list(
"file:///a/a.ts",
temp_dir.uri().join("a.ts").unwrap(),
(0, 7),
json!({ "triggerKind": 1 }),
);
@ -7668,20 +7669,23 @@ fn lsp_completions_auto_import_and_quick_fix_with_import_map() {
// try quick fix with path
let diagnostics = client.did_open(json!({
"textDocument": {
"uri": "file:///a/b.ts",
"uri": temp_dir.uri().join("b.ts").unwrap(),
"languageId": "typescript",
"version": 1,
"text": "getClient",
}
}));
let diagnostics = diagnostics
.messages_with_file_and_source("file:///a/b.ts", "deno-ts")
.messages_with_file_and_source(
temp_dir.uri().join("b.ts").unwrap().as_str(),
"deno-ts",
)
.diagnostics;
let res = client.write_request(
"textDocument/codeAction",
json!(json!({
"textDocument": {
"uri": "file:///a/b.ts"
"uri": temp_dir.uri().join("b.ts").unwrap()
},
"range": {
"start": { "line": 0, "character": 0 },
@ -7713,7 +7717,7 @@ fn lsp_completions_auto_import_and_quick_fix_with_import_map() {
"edit": {
"documentChanges": [{
"textDocument": {
"uri": "file:///a/b.ts",
"uri": temp_dir.uri().join("b.ts").unwrap(),
"version": 1,
},
"edits": [{
@ -7731,7 +7735,7 @@ fn lsp_completions_auto_import_and_quick_fix_with_import_map() {
// try auto-import without path
client.did_open(json!({
"textDocument": {
"uri": "file:///a/c.ts",
"uri": temp_dir.uri().join("c.ts").unwrap(),
"languageId": "typescript",
"version": 1,
"text": "chal",
@ -7739,7 +7743,7 @@ fn lsp_completions_auto_import_and_quick_fix_with_import_map() {
}));
let list = client.get_completion_list(
"file:///a/c.ts",
temp_dir.uri().join("c.ts").unwrap(),
(0, 4),
json!({ "triggerKind": 1 }),
);
@ -7778,20 +7782,23 @@ fn lsp_completions_auto_import_and_quick_fix_with_import_map() {
// try quick fix without path
let diagnostics = client.did_open(json!({
"textDocument": {
"uri": "file:///a/d.ts",
"uri": temp_dir.uri().join("d.ts").unwrap(),
"languageId": "typescript",
"version": 1,
"text": "chalk",
}
}));
let diagnostics = diagnostics
.messages_with_file_and_source("file:///a/d.ts", "deno-ts")
.messages_with_file_and_source(
temp_dir.uri().join("d.ts").unwrap().as_str(),
"deno-ts",
)
.diagnostics;
let res = client.write_request(
"textDocument/codeAction",
json!(json!({
"textDocument": {
"uri": "file:///a/d.ts"
"uri": temp_dir.uri().join("d.ts").unwrap()
},
"range": {
"start": { "line": 0, "character": 0 },
@ -7823,7 +7830,7 @@ fn lsp_completions_auto_import_and_quick_fix_with_import_map() {
"edit": {
"documentChanges": [{
"textDocument": {
"uri": "file:///a/d.ts",
"uri": temp_dir.uri().join("d.ts").unwrap(),
"version": 1,
},
"edits": [{
@ -7841,7 +7848,7 @@ fn lsp_completions_auto_import_and_quick_fix_with_import_map() {
// try auto-import with http import map
client.did_open(json!({
"textDocument": {
"uri": "file:///a/e.ts",
"uri": temp_dir.uri().join("e.ts").unwrap(),
"languageId": "typescript",
"version": 1,
"text": "printH",
@ -7849,7 +7856,7 @@ fn lsp_completions_auto_import_and_quick_fix_with_import_map() {
}));
let list = client.get_completion_list(
"file:///a/e.ts",
temp_dir.uri().join("e.ts").unwrap(),
(0, 6),
json!({ "triggerKind": 1 }),
);
@ -7888,20 +7895,23 @@ fn lsp_completions_auto_import_and_quick_fix_with_import_map() {
// try quick fix with http import
let diagnostics = client.did_open(json!({
"textDocument": {
"uri": "file:///a/f.ts",
"uri": temp_dir.uri().join("f.ts").unwrap(),
"languageId": "typescript",
"version": 1,
"text": "printHello",
}
}));
let diagnostics = diagnostics
.messages_with_file_and_source("file:///a/f.ts", "deno-ts")
.messages_with_file_and_source(
temp_dir.uri().join("f.ts").unwrap().as_str(),
"deno-ts",
)
.diagnostics;
let res = client.write_request(
"textDocument/codeAction",
json!(json!({
"textDocument": {
"uri": "file:///a/f.ts"
"uri": temp_dir.uri().join("f.ts").unwrap()
},
"range": {
"start": { "line": 0, "character": 0 },
@ -7933,7 +7943,7 @@ fn lsp_completions_auto_import_and_quick_fix_with_import_map() {
"edit": {
"documentChanges": [{
"textDocument": {
"uri": "file:///a/f.ts",
"uri": temp_dir.uri().join("f.ts").unwrap(),
"version": 1,
},
"edits": [{
@ -7951,14 +7961,14 @@ fn lsp_completions_auto_import_and_quick_fix_with_import_map() {
// try auto-import with npm package with sub-path on value side of import map
client.did_open(json!({
"textDocument": {
"uri": "file:///a/nested_path.ts",
"uri": temp_dir.uri().join("nested_path.ts").unwrap(),
"languageId": "typescript",
"version": 1,
"text": "entry",
}
}));
let list = client.get_completion_list(
"file:///a/nested_path.ts",
temp_dir.uri().join("nested_path.ts").unwrap(),
(0, 5),
json!({ "triggerKind": 1 }),
);
@ -11001,7 +11011,7 @@ fn lsp_lint_with_config() {
let diagnostics = client.did_open(json!({
"textDocument": {
"uri": "file:///a/file.ts",
"uri": temp_dir.uri().join("file.ts").unwrap(),
"languageId": "typescript",
"version": 1,
"text": "// TODO: fixme\nexport async function non_camel_case() {\nconsole.log(\"finished!\")\n}"
@ -12104,6 +12114,323 @@ fn lsp_vendor_dir() {
client.shutdown();
}
#[test]
fn lsp_deno_json_scopes_import_map() {
let context = TestContextBuilder::new().use_temp_cwd().build();
let temp_dir = context.temp_dir();
temp_dir.create_dir_all("project1");
temp_dir.create_dir_all("project2/project3");
temp_dir.write(
"project1/deno.json",
json!({
"imports": {
"foo": "./foo1.ts",
},
})
.to_string(),
);
temp_dir.write("project1/foo1.ts", "");
temp_dir.write(
"project2/deno.json",
json!({
"imports": {
"foo": "./foo2.ts",
},
})
.to_string(),
);
temp_dir.write("project2/foo2.ts", "");
temp_dir.write(
"project2/project3/deno.json",
json!({
"imports": {
"foo": "./foo3.ts",
},
})
.to_string(),
);
temp_dir.write("project2/project3/foo3.ts", "");
let mut client = context.new_lsp_command().build();
client.initialize_default();
client.did_open(json!({
"textDocument": {
"uri": temp_dir.uri().join("project1/file.ts").unwrap(),
"languageId": "typescript",
"version": 1,
"text": "import \"foo\";\n",
},
}));
let res = client.write_request(
"textDocument/hover",
json!({
"textDocument": {
"uri": temp_dir.uri().join("project1/file.ts").unwrap(),
},
"position": { "line": 0, "character": 7 },
}),
);
assert_eq!(
res,
json!({
"contents": {
"kind": "markdown",
"value": format!("**Resolved Dependency**\n\n**Code**: file&#8203;{}\n", temp_dir.uri().join("project1/foo1.ts").unwrap().as_str().trim_start_matches("file")),
},
"range": {
"start": { "line": 0, "character": 7 },
"end": { "line": 0, "character": 12 },
},
})
);
client.did_open(json!({
"textDocument": {
"uri": temp_dir.uri().join("project2/file.ts").unwrap(),
"languageId": "typescript",
"version": 1,
"text": "import \"foo\";\n",
},
}));
let res = client.write_request(
"textDocument/hover",
json!({
"textDocument": {
"uri": temp_dir.uri().join("project2/file.ts").unwrap(),
},
"position": { "line": 0, "character": 7 },
}),
);
assert_eq!(
res,
json!({
"contents": {
"kind": "markdown",
"value": format!("**Resolved Dependency**\n\n**Code**: file&#8203;{}\n", temp_dir.uri().join("project2/foo2.ts").unwrap().as_str().trim_start_matches("file")),
},
"range": {
"start": { "line": 0, "character": 7 },
"end": { "line": 0, "character": 12 },
},
})
);
client.did_open(json!({
"textDocument": {
"uri": temp_dir.uri().join("project2/project3/file.ts").unwrap(),
"languageId": "typescript",
"version": 1,
"text": "import \"foo\";\n",
},
}));
let res = client.write_request(
"textDocument/hover",
json!({
"textDocument": {
"uri": temp_dir.uri().join("project2/project3/file.ts").unwrap(),
},
"position": { "line": 0, "character": 7 },
}),
);
assert_eq!(
res,
json!({
"contents": {
"kind": "markdown",
"value": format!("**Resolved Dependency**\n\n**Code**: file&#8203;{}\n", temp_dir.uri().join("project2/project3/foo3.ts").unwrap().as_str().trim_start_matches("file")),
},
"range": {
"start": { "line": 0, "character": 7 },
"end": { "line": 0, "character": 12 },
},
})
);
client.shutdown();
}
#[test]
fn lsp_deno_json_scopes_vendor_dirs() {
let context = TestContextBuilder::new()
.use_http_server()
.use_temp_cwd()
.build();
let temp_dir = context.temp_dir();
temp_dir.create_dir_all("project1");
temp_dir.create_dir_all("project2/project3");
temp_dir.write(
"project1/deno.json",
json!({
"vendor": true,
})
.to_string(),
);
temp_dir.write(
"project2/deno.json",
json!({
"vendor": true,
})
.to_string(),
);
temp_dir.write(
"project2/project3/deno.json",
json!({
"vendor": true,
})
.to_string(),
);
let mut client = context.new_lsp_command().build();
client.initialize_default();
client.did_open(json!({
"textDocument": {
"uri": temp_dir.uri().join("project1/file.ts").unwrap(),
"languageId": "typescript",
"version": 1,
"text": "import \"http://localhost:4545/subdir/mod1.ts\";\n",
},
}));
client.write_request(
"workspace/executeCommand",
json!({
"command": "deno.cache",
"arguments": [[], temp_dir.uri().join("project1/file.ts").unwrap()],
}),
);
let res = client.write_request(
"textDocument/definition",
json!({
"textDocument": {
"uri": temp_dir.uri().join("project1/file.ts").unwrap(),
},
"position": { "line": 0, "character": 7 },
}),
);
assert_eq!(
res,
json!([{
"targetUri": temp_dir.uri().join("project1/vendor/http_localhost_4545/subdir/mod1.ts").unwrap(),
"targetRange": {
"start": {
"line": 0,
"character": 0,
},
"end": {
"line": 17,
"character": 0,
},
},
"targetSelectionRange": {
"start": {
"line": 0,
"character": 0,
},
"end": {
"line": 17,
"character": 0,
},
},
}]),
);
client.did_open(json!({
"textDocument": {
"uri": temp_dir.uri().join("project2/file.ts").unwrap(),
"languageId": "typescript",
"version": 1,
"text": "import \"http://localhost:4545/subdir/mod2.ts\";\n",
},
}));
client.write_request(
"workspace/executeCommand",
json!({
"command": "deno.cache",
"arguments": [[], temp_dir.uri().join("project2/file.ts").unwrap()],
}),
);
let res = client.write_request(
"textDocument/definition",
json!({
"textDocument": {
"uri": temp_dir.uri().join("project2/file.ts").unwrap(),
},
"position": { "line": 0, "character": 7 },
}),
);
assert_eq!(
res,
json!([{
"targetUri": temp_dir.uri().join("project2/vendor/http_localhost_4545/subdir/mod2.ts").unwrap(),
"targetRange": {
"start": {
"line": 0,
"character": 0,
},
"end": {
"line": 1,
"character": 0,
},
},
"targetSelectionRange": {
"start": {
"line": 0,
"character": 0,
},
"end": {
"line": 1,
"character": 0,
},
},
}]),
);
client.did_open(json!({
"textDocument": {
"uri": temp_dir.uri().join("project2/project3/file.ts").unwrap(),
"languageId": "typescript",
"version": 1,
"text": "import \"http://localhost:4545/subdir/mod3.js\";\n",
},
}));
client.write_request(
"workspace/executeCommand",
json!({
"command": "deno.cache",
"arguments": [[], temp_dir.uri().join("project2/project3/file.ts").unwrap()],
}),
);
let res = client.write_request(
"textDocument/definition",
json!({
"textDocument": {
"uri": temp_dir.uri().join("project2/project3/file.ts").unwrap(),
},
"position": { "line": 0, "character": 7 },
}),
);
assert_eq!(
res,
json!([{
"targetUri": temp_dir.uri().join("project2/project3/vendor/http_localhost_4545/subdir/mod3.js").unwrap(),
"targetRange": {
"start": {
"line": 0,
"character": 0,
},
"end": {
"line": 1,
"character": 0,
},
},
"targetSelectionRange": {
"start": {
"line": 0,
"character": 0,
},
"end": {
"line": 1,
"character": 0,
},
},
}]),
);
client.shutdown();
}
#[test]
fn lsp_deno_json_workspace_fmt_config() {
let context = TestContextBuilder::new().use_temp_cwd().build();
@ -13005,7 +13332,7 @@ fn lsp_uses_lockfile_for_npm_initialization() {
assert!(!line.contains("Running npm resolution."), "Line: {}", line);
line.contains("Server ready.")
});
assert_eq!(skipping_count, 1);
assert_eq!(skipping_count, 2);
client.shutdown();
}