1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-21 15:04:11 -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::url::Url;
use deno_core::ModuleSpecifier; use deno_core::ModuleSpecifier;
use std::collections::BTreeMap;
use std::fs; use std::fs;
use std::path::Path; use std::path::Path;
use std::sync::Arc; 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( pub fn calculate_fs_version(
cache: &LspCache, cache: &LspCache,
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
file_referrer: Option<&ModuleSpecifier>,
) -> Option<String> { ) -> Option<String> {
match specifier.scheme() { match specifier.scheme() {
"npm" | "node" | "data" | "blob" => None, "npm" | "node" | "data" | "blob" => None,
"file" => specifier_to_file_path(specifier) "file" => specifier_to_file_path(specifier)
.ok() .ok()
.and_then(|path| calculate_fs_version_at_path(&path)), .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( fn calculate_fs_version_in_cache(
cache: &LspCache, cache: &LspCache,
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
file_referrer: Option<&ModuleSpecifier>,
) -> Option<String> { ) -> 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 { let Ok(cache_key) = http_cache.cache_item_key(specifier) else {
return Some("1".to_string()); return Some("1".to_string());
}; };
@ -77,7 +80,7 @@ fn calculate_fs_version_in_cache(
pub struct LspCache { pub struct LspCache {
deno_dir: DenoDir, deno_dir: DenoDir,
global: Arc<GlobalHttpCache>, global: Arc<GlobalHttpCache>,
root_vendor: Option<Arc<LocalLspHttpCache>>, vendors_by_scope: BTreeMap<ModuleSpecifier, Option<Arc<LocalLspHttpCache>>>,
} }
impl Default for LspCache { impl Default for LspCache {
@ -107,18 +110,24 @@ impl LspCache {
Self { Self {
deno_dir, deno_dir,
global, global,
root_vendor: None, vendors_by_scope: Default::default(),
} }
} }
pub fn update_config(&mut self, config: &Config) { pub fn update_config(&mut self, config: &Config) {
self.root_vendor = config.tree.root_data().and_then(|data| { self.vendors_by_scope = config
let vendor_dir = data.vendor_dir.as_ref()?; .tree
Some(Arc::new(LocalLspHttpCache::new( .data_by_scope()
vendor_dir.clone(), .iter()
self.global.clone(), .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 { pub fn deno_dir(&self) -> &DenoDir {
@ -129,15 +138,50 @@ impl LspCache {
&self.global &self.global
} }
pub fn root_vendor(&self) -> Option<&Arc<LocalLspHttpCache>> { pub fn for_specifier(
self.root_vendor.as_ref() &self,
} file_referrer: Option<&ModuleSpecifier>,
) -> Arc<dyn HttpCache> {
pub fn root_vendor_or_global(&self) -> Arc<dyn HttpCache> { let Some(file_referrer) = file_referrer else {
return self.global.clone();
};
self self
.root_vendor .vendors_by_scope
.as_ref() .iter()
.map(|v| v.clone() as _) .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 _) .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( locations.push(
reference reference
.entry .entry
.to_location(asset_or_doc.line_index(), &language_server.url_map), .to_location(asset_or_doc.line_index(), language_server),
); );
} }
Ok(locations) Ok(locations)

View file

@ -1568,27 +1568,13 @@ impl ConfigData {
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct ConfigTree { pub struct ConfigTree {
first_folder: Option<ModuleSpecifier>, first_folder: Option<ModuleSpecifier>,
scopes: Arc<BTreeMap<ModuleSpecifier, ConfigData>>, scopes: Arc<BTreeMap<ModuleSpecifier, Arc<ConfigData>>>,
} }
impl ConfigTree { 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> { pub fn root_ts_config(&self) -> Arc<LspTsConfig> {
self let root_data = self.first_folder.as_ref().and_then(|s| self.scopes.get(s));
.root_data() root_data.map(|d| d.ts_config.clone()).unwrap_or_default()
.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())
} }
pub fn scope_for_specifier( pub fn scope_for_specifier(
@ -1599,19 +1585,20 @@ impl ConfigTree {
.scopes .scopes
.keys() .keys()
.rfind(|s| specifier.as_str().starts_with(s.as_str())) .rfind(|s| specifier.as_str().starts_with(s.as_str()))
.or(self.first_folder.as_ref())
} }
pub fn data_for_specifier( pub fn data_for_specifier(
&self, &self,
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
) -> Option<&ConfigData> { ) -> Option<&Arc<ConfigData>> {
self self
.scope_for_specifier(specifier) .scope_for_specifier(specifier)
.and_then(|s| self.scopes.get(s)) .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 &self.scopes
} }
@ -1694,14 +1681,16 @@ impl ConfigTree {
if let Ok(config_uri) = folder_uri.join(config_path) { if let Ok(config_uri) = folder_uri.join(config_path) {
scopes.insert( scopes.insert(
folder_uri.clone(), folder_uri.clone(),
ConfigData::load( Arc::new(
Some(&config_uri), ConfigData::load(
folder_uri, Some(&config_uri),
None, folder_uri,
settings, None,
Some(file_fetcher), settings,
) Some(file_fetcher),
.await, )
.await,
),
); );
} }
} }
@ -1756,10 +1745,10 @@ impl ConfigTree {
Some(file_fetcher), Some(file_fetcher),
) )
.await; .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() { for folder_uri in settings.by_workspace_folder.keys() {
@ -1769,14 +1758,16 @@ impl ConfigTree {
{ {
scopes.insert( scopes.insert(
folder_uri.clone(), folder_uri.clone(),
ConfigData::load( Arc::new(
None, ConfigData::load(
folder_uri, None,
None, folder_uri,
settings, None,
Some(file_fetcher), settings,
) Some(file_fetcher),
.await, )
.await,
),
); );
} }
} }
@ -1787,14 +1778,16 @@ impl ConfigTree {
#[cfg(test)] #[cfg(test)]
pub async fn inject_config_file(&mut self, config_file: ConfigFile) { pub async fn inject_config_file(&mut self, config_file: ConfigFile) {
let scope = config_file.specifier.join(".").unwrap(); let scope = config_file.specifier.join(".").unwrap();
let data = ConfigData::load_inner( let data = Arc::new(
Some(config_file), ConfigData::load_inner(
&scope, Some(config_file),
None, &scope,
&Default::default(), None,
None, &Default::default(),
) None,
.await; )
.await,
);
self.first_folder = Some(scope.clone()); self.first_folder = Some(scope.clone());
self.scopes = Arc::new([(scope, data)].into_iter().collect()); 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::config::Config;
use super::documents; use super::documents;
use super::documents::Document; use super::documents::Document;
use super::documents::Documents;
use super::documents::DocumentsFilter; use super::documents::DocumentsFilter;
use super::language_server; use super::language_server;
use super::language_server::StateSnapshot; use super::language_server::StateSnapshot;
@ -120,6 +121,7 @@ impl DiagnosticsPublisher {
source: DiagnosticSource, source: DiagnosticSource,
diagnostics: DiagnosticVec, diagnostics: DiagnosticVec,
url_map: &LspUrlMap, url_map: &LspUrlMap,
documents: &Documents,
token: &CancellationToken, token: &CancellationToken,
) -> usize { ) -> usize {
let mut diagnostics_by_specifier = let mut diagnostics_by_specifier =
@ -153,11 +155,12 @@ impl DiagnosticsPublisher {
self self
.state .state
.update(&record.specifier, version, &all_specifier_diagnostics); .update(&record.specifier, version, &all_specifier_diagnostics);
let file_referrer = documents.get_file_referrer(&record.specifier);
self self
.client .client
.publish_diagnostics( .publish_diagnostics(
url_map url_map
.normalize_specifier(&record.specifier) .normalize_specifier(&record.specifier, file_referrer.as_deref())
.unwrap_or(LspClientUrl::new(record.specifier)), .unwrap_or(LspClientUrl::new(record.specifier)),
all_specifier_diagnostics, all_specifier_diagnostics,
version, version,
@ -183,11 +186,12 @@ impl DiagnosticsPublisher {
if let Some(removed_value) = maybe_removed_value { if let Some(removed_value) = maybe_removed_value {
// clear out any diagnostics for this specifier // clear out any diagnostics for this specifier
self.state.update(specifier, removed_value.version, &[]); self.state.update(specifier, removed_value.version, &[]);
let file_referrer = documents.get_file_referrer(specifier);
self self
.client .client
.publish_diagnostics( .publish_diagnostics(
url_map url_map
.normalize_specifier(specifier) .normalize_specifier(specifier, file_referrer.as_deref())
.unwrap_or_else(|_| LspClientUrl::new(specifier.clone())), .unwrap_or_else(|_| LspClientUrl::new(specifier.clone())),
Vec::new(), Vec::new(),
removed_value.version, removed_value.version,
@ -519,6 +523,7 @@ impl DiagnosticsServer {
DiagnosticSource::Ts, DiagnosticSource::Ts,
diagnostics, diagnostics,
&url_map, &url_map,
snapshot.documents.as_ref(),
&token, &token,
) )
.await; .await;
@ -556,6 +561,7 @@ impl DiagnosticsServer {
let mark = performance.mark("lsp.update_diagnostics_deps"); let mark = performance.mark("lsp.update_diagnostics_deps");
let diagnostics = spawn_blocking({ let diagnostics = spawn_blocking({
let token = token.clone(); let token = token.clone();
let snapshot = snapshot.clone();
move || generate_deno_diagnostics(&snapshot, &config, token) move || generate_deno_diagnostics(&snapshot, &config, token)
}) })
.await .await
@ -568,6 +574,7 @@ impl DiagnosticsServer {
DiagnosticSource::Deno, DiagnosticSource::Deno,
diagnostics, diagnostics,
&url_map, &url_map,
snapshot.documents.as_ref(),
&token, &token,
) )
.await; .await;
@ -605,6 +612,7 @@ impl DiagnosticsServer {
let mark = performance.mark("lsp.update_diagnostics_lint"); let mark = performance.mark("lsp.update_diagnostics_lint");
let diagnostics = spawn_blocking({ let diagnostics = spawn_blocking({
let token = token.clone(); let token = token.clone();
let snapshot = snapshot.clone();
move || generate_lint_diagnostics(&snapshot, &config, token) move || generate_lint_diagnostics(&snapshot, &config, token)
}) })
.await .await
@ -617,6 +625,7 @@ impl DiagnosticsServer {
DiagnosticSource::Lint, DiagnosticSource::Lint,
diagnostics, diagnostics,
&url_map, &url_map,
snapshot.documents.as_ref(),
&token, &token,
) )
.await; .await;
@ -1466,7 +1475,11 @@ fn diagnose_dependency(
return; // ignore, surface typescript errors instead 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 Some(import_map) = import_map {
if let Resolution::Ok(resolved) = &dependency.maybe_code { if let Resolution::Ok(resolved) = &dependency.maybe_code {
if let Some(to) = import_map.lookup(&resolved.specifier, referrer) { if let Some(to) = import_map.lookup(&resolved.specifier, referrer) {

View file

@ -303,6 +303,10 @@ impl Document {
cache: &Arc<LspCache>, cache: &Arc<LspCache>,
file_referrer: Option<ModuleSpecifier>, file_referrer: Option<ModuleSpecifier>,
) -> Arc<Self> { ) -> Arc<Self> {
let file_referrer = Some(&specifier)
.filter(|s| s.scheme() == "file")
.cloned()
.or(file_referrer);
let media_type = resolve_media_type( let media_type = resolve_media_type(
&specifier, &specifier,
maybe_headers.as_ref(), maybe_headers.as_ref(),
@ -336,9 +340,13 @@ impl Document {
Arc::new(Self { Arc::new(Self {
config, config,
dependencies, 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_types_dependency,
maybe_fs_version: calculate_fs_version(cache, &specifier),
line_index, line_index,
maybe_language_id, maybe_language_id,
maybe_headers, maybe_headers,
@ -540,7 +548,11 @@ impl Document {
config: self.config.clone(), config: self.config.clone(),
specifier: self.specifier.clone(), specifier: self.specifier.clone(),
file_referrer: self.file_referrer.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, maybe_language_id: self.maybe_language_id,
dependencies: self.dependencies.clone(), dependencies: self.dependencies.clone(),
maybe_types_dependency: self.maybe_types_dependency.clone(), maybe_types_dependency: self.maybe_types_dependency.clone(),
@ -563,7 +575,11 @@ impl Document {
config: self.config.clone(), config: self.config.clone(),
specifier: self.specifier.clone(), specifier: self.specifier.clone(),
file_referrer: self.file_referrer.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, maybe_language_id: self.maybe_language_id,
dependencies: self.dependencies.clone(), dependencies: self.dependencies.clone(),
maybe_types_dependency: self.maybe_types_dependency.clone(), maybe_types_dependency: self.maybe_types_dependency.clone(),
@ -766,7 +782,10 @@ impl FileSystemDocuments {
cache: &Arc<LspCache>, cache: &Arc<LspCache>,
file_referrer: Option<&ModuleSpecifier>, file_referrer: Option<&ModuleSpecifier>,
) -> Option<Arc<Document>> { ) -> 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 old_doc = self.docs.get(specifier).map(|v| v.value().clone());
let dirty = match &old_doc { let dirty = match &old_doc {
None => true, None => true,
@ -830,7 +849,7 @@ impl FileSystemDocuments {
file_referrer.cloned(), file_referrer.cloned(),
) )
} else { } 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 cache_key = http_cache.cache_item_key(specifier).ok()?;
let bytes = http_cache let bytes = http_cache
.read_file_bytes(&cache_key, None, LSP_DISALLOW_GLOBAL_TO_LOCAL_COPY) .read_file_bytes(&cache_key, None, LSP_DISALLOW_GLOBAL_TO_LOCAL_COPY)
@ -1089,7 +1108,7 @@ impl Documents {
.map(|p| p.is_file()) .map(|p| p.is_file())
.unwrap_or(false); .unwrap_or(false);
} }
if self.cache.root_vendor_or_global().contains(&specifier) { if self.cache.for_specifier(file_referrer).contains(&specifier) {
return true; return true;
} }
} }
@ -1335,8 +1354,7 @@ impl Documents {
let mut visit_doc = |doc: &Arc<Document>| { let mut visit_doc = |doc: &Arc<Document>| {
let scope = doc let scope = doc
.file_referrer() .file_referrer()
.and_then(|r| self.config.tree.scope_for_specifier(r)) .and_then(|r| self.config.tree.scope_for_specifier(r));
.or(self.config.tree.root_scope());
let reqs = npm_reqs_by_scope.entry(scope.cloned()).or_default(); let reqs = npm_reqs_by_scope.entry(scope.cloned()).or_default();
for dependency in doc.dependencies().values() { for dependency in doc.dependencies().values() {
if let Some(dep) = dependency.get_code() { if let Some(dep) = dependency.get_code() {
@ -1367,21 +1385,15 @@ impl Documents {
} }
// fill the reqs from the lockfile // fill the reqs from the lockfile
// TODO(nayeemrmn): Iterate every lockfile here for multi-deno.json. for (scope, config_data) in self.config.tree.data_by_scope().as_ref() {
if let Some(lockfile) = self if let Some(lockfile) = config_data.lockfile.as_ref() {
.config let reqs = npm_reqs_by_scope.entry(Some(scope.clone())).or_default();
.tree let lockfile = lockfile.lock();
.root_data() for key in lockfile.content.packages.specifiers.keys() {
.and_then(|d| d.lockfile.as_ref()) if let Some(key) = key.strip_prefix("npm:") {
{ if let Ok(req) = PackageReq::from_str(key) {
let reqs = npm_reqs_by_scope reqs.insert(req);
.entry(self.config.tree.root_scope().cloned()) }
.or_default();
let lockfile = lockfile.lock();
for key in lockfile.content.packages.specifiers.keys() {
if let Some(key) = key.strip_prefix("npm:") {
if let Ok(req) = PackageReq::from_str(key) {
reqs.insert(req);
} }
} }
} }

View file

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

View file

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

View file

@ -19,7 +19,6 @@ use super::semantic_tokens;
use super::semantic_tokens::SemanticTokensBuilder; use super::semantic_tokens::SemanticTokensBuilder;
use super::text::LineIndex; use super::text::LineIndex;
use super::urls::LspClientUrl; use super::urls::LspClientUrl;
use super::urls::LspUrlMap;
use super::urls::INVALID_SPECIFIER; use super::urls::INVALID_SPECIFIER;
use crate::args::jsr_url; use crate::args::jsr_url;
@ -1844,9 +1843,12 @@ impl DocumentSpan {
let target_asset_or_doc = let target_asset_or_doc =
language_server.get_maybe_asset_or_document(&target_specifier)?; language_server.get_maybe_asset_or_document(&target_specifier)?;
let target_line_index = target_asset_or_doc.line_index(); 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 let target_uri = language_server
.url_map .url_map
.normalize_specifier(&target_specifier) .normalize_specifier(&target_specifier, file_referrer.as_deref())
.ok()?; .ok()?;
let (target_range, target_selection_range) = let (target_range, target_selection_range) =
if let Some(context_span) = &self.context_span { if let Some(context_span) = &self.context_span {
@ -1890,9 +1892,10 @@ impl DocumentSpan {
language_server.get_maybe_asset_or_document(&specifier)?; language_server.get_maybe_asset_or_document(&specifier)?;
let line_index = asset_or_doc.line_index(); let line_index = asset_or_doc.line_index();
let range = self.text_span.to_range(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 let mut target = language_server
.url_map .url_map
.normalize_specifier(&specifier) .normalize_specifier(&specifier, file_referrer.as_deref())
.ok()? .ok()?
.into_url(); .into_url();
target.set_fragment(Some(&format!( target.set_fragment(Some(&format!(
@ -1950,9 +1953,10 @@ impl NavigateToItem {
let asset_or_doc = let asset_or_doc =
language_server.get_asset_or_document(&specifier).ok()?; language_server.get_asset_or_document(&specifier).ok()?;
let line_index = asset_or_doc.line_index(); let line_index = asset_or_doc.line_index();
let file_referrer = language_server.documents.get_file_referrer(&specifier);
let uri = language_server let uri = language_server
.url_map .url_map
.normalize_specifier(&specifier) .normalize_specifier(&specifier, file_referrer.as_deref())
.ok()?; .ok()?;
let range = self.text_span.to_range(line_index); let range = self.text_span.to_range(line_index);
let location = lsp::Location { let location = lsp::Location {
@ -2208,9 +2212,10 @@ impl ImplementationLocation {
) -> lsp::Location { ) -> lsp::Location {
let specifier = resolve_url(&self.document_span.file_name) let specifier = resolve_url(&self.document_span.file_name)
.unwrap_or_else(|_| ModuleSpecifier::parse("deno://invalid").unwrap()); .unwrap_or_else(|_| ModuleSpecifier::parse("deno://invalid").unwrap());
let file_referrer = language_server.documents.get_file_referrer(&specifier);
let uri = language_server let uri = language_server
.url_map .url_map
.normalize_specifier(&specifier) .normalize_specifier(&specifier, file_referrer.as_deref())
.unwrap_or_else(|_| { .unwrap_or_else(|_| {
LspClientUrl::new(ModuleSpecifier::parse("deno://invalid").unwrap()) LspClientUrl::new(ModuleSpecifier::parse("deno://invalid").unwrap())
}); });
@ -2270,7 +2275,11 @@ impl RenameLocations {
includes_non_files = true; includes_non_files = true;
continue; 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)?; let asset_or_doc = language_server.get_asset_or_document(&specifier)?;
// ensure TextDocumentEdit for `location.file_name`. // ensure TextDocumentEdit for `location.file_name`.
@ -2916,12 +2925,14 @@ impl ReferenceEntry {
pub fn to_location( pub fn to_location(
&self, &self,
line_index: Arc<LineIndex>, line_index: Arc<LineIndex>,
url_map: &LspUrlMap, language_server: &language_server::Inner,
) -> lsp::Location { ) -> lsp::Location {
let specifier = resolve_url(&self.document_span.file_name) let specifier = resolve_url(&self.document_span.file_name)
.unwrap_or_else(|_| INVALID_SPECIFIER.clone()); .unwrap_or_else(|_| INVALID_SPECIFIER.clone());
let uri = url_map let file_referrer = language_server.documents.get_file_referrer(&specifier);
.normalize_specifier(&specifier) let uri = language_server
.url_map
.normalize_specifier(&specifier, file_referrer.as_deref())
.unwrap_or_else(|_| LspClientUrl::new(INVALID_SPECIFIER.clone())); .unwrap_or_else(|_| LspClientUrl::new(INVALID_SPECIFIER.clone()));
lsp::Location { lsp::Location {
uri: uri.into_url(), uri: uri.into_url(),
@ -2977,9 +2988,12 @@ impl CallHierarchyItem {
) -> lsp::CallHierarchyItem { ) -> lsp::CallHierarchyItem {
let target_specifier = let target_specifier =
resolve_url(&self.file).unwrap_or_else(|_| INVALID_SPECIFIER.clone()); 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 let uri = language_server
.url_map .url_map
.normalize_specifier(&target_specifier) .normalize_specifier(&target_specifier, file_referrer.as_deref())
.unwrap_or_else(|_| LspClientUrl::new(INVALID_SPECIFIER.clone())); .unwrap_or_else(|_| LspClientUrl::new(INVALID_SPECIFIER.clone()));
let use_file_name = self.is_source_file_item(); 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. // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use crate::cache::LocalLspHttpCache;
use deno_ast::MediaType; use deno_ast::MediaType;
use deno_core::error::AnyError; use deno_core::error::AnyError;
use deno_core::parking_lot::Mutex; use deno_core::parking_lot::Mutex;
@ -12,6 +10,8 @@ use once_cell::sync::Lazy;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use super::cache::LspCache;
/// Used in situations where a default URL needs to be used where otherwise a /// Used in situations where a default URL needs to be used where otherwise a
/// panic is undesired. /// panic is undesired.
pub static INVALID_SPECIFIER: Lazy<ModuleSpecifier> = 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. /// to allow the Deno language server to manage these as virtual documents.
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct LspUrlMap { pub struct LspUrlMap {
local_http_cache: Option<Arc<LocalLspHttpCache>>, cache: LspCache,
inner: Arc<Mutex<LspUrlMapInner>>, inner: Arc<Mutex<LspUrlMapInner>>,
} }
impl LspUrlMap { impl LspUrlMap {
pub fn set_cache(&mut self, http_cache: Option<Arc<LocalLspHttpCache>>) { pub fn set_cache(&mut self, cache: &LspCache) {
self.local_http_cache = http_cache; self.cache = cache.clone();
} }
/// Normalize a specifier that is used internally within Deno (or tsc) to a /// Normalize a specifier that is used internally within Deno (or tsc) to a
@ -170,13 +170,12 @@ impl LspUrlMap {
pub fn normalize_specifier( pub fn normalize_specifier(
&self, &self,
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
file_referrer: Option<&ModuleSpecifier>,
) -> Result<LspClientUrl, AnyError> { ) -> Result<LspClientUrl, AnyError> {
if let Some(cache) = &self.local_http_cache { if let Some(file_url) =
if matches!(specifier.scheme(), "http" | "https") { self.cache.vendored_specifier(specifier, file_referrer)
if let Some(file_url) = cache.get_file_url(specifier) { {
return Ok(LspClientUrl(file_url)); return Ok(LspClientUrl(file_url));
}
}
} }
let mut inner = self.inner.lock(); let mut inner = self.inner.lock();
if let Some(url) = inner.get_url(specifier).cloned() { if let Some(url) = inner.get_url(specifier).cloned() {
@ -220,14 +219,8 @@ impl LspUrlMap {
/// so we need to force it to in the mapping and nee to explicitly state whether /// so we need to force it to in the mapping and nee to explicitly state whether
/// this is a file or directory url. /// this is a file or directory url.
pub fn normalize_url(&self, url: &Url, kind: LspUrlKind) -> ModuleSpecifier { pub fn normalize_url(&self, url: &Url, kind: LspUrlKind) -> ModuleSpecifier {
if let Some(cache) = &self.local_http_cache { if let Some(remote_url) = self.cache.unvendored_specifier(url) {
if url.scheme() == "file" { return remote_url;
if let Ok(path) = url.to_file_path() {
if let Some(remote_url) = cache.get_remote_url(&path) {
return remote_url;
}
}
}
} }
let mut inner = self.inner.lock(); let mut inner = self.inner.lock();
if let Some(specifier) = inner.get_specifier(url).cloned() { if let Some(specifier) = inner.get_specifier(url).cloned() {
@ -296,7 +289,7 @@ mod tests {
let map = LspUrlMap::default(); let map = LspUrlMap::default();
let fixture = resolve_url("https://deno.land/x/pkg@1.0.0/mod.ts").unwrap(); let fixture = resolve_url("https://deno.land/x/pkg@1.0.0/mod.ts").unwrap();
let actual_url = map let actual_url = map
.normalize_specifier(&fixture) .normalize_specifier(&fixture, None)
.expect("could not handle specifier"); .expect("could not handle specifier");
let expected_url = let expected_url =
Url::parse("deno:/https/deno.land/x/pkg%401.0.0/mod.ts").unwrap(); 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); assert_eq!(&actual_specifier, &expected_specifier);
let actual_url = map let actual_url = map
.normalize_specifier(&actual_specifier) .normalize_specifier(&actual_specifier, None)
.unwrap() .unwrap()
.as_url() .as_url()
.clone(); .clone();
@ -331,7 +324,7 @@ mod tests {
let map = LspUrlMap::default(); 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 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 let actual_url = map
.normalize_specifier(&fixture) .normalize_specifier(&fixture, None)
.expect("could not handle specifier"); .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(); 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); assert_eq!(actual_url.as_url(), &expected_url);
@ -346,7 +339,7 @@ mod tests {
let map = LspUrlMap::default(); let map = LspUrlMap::default();
let fixture = resolve_url("data:application/typescript;base64,ZXhwb3J0IGNvbnN0IGEgPSAiYSI7CgpleHBvcnQgZW51bSBBIHsKICBBLAogIEIsCiAgQywKfQo=").unwrap(); let fixture = resolve_url("data:application/typescript;base64,ZXhwb3J0IGNvbnN0IGEgPSAiYSI7CgpleHBvcnQgZW51bSBBIHsKICBBLAogIEIsCiAgQywKfQo=").unwrap();
let actual_url = map let actual_url = map
.normalize_specifier(&fixture) .normalize_specifier(&fixture, None)
.expect("could not handle specifier"); .expect("could not handle specifier");
let expected_url = Url::parse("deno:/c21c7fc382b2b0553dc0864aa81a3acacfb7b3d1285ab5ae76da6abec213fb37/data_url.ts").unwrap(); let expected_url = Url::parse("deno:/c21c7fc382b2b0553dc0864aa81a3acacfb7b3d1285ab5ae76da6abec213fb37/data_url.ts").unwrap();
assert_eq!(actual_url.as_url(), &expected_url); assert_eq!(actual_url.as_url(), &expected_url);
@ -361,7 +354,7 @@ mod tests {
let map = LspUrlMap::default(); let map = LspUrlMap::default();
let fixture = resolve_url("http://localhost:8000/mod.ts").unwrap(); let fixture = resolve_url("http://localhost:8000/mod.ts").unwrap();
let actual_url = map let actual_url = map
.normalize_specifier(&fixture) .normalize_specifier(&fixture, None)
.expect("could not handle specifier"); .expect("could not handle specifier");
let expected_url = let expected_url =
Url::parse("deno:/http/localhost%3A8000/mod.ts").unwrap(); Url::parse("deno:/http/localhost%3A8000/mod.ts").unwrap();

View file

@ -280,13 +280,14 @@ fn lsp_import_map_remote() {
#[test] #[test]
fn lsp_import_map_data_url() { fn lsp_import_map_data_url() {
let context = TestContextBuilder::new().use_temp_cwd().build(); let context = TestContextBuilder::new().use_temp_cwd().build();
let temp_dir = context.temp_dir();
let mut client = context.new_lsp_command().build(); let mut client = context.new_lsp_command().build();
client.initialize(|builder| { client.initialize(|builder| {
builder.set_import_map("data:application/json;utf8,{\"imports\": { \"example\": \"https://deno.land/x/example/mod.ts\" }}"); builder.set_import_map("data:application/json;utf8,{\"imports\": { \"example\": \"https://deno.land/x/example/mod.ts\" }}");
}); });
let diagnostics = client.did_open(json!({ let diagnostics = client.did_open(json!({
"textDocument": { "textDocument": {
"uri": "file:///a/file.ts", "uri": temp_dir.uri().join("file.ts").unwrap(),
"languageId": "typescript", "languageId": "typescript",
"version": 1, "version": 1,
"text": "import example from \"example\";\n" "text": "import example from \"example\";\n"
@ -780,7 +781,7 @@ fn lsp_format_vendor_path() {
client.initialize_default(); client.initialize_default();
let diagnostics = client.did_open(json!({ let diagnostics = client.did_open(json!({
"textDocument": { "textDocument": {
"uri": "file:///a/file.ts", "uri": temp_dir.uri().join("file.ts").unwrap(),
"languageId": "typescript", "languageId": "typescript",
"version": 1, "version": 1,
"text": r#"import "http://localhost:4545/run/002_hello.ts";"#, "text": r#"import "http://localhost:4545/run/002_hello.ts";"#,
@ -802,7 +803,7 @@ fn lsp_format_vendor_path() {
"workspace/executeCommand", "workspace/executeCommand",
json!({ json!({
"command": "deno.cache", "command": "deno.cache",
"arguments": [[], "file:///a/file.ts"], "arguments": [[], temp_dir.uri().join("file.ts").unwrap()],
}), }),
); );
assert!(temp_dir assert!(temp_dir
@ -2622,7 +2623,7 @@ fn lsp_import_map_setting_with_deno_json() {
}); });
let diagnostics = client.did_open(json!({ let diagnostics = client.did_open(json!({
"textDocument": { "textDocument": {
"uri": "file:///a/file.ts", "uri": temp_dir.uri().join("file.ts").unwrap(),
"languageId": "typescript", "languageId": "typescript",
"version": 1, "version": 1,
"text": "import \"file2\";\n", "text": "import \"file2\";\n",
@ -7585,7 +7586,7 @@ fn lsp_completions_auto_import_and_quick_fix_with_import_map() {
client.did_open( client.did_open(
json!({ json!({
"textDocument": { "textDocument": {
"uri": "file:///a/file.ts", "uri": temp_dir.uri().join("file.ts").unwrap(),
"languageId": "typescript", "languageId": "typescript",
"version": 1, "version": 1,
"text": concat!( "text": concat!(
@ -7612,7 +7613,7 @@ fn lsp_completions_auto_import_and_quick_fix_with_import_map() {
"npm:chalk@~5", "npm:chalk@~5",
"http://localhost:4545/subdir/print_hello.ts", "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 // try auto-import with path
client.did_open(json!({ client.did_open(json!({
"textDocument": { "textDocument": {
"uri": "file:///a/a.ts", "uri": temp_dir.uri().join("a.ts").unwrap(),
"languageId": "typescript", "languageId": "typescript",
"version": 1, "version": 1,
"text": "getClie", "text": "getClie",
} }
})); }));
let list = client.get_completion_list( let list = client.get_completion_list(
"file:///a/a.ts", temp_dir.uri().join("a.ts").unwrap(),
(0, 7), (0, 7),
json!({ "triggerKind": 1 }), json!({ "triggerKind": 1 }),
); );
@ -7668,20 +7669,23 @@ fn lsp_completions_auto_import_and_quick_fix_with_import_map() {
// try quick fix with path // try quick fix with path
let diagnostics = client.did_open(json!({ let diagnostics = client.did_open(json!({
"textDocument": { "textDocument": {
"uri": "file:///a/b.ts", "uri": temp_dir.uri().join("b.ts").unwrap(),
"languageId": "typescript", "languageId": "typescript",
"version": 1, "version": 1,
"text": "getClient", "text": "getClient",
} }
})); }));
let diagnostics = diagnostics 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; .diagnostics;
let res = client.write_request( let res = client.write_request(
"textDocument/codeAction", "textDocument/codeAction",
json!(json!({ json!(json!({
"textDocument": { "textDocument": {
"uri": "file:///a/b.ts" "uri": temp_dir.uri().join("b.ts").unwrap()
}, },
"range": { "range": {
"start": { "line": 0, "character": 0 }, "start": { "line": 0, "character": 0 },
@ -7713,7 +7717,7 @@ fn lsp_completions_auto_import_and_quick_fix_with_import_map() {
"edit": { "edit": {
"documentChanges": [{ "documentChanges": [{
"textDocument": { "textDocument": {
"uri": "file:///a/b.ts", "uri": temp_dir.uri().join("b.ts").unwrap(),
"version": 1, "version": 1,
}, },
"edits": [{ "edits": [{
@ -7731,7 +7735,7 @@ fn lsp_completions_auto_import_and_quick_fix_with_import_map() {
// try auto-import without path // try auto-import without path
client.did_open(json!({ client.did_open(json!({
"textDocument": { "textDocument": {
"uri": "file:///a/c.ts", "uri": temp_dir.uri().join("c.ts").unwrap(),
"languageId": "typescript", "languageId": "typescript",
"version": 1, "version": 1,
"text": "chal", "text": "chal",
@ -7739,7 +7743,7 @@ fn lsp_completions_auto_import_and_quick_fix_with_import_map() {
})); }));
let list = client.get_completion_list( let list = client.get_completion_list(
"file:///a/c.ts", temp_dir.uri().join("c.ts").unwrap(),
(0, 4), (0, 4),
json!({ "triggerKind": 1 }), json!({ "triggerKind": 1 }),
); );
@ -7778,20 +7782,23 @@ fn lsp_completions_auto_import_and_quick_fix_with_import_map() {
// try quick fix without path // try quick fix without path
let diagnostics = client.did_open(json!({ let diagnostics = client.did_open(json!({
"textDocument": { "textDocument": {
"uri": "file:///a/d.ts", "uri": temp_dir.uri().join("d.ts").unwrap(),
"languageId": "typescript", "languageId": "typescript",
"version": 1, "version": 1,
"text": "chalk", "text": "chalk",
} }
})); }));
let diagnostics = diagnostics 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; .diagnostics;
let res = client.write_request( let res = client.write_request(
"textDocument/codeAction", "textDocument/codeAction",
json!(json!({ json!(json!({
"textDocument": { "textDocument": {
"uri": "file:///a/d.ts" "uri": temp_dir.uri().join("d.ts").unwrap()
}, },
"range": { "range": {
"start": { "line": 0, "character": 0 }, "start": { "line": 0, "character": 0 },
@ -7823,7 +7830,7 @@ fn lsp_completions_auto_import_and_quick_fix_with_import_map() {
"edit": { "edit": {
"documentChanges": [{ "documentChanges": [{
"textDocument": { "textDocument": {
"uri": "file:///a/d.ts", "uri": temp_dir.uri().join("d.ts").unwrap(),
"version": 1, "version": 1,
}, },
"edits": [{ "edits": [{
@ -7841,7 +7848,7 @@ fn lsp_completions_auto_import_and_quick_fix_with_import_map() {
// try auto-import with http import map // try auto-import with http import map
client.did_open(json!({ client.did_open(json!({
"textDocument": { "textDocument": {
"uri": "file:///a/e.ts", "uri": temp_dir.uri().join("e.ts").unwrap(),
"languageId": "typescript", "languageId": "typescript",
"version": 1, "version": 1,
"text": "printH", "text": "printH",
@ -7849,7 +7856,7 @@ fn lsp_completions_auto_import_and_quick_fix_with_import_map() {
})); }));
let list = client.get_completion_list( let list = client.get_completion_list(
"file:///a/e.ts", temp_dir.uri().join("e.ts").unwrap(),
(0, 6), (0, 6),
json!({ "triggerKind": 1 }), json!({ "triggerKind": 1 }),
); );
@ -7888,20 +7895,23 @@ fn lsp_completions_auto_import_and_quick_fix_with_import_map() {
// try quick fix with http import // try quick fix with http import
let diagnostics = client.did_open(json!({ let diagnostics = client.did_open(json!({
"textDocument": { "textDocument": {
"uri": "file:///a/f.ts", "uri": temp_dir.uri().join("f.ts").unwrap(),
"languageId": "typescript", "languageId": "typescript",
"version": 1, "version": 1,
"text": "printHello", "text": "printHello",
} }
})); }));
let diagnostics = diagnostics 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; .diagnostics;
let res = client.write_request( let res = client.write_request(
"textDocument/codeAction", "textDocument/codeAction",
json!(json!({ json!(json!({
"textDocument": { "textDocument": {
"uri": "file:///a/f.ts" "uri": temp_dir.uri().join("f.ts").unwrap()
}, },
"range": { "range": {
"start": { "line": 0, "character": 0 }, "start": { "line": 0, "character": 0 },
@ -7933,7 +7943,7 @@ fn lsp_completions_auto_import_and_quick_fix_with_import_map() {
"edit": { "edit": {
"documentChanges": [{ "documentChanges": [{
"textDocument": { "textDocument": {
"uri": "file:///a/f.ts", "uri": temp_dir.uri().join("f.ts").unwrap(),
"version": 1, "version": 1,
}, },
"edits": [{ "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 // try auto-import with npm package with sub-path on value side of import map
client.did_open(json!({ client.did_open(json!({
"textDocument": { "textDocument": {
"uri": "file:///a/nested_path.ts", "uri": temp_dir.uri().join("nested_path.ts").unwrap(),
"languageId": "typescript", "languageId": "typescript",
"version": 1, "version": 1,
"text": "entry", "text": "entry",
} }
})); }));
let list = client.get_completion_list( let list = client.get_completion_list(
"file:///a/nested_path.ts", temp_dir.uri().join("nested_path.ts").unwrap(),
(0, 5), (0, 5),
json!({ "triggerKind": 1 }), json!({ "triggerKind": 1 }),
); );
@ -11001,7 +11011,7 @@ fn lsp_lint_with_config() {
let diagnostics = client.did_open(json!({ let diagnostics = client.did_open(json!({
"textDocument": { "textDocument": {
"uri": "file:///a/file.ts", "uri": temp_dir.uri().join("file.ts").unwrap(),
"languageId": "typescript", "languageId": "typescript",
"version": 1, "version": 1,
"text": "// TODO: fixme\nexport async function non_camel_case() {\nconsole.log(\"finished!\")\n}" "text": "// TODO: fixme\nexport async function non_camel_case() {\nconsole.log(\"finished!\")\n}"
@ -12104,6 +12114,323 @@ fn lsp_vendor_dir() {
client.shutdown(); 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] #[test]
fn lsp_deno_json_workspace_fmt_config() { fn lsp_deno_json_workspace_fmt_config() {
let context = TestContextBuilder::new().use_temp_cwd().build(); 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); assert!(!line.contains("Running npm resolution."), "Line: {}", line);
line.contains("Server ready.") line.contains("Server ready.")
}); });
assert_eq!(skipping_count, 1); assert_eq!(skipping_count, 2);
client.shutdown(); client.shutdown();
} }