mirror of
https://github.com/denoland/deno.git
synced 2024-12-24 08:09:08 -05:00
feat(unstable/lsp): support navigating to deno_modules folder (#20030)
Closes #20015 Closes https://github.com/denoland/vscode_deno/issues/850 (only for deno_modules, but I don't think this will be possible for the global cache)
This commit is contained in:
parent
e8d03119a0
commit
480894e5c8
11 changed files with 586 additions and 80 deletions
424
cli/cache/http_cache/local.rs
vendored
424
cli/cache/http_cache/local.rs
vendored
|
@ -11,12 +11,9 @@ use std::time::SystemTime;
|
||||||
use deno_ast::MediaType;
|
use deno_ast::MediaType;
|
||||||
use deno_core::error::AnyError;
|
use deno_core::error::AnyError;
|
||||||
use deno_core::parking_lot::RwLock;
|
use deno_core::parking_lot::RwLock;
|
||||||
use deno_core::serde_json;
|
|
||||||
use deno_core::url::Url;
|
use deno_core::url::Url;
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use serde::Deserialize;
|
|
||||||
use serde::Serialize;
|
|
||||||
|
|
||||||
use crate::cache::CACHE_PERM;
|
use crate::cache::CACHE_PERM;
|
||||||
use crate::http_util::HeadersMap;
|
use crate::http_util::HeadersMap;
|
||||||
|
@ -31,6 +28,129 @@ use super::CachedUrlMetadata;
|
||||||
use super::HttpCache;
|
use super::HttpCache;
|
||||||
use super::HttpCacheItemKey;
|
use super::HttpCacheItemKey;
|
||||||
|
|
||||||
|
/// A deno_modules http cache for the lsp that provides functionality
|
||||||
|
/// for doing a reverse mapping.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct LocalLspHttpCache {
|
||||||
|
cache: LocalHttpCache,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LocalLspHttpCache {
|
||||||
|
pub fn new(path: PathBuf, global_cache: Arc<GlobalHttpCache>) -> Self {
|
||||||
|
assert!(path.is_absolute());
|
||||||
|
let manifest = LocalCacheManifest::new_for_lsp(path.join("manifest.json"));
|
||||||
|
Self {
|
||||||
|
cache: LocalHttpCache {
|
||||||
|
path,
|
||||||
|
manifest,
|
||||||
|
global_cache,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_file_url(&self, url: &Url) -> Option<Url> {
|
||||||
|
{
|
||||||
|
let data = self.cache.manifest.data.read();
|
||||||
|
if let Some(data) = data.get(url) {
|
||||||
|
if let Some(path) = &data.path {
|
||||||
|
return Url::from_file_path(self.cache.path.join(path)).ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match self.cache.get_cache_filepath(url, &Default::default()) {
|
||||||
|
Ok(path) if path.exists() => Url::from_file_path(path).ok(),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_remote_url(&self, path: &Path) -> Option<Url> {
|
||||||
|
let Ok(path) = path.strip_prefix(&self.cache.path) else {
|
||||||
|
return None; // not in this directory
|
||||||
|
};
|
||||||
|
let has_hashed_component = path
|
||||||
|
.components()
|
||||||
|
.any(|p| p.as_os_str().to_string_lossy().starts_with('#'));
|
||||||
|
if has_hashed_component {
|
||||||
|
// check in the manifest
|
||||||
|
{
|
||||||
|
let data = self.cache.manifest.data.read();
|
||||||
|
if let Some(url) = data.get_reverse_mapping(path) {
|
||||||
|
return Some(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
// we can work backwards from the path to the url
|
||||||
|
let mut parts = Vec::new();
|
||||||
|
for (i, part) in path.components().enumerate() {
|
||||||
|
let part = part.as_os_str().to_string_lossy();
|
||||||
|
if i == 0 {
|
||||||
|
let mut result = String::new();
|
||||||
|
let part = if let Some(part) = part.strip_prefix("http_") {
|
||||||
|
result.push_str("http://");
|
||||||
|
part
|
||||||
|
} else {
|
||||||
|
result.push_str("https://");
|
||||||
|
&part
|
||||||
|
};
|
||||||
|
if let Some((domain, port)) = part.rsplit_once('_') {
|
||||||
|
result.push_str(&format!("{}:{}", domain, port));
|
||||||
|
} else {
|
||||||
|
result.push_str(part);
|
||||||
|
}
|
||||||
|
parts.push(result);
|
||||||
|
} else {
|
||||||
|
parts.push(part.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Url::parse(&parts.join("/")).ok()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HttpCache for LocalLspHttpCache {
|
||||||
|
fn cache_item_key<'a>(
|
||||||
|
&self,
|
||||||
|
url: &'a Url,
|
||||||
|
) -> Result<HttpCacheItemKey<'a>, AnyError> {
|
||||||
|
self.cache.cache_item_key(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn contains(&self, url: &Url) -> bool {
|
||||||
|
self.cache.contains(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set(
|
||||||
|
&self,
|
||||||
|
url: &Url,
|
||||||
|
headers: HeadersMap,
|
||||||
|
content: &[u8],
|
||||||
|
) -> Result<(), AnyError> {
|
||||||
|
self.cache.set(url, headers, content)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_modified_time(
|
||||||
|
&self,
|
||||||
|
key: &HttpCacheItemKey,
|
||||||
|
) -> Result<Option<SystemTime>, AnyError> {
|
||||||
|
self.cache.read_modified_time(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_file_bytes(
|
||||||
|
&self,
|
||||||
|
key: &HttpCacheItemKey,
|
||||||
|
) -> Result<Option<Vec<u8>>, AnyError> {
|
||||||
|
self.cache.read_file_bytes(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_metadata(
|
||||||
|
&self,
|
||||||
|
key: &HttpCacheItemKey,
|
||||||
|
) -> Result<Option<CachedUrlMetadata>, AnyError> {
|
||||||
|
self.cache.read_metadata(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct LocalHttpCache {
|
pub struct LocalHttpCache {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
|
@ -181,7 +301,7 @@ impl HttpCache for LocalHttpCache {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LocalCacheSubPath {
|
pub(super) struct LocalCacheSubPath {
|
||||||
pub has_hash: bool,
|
pub has_hash: bool,
|
||||||
pub parts: Vec<String>,
|
pub parts: Vec<String>,
|
||||||
}
|
}
|
||||||
|
@ -194,6 +314,14 @@ impl LocalCacheSubPath {
|
||||||
}
|
}
|
||||||
path
|
path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn as_relative_path(&self) -> PathBuf {
|
||||||
|
let mut path = PathBuf::with_capacity(self.parts.len());
|
||||||
|
for part in &self.parts {
|
||||||
|
path.push(part);
|
||||||
|
}
|
||||||
|
path
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn url_to_local_sub_path(
|
fn url_to_local_sub_path(
|
||||||
|
@ -335,48 +463,28 @@ fn url_to_local_sub_path(
|
||||||
Ok(LocalCacheSubPath { has_hash, parts })
|
Ok(LocalCacheSubPath { has_hash, parts })
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone)]
|
|
||||||
struct LocalCacheManifestData {
|
|
||||||
serialized: SerializedLocalCacheManifestData,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
||||||
struct SerializedLocalCacheManifestDataModule {
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub path: Option<String>,
|
|
||||||
#[serde(
|
|
||||||
default = "IndexMap::new",
|
|
||||||
skip_serializing_if = "IndexMap::is_empty"
|
|
||||||
)]
|
|
||||||
pub headers: IndexMap<String, String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
|
||||||
struct SerializedLocalCacheManifestData {
|
|
||||||
pub modules: IndexMap<Url, SerializedLocalCacheManifestDataModule>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct LocalCacheManifest {
|
struct LocalCacheManifest {
|
||||||
file_path: PathBuf,
|
file_path: PathBuf,
|
||||||
data: RwLock<LocalCacheManifestData>,
|
data: RwLock<manifest::LocalCacheManifestData>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LocalCacheManifest {
|
impl LocalCacheManifest {
|
||||||
pub fn new(file_path: PathBuf) -> Self {
|
pub fn new(file_path: PathBuf) -> Self {
|
||||||
let serialized: SerializedLocalCacheManifestData =
|
Self::new_internal(file_path, false)
|
||||||
std::fs::read(&file_path)
|
}
|
||||||
.ok()
|
|
||||||
.and_then(|data| match serde_json::from_slice(&data) {
|
pub fn new_for_lsp(file_path: PathBuf) -> Self {
|
||||||
Ok(data) => Some(data),
|
Self::new_internal(file_path, true)
|
||||||
Err(err) => {
|
}
|
||||||
log::debug!("Failed deserializing local cache manifest: {:#}", err);
|
|
||||||
None
|
fn new_internal(file_path: PathBuf, use_reverse_mapping: bool) -> Self {
|
||||||
}
|
let text = std::fs::read_to_string(&file_path).ok();
|
||||||
})
|
|
||||||
.unwrap_or_default();
|
|
||||||
Self {
|
Self {
|
||||||
data: RwLock::new(LocalCacheManifestData { serialized }),
|
data: RwLock::new(manifest::LocalCacheManifestData::new(
|
||||||
|
text.as_deref(),
|
||||||
|
use_reverse_mapping,
|
||||||
|
)),
|
||||||
file_path,
|
file_path,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -419,9 +527,9 @@ impl LocalCacheManifest {
|
||||||
let mut data = self.data.write();
|
let mut data = self.data.write();
|
||||||
let is_empty = headers_subset.is_empty() && !sub_path.has_hash;
|
let is_empty = headers_subset.is_empty() && !sub_path.has_hash;
|
||||||
let has_changed = if is_empty {
|
let has_changed = if is_empty {
|
||||||
data.serialized.modules.remove(&url).is_some()
|
data.remove(&url, &sub_path)
|
||||||
} else {
|
} else {
|
||||||
let new_data = SerializedLocalCacheManifestDataModule {
|
let new_data = manifest::SerializedLocalCacheManifestDataModule {
|
||||||
path: if headers_subset.contains_key("location") {
|
path: if headers_subset.contains_key("location") {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
|
@ -429,10 +537,10 @@ impl LocalCacheManifest {
|
||||||
},
|
},
|
||||||
headers: headers_subset,
|
headers: headers_subset,
|
||||||
};
|
};
|
||||||
if data.serialized.modules.get(&url) == Some(&new_data) {
|
if data.get(&url) == Some(&new_data) {
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
data.serialized.modules.insert(url.clone(), new_data);
|
data.insert(url.clone(), &sub_path, new_data);
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -440,11 +548,8 @@ impl LocalCacheManifest {
|
||||||
if has_changed {
|
if has_changed {
|
||||||
// don't bother ensuring the directory here because it will
|
// don't bother ensuring the directory here because it will
|
||||||
// eventually be created by files being added to the cache
|
// eventually be created by files being added to the cache
|
||||||
let result = atomic_write_file(
|
let result =
|
||||||
&self.file_path,
|
atomic_write_file(&self.file_path, data.as_json(), CACHE_PERM);
|
||||||
serde_json::to_string_pretty(&data.serialized).unwrap(),
|
|
||||||
CACHE_PERM,
|
|
||||||
);
|
|
||||||
if let Err(err) = result {
|
if let Err(err) = result {
|
||||||
log::debug!("Failed saving local cache manifest: {:#}", err);
|
log::debug!("Failed saving local cache manifest: {:#}", err);
|
||||||
}
|
}
|
||||||
|
@ -453,7 +558,7 @@ impl LocalCacheManifest {
|
||||||
|
|
||||||
pub fn get_metadata(&self, url: &Url) -> Option<CachedUrlMetadata> {
|
pub fn get_metadata(&self, url: &Url) -> Option<CachedUrlMetadata> {
|
||||||
let data = self.data.read();
|
let data = self.data.read();
|
||||||
match data.serialized.modules.get(url) {
|
match data.get(url) {
|
||||||
Some(module) => {
|
Some(module) => {
|
||||||
let headers = module
|
let headers = module
|
||||||
.headers
|
.headers
|
||||||
|
@ -500,6 +605,126 @@ impl LocalCacheManifest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is in a separate module in order to enforce keeping
|
||||||
|
// the internal implementation private.
|
||||||
|
mod manifest {
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use deno_core::serde_json;
|
||||||
|
use deno_core::url::Url;
|
||||||
|
use indexmap::IndexMap;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use super::LocalCacheSubPath;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
pub struct SerializedLocalCacheManifestDataModule {
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub path: Option<String>,
|
||||||
|
#[serde(
|
||||||
|
default = "IndexMap::new",
|
||||||
|
skip_serializing_if = "IndexMap::is_empty"
|
||||||
|
)]
|
||||||
|
pub headers: IndexMap<String, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||||
|
struct SerializedLocalCacheManifestData {
|
||||||
|
pub modules: IndexMap<Url, SerializedLocalCacheManifestDataModule>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone)]
|
||||||
|
pub(super) struct LocalCacheManifestData {
|
||||||
|
serialized: SerializedLocalCacheManifestData,
|
||||||
|
// reverse mapping used in the lsp
|
||||||
|
reverse_mapping: Option<HashMap<PathBuf, Url>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LocalCacheManifestData {
|
||||||
|
pub fn new(maybe_text: Option<&str>, use_reverse_mapping: bool) -> Self {
|
||||||
|
let serialized: SerializedLocalCacheManifestData = maybe_text
|
||||||
|
.and_then(|text| match serde_json::from_str(text) {
|
||||||
|
Ok(data) => Some(data),
|
||||||
|
Err(err) => {
|
||||||
|
log::debug!("Failed deserializing local cache manifest: {:#}", err);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
let reverse_mapping = if use_reverse_mapping {
|
||||||
|
Some(
|
||||||
|
serialized
|
||||||
|
.modules
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(url, module)| {
|
||||||
|
module.path.as_ref().map(|path| {
|
||||||
|
let path = if cfg!(windows) {
|
||||||
|
PathBuf::from(path.split('/').collect::<Vec<_>>().join("\\"))
|
||||||
|
} else {
|
||||||
|
PathBuf::from(path)
|
||||||
|
};
|
||||||
|
(path, url.clone())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<HashMap<_, _>>(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
Self {
|
||||||
|
serialized,
|
||||||
|
reverse_mapping,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(
|
||||||
|
&self,
|
||||||
|
url: &Url,
|
||||||
|
) -> Option<&SerializedLocalCacheManifestDataModule> {
|
||||||
|
self.serialized.modules.get(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_reverse_mapping(&self, path: &Path) -> Option<Url> {
|
||||||
|
debug_assert!(self.reverse_mapping.is_some()); // only call this if you're in the lsp
|
||||||
|
self
|
||||||
|
.reverse_mapping
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|mapping| mapping.get(path))
|
||||||
|
.cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert(
|
||||||
|
&mut self,
|
||||||
|
url: Url,
|
||||||
|
sub_path: &LocalCacheSubPath,
|
||||||
|
new_data: SerializedLocalCacheManifestDataModule,
|
||||||
|
) {
|
||||||
|
if let Some(reverse_mapping) = &mut self.reverse_mapping {
|
||||||
|
reverse_mapping.insert(sub_path.as_relative_path(), url.clone());
|
||||||
|
}
|
||||||
|
self.serialized.modules.insert(url, new_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove(&mut self, url: &Url, sub_path: &LocalCacheSubPath) -> bool {
|
||||||
|
if self.serialized.modules.remove(url).is_some() {
|
||||||
|
if let Some(reverse_mapping) = &mut self.reverse_mapping {
|
||||||
|
reverse_mapping.remove(&sub_path.as_relative_path());
|
||||||
|
}
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_json(&self) -> String {
|
||||||
|
serde_json::to_string_pretty(&self.serialized).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -869,4 +1094,107 @@ mod test {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lsp_local_cache() {
|
||||||
|
let temp_dir = TempDir::new();
|
||||||
|
let global_cache_path = temp_dir.path().join("global");
|
||||||
|
let local_cache_path = temp_dir.path().join("local");
|
||||||
|
let global_cache =
|
||||||
|
Arc::new(GlobalHttpCache::new(global_cache_path.to_path_buf()));
|
||||||
|
let local_cache = LocalLspHttpCache::new(
|
||||||
|
local_cache_path.to_path_buf(),
|
||||||
|
global_cache.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// mapped url
|
||||||
|
{
|
||||||
|
let url = Url::parse("https://deno.land/x/mod.ts").unwrap();
|
||||||
|
let content = "export const test = 5;";
|
||||||
|
global_cache
|
||||||
|
.set(
|
||||||
|
&url,
|
||||||
|
HashMap::from([(
|
||||||
|
"content-type".to_string(),
|
||||||
|
"application/typescript".to_string(),
|
||||||
|
)]),
|
||||||
|
content.as_bytes(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let key = local_cache.cache_item_key(&url).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
String::from_utf8(local_cache.read_file_bytes(&key).unwrap().unwrap())
|
||||||
|
.unwrap(),
|
||||||
|
content
|
||||||
|
);
|
||||||
|
|
||||||
|
// check getting the file url works
|
||||||
|
let file_url = local_cache.get_file_url(&url);
|
||||||
|
let expected = local_cache_path
|
||||||
|
.uri_dir()
|
||||||
|
.join("deno.land/x/mod.ts")
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(file_url, Some(expected));
|
||||||
|
|
||||||
|
// get the reverse mapping
|
||||||
|
let mapping = local_cache.get_remote_url(
|
||||||
|
local_cache_path
|
||||||
|
.join("deno.land")
|
||||||
|
.join("x")
|
||||||
|
.join("mod.ts")
|
||||||
|
.as_path(),
|
||||||
|
);
|
||||||
|
assert_eq!(mapping.as_ref(), Some(&url));
|
||||||
|
}
|
||||||
|
|
||||||
|
// now try a file with a different content-type header
|
||||||
|
{
|
||||||
|
let url =
|
||||||
|
Url::parse("https://deno.land/x/different_content_type.ts").unwrap();
|
||||||
|
let content = "export const test = 5;";
|
||||||
|
global_cache
|
||||||
|
.set(
|
||||||
|
&url,
|
||||||
|
HashMap::from([(
|
||||||
|
"content-type".to_string(),
|
||||||
|
"application/javascript".to_string(),
|
||||||
|
)]),
|
||||||
|
content.as_bytes(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let key = local_cache.cache_item_key(&url).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
String::from_utf8(local_cache.read_file_bytes(&key).unwrap().unwrap())
|
||||||
|
.unwrap(),
|
||||||
|
content
|
||||||
|
);
|
||||||
|
|
||||||
|
let file_url = local_cache.get_file_url(&url).unwrap();
|
||||||
|
let path = file_url.to_file_path().unwrap();
|
||||||
|
assert!(path.exists());
|
||||||
|
let mapping = local_cache.get_remote_url(&path);
|
||||||
|
assert_eq!(mapping.as_ref(), Some(&url));
|
||||||
|
}
|
||||||
|
|
||||||
|
// try an http specifier that can't be mapped to the file system
|
||||||
|
{
|
||||||
|
let url = Url::parse("http://deno.land/INVALID/Module.ts?dev").unwrap();
|
||||||
|
let content = "export const test = 5;";
|
||||||
|
global_cache
|
||||||
|
.set(&url, HashMap::new(), content.as_bytes())
|
||||||
|
.unwrap();
|
||||||
|
let key = local_cache.cache_item_key(&url).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
String::from_utf8(local_cache.read_file_bytes(&key).unwrap().unwrap())
|
||||||
|
.unwrap(),
|
||||||
|
content
|
||||||
|
);
|
||||||
|
|
||||||
|
let file_url = local_cache.get_file_url(&url).unwrap();
|
||||||
|
let path = file_url.to_file_path().unwrap();
|
||||||
|
assert!(path.exists());
|
||||||
|
let mapping = local_cache.get_remote_url(&path);
|
||||||
|
assert_eq!(mapping.as_ref(), Some(&url));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
1
cli/cache/http_cache/mod.rs
vendored
1
cli/cache/http_cache/mod.rs
vendored
|
@ -16,6 +16,7 @@ mod local;
|
||||||
pub use global::url_to_filename;
|
pub use global::url_to_filename;
|
||||||
pub use global::GlobalHttpCache;
|
pub use global::GlobalHttpCache;
|
||||||
pub use local::LocalHttpCache;
|
pub use local::LocalHttpCache;
|
||||||
|
pub use local::LocalLspHttpCache;
|
||||||
|
|
||||||
/// Cached metadata about a url.
|
/// Cached metadata about a url.
|
||||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
|
1
cli/cache/mod.rs
vendored
1
cli/cache/mod.rs
vendored
|
@ -38,6 +38,7 @@ pub use http_cache::CachedUrlMetadata;
|
||||||
pub use http_cache::GlobalHttpCache;
|
pub use http_cache::GlobalHttpCache;
|
||||||
pub use http_cache::HttpCache;
|
pub use http_cache::HttpCache;
|
||||||
pub use http_cache::LocalHttpCache;
|
pub use http_cache::LocalHttpCache;
|
||||||
|
pub use http_cache::LocalLspHttpCache;
|
||||||
pub use incremental::IncrementalCache;
|
pub use incremental::IncrementalCache;
|
||||||
pub use node::NodeAnalysisCache;
|
pub use node::NodeAnalysisCache;
|
||||||
pub use parsed_source::ParsedSourceCache;
|
pub use parsed_source::ParsedSourceCache;
|
||||||
|
|
|
@ -154,11 +154,14 @@ impl OutsideLockClient {
|
||||||
|
|
||||||
pub async fn publish_diagnostics(
|
pub async fn publish_diagnostics(
|
||||||
&self,
|
&self,
|
||||||
uri: lsp::Url,
|
uri: LspClientUrl,
|
||||||
diags: Vec<lsp::Diagnostic>,
|
diags: Vec<lsp::Diagnostic>,
|
||||||
version: Option<i32>,
|
version: Option<i32>,
|
||||||
) {
|
) {
|
||||||
self.0.publish_diagnostics(uri, diags, version).await;
|
self
|
||||||
|
.0
|
||||||
|
.publish_diagnostics(uri.into_url(), diags, version)
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,8 @@ use super::language_server::StateSnapshot;
|
||||||
use super::performance::Performance;
|
use super::performance::Performance;
|
||||||
use super::tsc;
|
use super::tsc;
|
||||||
use super::tsc::TsServer;
|
use super::tsc::TsServer;
|
||||||
|
use super::urls::LspClientUrl;
|
||||||
|
use super::urls::LspUrlMap;
|
||||||
|
|
||||||
use crate::args::LintOptions;
|
use crate::args::LintOptions;
|
||||||
use crate::graph_util;
|
use crate::graph_util;
|
||||||
|
@ -54,6 +56,7 @@ pub struct DiagnosticServerUpdateMessage {
|
||||||
pub snapshot: Arc<StateSnapshot>,
|
pub snapshot: Arc<StateSnapshot>,
|
||||||
pub config: Arc<ConfigSnapshot>,
|
pub config: Arc<ConfigSnapshot>,
|
||||||
pub lint_options: LintOptions,
|
pub lint_options: LintOptions,
|
||||||
|
pub url_map: LspUrlMap,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DiagnosticRecord {
|
struct DiagnosticRecord {
|
||||||
|
@ -107,6 +110,7 @@ impl DiagnosticsPublisher {
|
||||||
&self,
|
&self,
|
||||||
source: DiagnosticSource,
|
source: DiagnosticSource,
|
||||||
diagnostics: DiagnosticVec,
|
diagnostics: DiagnosticVec,
|
||||||
|
url_map: &LspUrlMap,
|
||||||
token: &CancellationToken,
|
token: &CancellationToken,
|
||||||
) -> usize {
|
) -> usize {
|
||||||
let mut diagnostics_by_specifier =
|
let mut diagnostics_by_specifier =
|
||||||
|
@ -141,7 +145,9 @@ impl DiagnosticsPublisher {
|
||||||
.client
|
.client
|
||||||
.when_outside_lsp_lock()
|
.when_outside_lsp_lock()
|
||||||
.publish_diagnostics(
|
.publish_diagnostics(
|
||||||
record.specifier,
|
url_map
|
||||||
|
.normalize_specifier(&record.specifier)
|
||||||
|
.unwrap_or(LspClientUrl::new(record.specifier)),
|
||||||
all_specifier_diagnostics,
|
all_specifier_diagnostics,
|
||||||
version,
|
version,
|
||||||
)
|
)
|
||||||
|
@ -169,7 +175,9 @@ impl DiagnosticsPublisher {
|
||||||
.client
|
.client
|
||||||
.when_outside_lsp_lock()
|
.when_outside_lsp_lock()
|
||||||
.publish_diagnostics(
|
.publish_diagnostics(
|
||||||
specifier.clone(),
|
url_map
|
||||||
|
.normalize_specifier(specifier)
|
||||||
|
.unwrap_or_else(|_| LspClientUrl::new(specifier.clone())),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
removed_value.version,
|
removed_value.version,
|
||||||
)
|
)
|
||||||
|
@ -366,9 +374,11 @@ impl DiagnosticsServer {
|
||||||
snapshot,
|
snapshot,
|
||||||
config,
|
config,
|
||||||
lint_options,
|
lint_options,
|
||||||
|
url_map,
|
||||||
},
|
},
|
||||||
batch_index,
|
batch_index,
|
||||||
} = message;
|
} = message;
|
||||||
|
let url_map = Arc::new(url_map);
|
||||||
|
|
||||||
// cancel the previous run
|
// cancel the previous run
|
||||||
token.cancel();
|
token.cancel();
|
||||||
|
@ -383,6 +393,7 @@ impl DiagnosticsServer {
|
||||||
let ts_diagnostics_store = ts_diagnostics_store.clone();
|
let ts_diagnostics_store = ts_diagnostics_store.clone();
|
||||||
let snapshot = snapshot.clone();
|
let snapshot = snapshot.clone();
|
||||||
let config = config.clone();
|
let config = config.clone();
|
||||||
|
let url_map = url_map.clone();
|
||||||
async move {
|
async move {
|
||||||
if let Some(previous_handle) = previous_ts_handle {
|
if let Some(previous_handle) = previous_ts_handle {
|
||||||
// Wait on the previous run to complete in order to prevent
|
// Wait on the previous run to complete in order to prevent
|
||||||
|
@ -419,7 +430,12 @@ impl DiagnosticsServer {
|
||||||
if !token.is_cancelled() {
|
if !token.is_cancelled() {
|
||||||
ts_diagnostics_store.update(&diagnostics);
|
ts_diagnostics_store.update(&diagnostics);
|
||||||
messages_len = diagnostics_publisher
|
messages_len = diagnostics_publisher
|
||||||
.publish(DiagnosticSource::Ts, diagnostics, &token)
|
.publish(
|
||||||
|
DiagnosticSource::Ts,
|
||||||
|
diagnostics,
|
||||||
|
&url_map,
|
||||||
|
&token,
|
||||||
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
if !token.is_cancelled() {
|
if !token.is_cancelled() {
|
||||||
|
@ -447,6 +463,7 @@ impl DiagnosticsServer {
|
||||||
let token = token.clone();
|
let token = token.clone();
|
||||||
let snapshot = snapshot.clone();
|
let snapshot = snapshot.clone();
|
||||||
let config = config.clone();
|
let config = config.clone();
|
||||||
|
let url_map = url_map.clone();
|
||||||
async move {
|
async move {
|
||||||
if let Some(previous_handle) = previous_deps_handle {
|
if let Some(previous_handle) = previous_deps_handle {
|
||||||
previous_handle.await;
|
previous_handle.await;
|
||||||
|
@ -463,7 +480,12 @@ impl DiagnosticsServer {
|
||||||
let mut messages_len = 0;
|
let mut messages_len = 0;
|
||||||
if !token.is_cancelled() {
|
if !token.is_cancelled() {
|
||||||
messages_len = diagnostics_publisher
|
messages_len = diagnostics_publisher
|
||||||
.publish(DiagnosticSource::Deno, diagnostics, &token)
|
.publish(
|
||||||
|
DiagnosticSource::Deno,
|
||||||
|
diagnostics,
|
||||||
|
&url_map,
|
||||||
|
&token,
|
||||||
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
if !token.is_cancelled() {
|
if !token.is_cancelled() {
|
||||||
|
@ -491,6 +513,7 @@ impl DiagnosticsServer {
|
||||||
let token = token.clone();
|
let token = token.clone();
|
||||||
let snapshot = snapshot.clone();
|
let snapshot = snapshot.clone();
|
||||||
let config = config.clone();
|
let config = config.clone();
|
||||||
|
let url_map = url_map.clone();
|
||||||
async move {
|
async move {
|
||||||
if let Some(previous_handle) = previous_lint_handle {
|
if let Some(previous_handle) = previous_lint_handle {
|
||||||
previous_handle.await;
|
previous_handle.await;
|
||||||
|
@ -514,7 +537,12 @@ impl DiagnosticsServer {
|
||||||
let mut messages_len = 0;
|
let mut messages_len = 0;
|
||||||
if !token.is_cancelled() {
|
if !token.is_cancelled() {
|
||||||
messages_len = diagnostics_publisher
|
messages_len = diagnostics_publisher
|
||||||
.publish(DiagnosticSource::Lint, diagnostics, &token)
|
.publish(
|
||||||
|
DiagnosticSource::Lint,
|
||||||
|
diagnostics,
|
||||||
|
&url_map,
|
||||||
|
&token,
|
||||||
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
if !token.is_cancelled() {
|
if !token.is_cancelled() {
|
||||||
|
|
|
@ -969,6 +969,13 @@ impl Documents {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn resolve_redirected(
|
||||||
|
&self,
|
||||||
|
specifier: &ModuleSpecifier,
|
||||||
|
) -> Option<ModuleSpecifier> {
|
||||||
|
self.specifier_resolver.resolve(specifier)
|
||||||
|
}
|
||||||
|
|
||||||
/// Return `true` if the specifier can be resolved to a document.
|
/// Return `true` if the specifier can be resolved to a document.
|
||||||
pub fn exists(&self, specifier: &ModuleSpecifier) -> bool {
|
pub fn exists(&self, specifier: &ModuleSpecifier) -> bool {
|
||||||
let specifier = self.specifier_resolver.resolve(specifier);
|
let specifier = self.specifier_resolver.resolve(specifier);
|
||||||
|
@ -1498,7 +1505,7 @@ impl Documents {
|
||||||
self.resolve_dependency(specifier, maybe_node_resolver)
|
self.resolve_dependency(specifier, maybe_node_resolver)
|
||||||
} else {
|
} else {
|
||||||
let media_type = doc.media_type();
|
let media_type = doc.media_type();
|
||||||
Some((specifier.clone(), media_type))
|
Some((doc.specifier().clone(), media_type))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -88,7 +88,7 @@ use crate::cache::DenoDir;
|
||||||
use crate::cache::FastInsecureHasher;
|
use crate::cache::FastInsecureHasher;
|
||||||
use crate::cache::GlobalHttpCache;
|
use crate::cache::GlobalHttpCache;
|
||||||
use crate::cache::HttpCache;
|
use crate::cache::HttpCache;
|
||||||
use crate::cache::LocalHttpCache;
|
use crate::cache::LocalLspHttpCache;
|
||||||
use crate::factory::CliFactory;
|
use crate::factory::CliFactory;
|
||||||
use crate::file_fetcher::FileFetcher;
|
use crate::file_fetcher::FileFetcher;
|
||||||
use crate::graph_util;
|
use crate::graph_util;
|
||||||
|
@ -204,7 +204,7 @@ pub struct Inner {
|
||||||
/// An abstraction that handles interactions with TypeScript.
|
/// An abstraction that handles interactions with TypeScript.
|
||||||
pub ts_server: Arc<TsServer>,
|
pub ts_server: Arc<TsServer>,
|
||||||
/// A map of specifiers and URLs used to translate over the LSP.
|
/// A map of specifiers and URLs used to translate over the LSP.
|
||||||
pub url_map: Arc<urls::LspUrlMap>,
|
pub url_map: urls::LspUrlMap,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LanguageServer {
|
impl LanguageServer {
|
||||||
|
@ -905,16 +905,18 @@ impl Inner {
|
||||||
self.module_registries_location = module_registries_location;
|
self.module_registries_location = module_registries_location;
|
||||||
// update the cache path
|
// update the cache path
|
||||||
let global_cache = Arc::new(GlobalHttpCache::new(dir.deps_folder_path()));
|
let global_cache = Arc::new(GlobalHttpCache::new(dir.deps_folder_path()));
|
||||||
let cache: Arc<dyn HttpCache> =
|
let maybe_local_cache =
|
||||||
match self.config.maybe_deno_modules_dir_path() {
|
self.config.maybe_deno_modules_dir_path().map(|local_path| {
|
||||||
Some(local_path) => {
|
Arc::new(LocalLspHttpCache::new(local_path, global_cache.clone()))
|
||||||
Arc::new(LocalHttpCache::new(local_path, global_cache))
|
});
|
||||||
}
|
let cache: Arc<dyn HttpCache> = maybe_local_cache
|
||||||
None => global_cache,
|
.clone()
|
||||||
};
|
.map(|c| c as Arc<dyn HttpCache>)
|
||||||
|
.unwrap_or(global_cache);
|
||||||
self.deps_http_cache = cache.clone();
|
self.deps_http_cache = cache.clone();
|
||||||
self.documents.set_cache(cache.clone());
|
self.documents.set_cache(cache.clone());
|
||||||
self.cache_metadata.set_cache(cache);
|
self.cache_metadata.set_cache(cache);
|
||||||
|
self.url_map.set_cache(maybe_local_cache);
|
||||||
self.maybe_global_cache_path = new_cache_path;
|
self.maybe_global_cache_path = new_cache_path;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -2946,6 +2948,7 @@ impl Inner {
|
||||||
snapshot: self.snapshot(),
|
snapshot: self.snapshot(),
|
||||||
config: self.config.snapshot(),
|
config: self.config.snapshot(),
|
||||||
lint_options: self.lint_options.clone(),
|
lint_options: self.lint_options.clone(),
|
||||||
|
url_map: self.url_map.clone(),
|
||||||
};
|
};
|
||||||
if let Err(err) = self.diagnostics_server.update(snapshot) {
|
if let Err(err) = self.diagnostics_server.update(snapshot) {
|
||||||
error!("Cannot update diagnostics: {}", err);
|
error!("Cannot update diagnostics: {}", err);
|
||||||
|
|
|
@ -3214,9 +3214,13 @@ fn op_script_names(state: &mut OpState) -> Vec<String> {
|
||||||
.filter_map(|dep| dep.get_type().or_else(|| dep.get_code())),
|
.filter_map(|dep| dep.get_type().or_else(|| dep.get_code())),
|
||||||
);
|
);
|
||||||
for specifier in specifiers {
|
for specifier in specifiers {
|
||||||
if seen.insert(specifier.as_str()) && documents.exists(specifier) {
|
if seen.insert(specifier.as_str()) {
|
||||||
// only include dependencies we know to exist otherwise typescript will error
|
if let Some(specifier) = documents.resolve_redirected(specifier) {
|
||||||
result.push(specifier.to_string());
|
// only include dependencies we know to exist otherwise typescript will error
|
||||||
|
if documents.exists(&specifier) {
|
||||||
|
result.push(specifier.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
use crate::cache::LocalLspHttpCache;
|
||||||
use crate::file_fetcher::map_content_type;
|
use crate::file_fetcher::map_content_type;
|
||||||
|
|
||||||
use data_url::DataUrl;
|
use data_url::DataUrl;
|
||||||
|
@ -12,6 +13,7 @@ use deno_core::url::Url;
|
||||||
use deno_core::ModuleSpecifier;
|
use deno_core::ModuleSpecifier;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// 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.
|
||||||
|
@ -119,17 +121,31 @@ pub enum LspUrlKind {
|
||||||
/// A bi-directional map of URLs sent to the LSP client and internal module
|
/// A bi-directional map of URLs sent to the LSP client and internal module
|
||||||
/// specifiers. We need to map internal specifiers into `deno:` schema URLs
|
/// specifiers. We need to map internal specifiers into `deno:` schema URLs
|
||||||
/// 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)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub struct LspUrlMap(Mutex<LspUrlMapInner>);
|
pub struct LspUrlMap {
|
||||||
|
local_http_cache: Option<Arc<LocalLspHttpCache>>,
|
||||||
|
inner: Arc<Mutex<LspUrlMapInner>>,
|
||||||
|
}
|
||||||
|
|
||||||
impl LspUrlMap {
|
impl LspUrlMap {
|
||||||
|
pub fn set_cache(&mut self, http_cache: Option<Arc<LocalLspHttpCache>>) {
|
||||||
|
self.local_http_cache = http_cache;
|
||||||
|
}
|
||||||
|
|
||||||
/// 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
|
||||||
/// URL that can be handled as a "virtual" document by an LSP client.
|
/// URL that can be handled as a "virtual" document by an LSP client.
|
||||||
pub fn normalize_specifier(
|
pub fn normalize_specifier(
|
||||||
&self,
|
&self,
|
||||||
specifier: &ModuleSpecifier,
|
specifier: &ModuleSpecifier,
|
||||||
) -> Result<LspClientUrl, AnyError> {
|
) -> Result<LspClientUrl, AnyError> {
|
||||||
let mut inner = self.0.lock();
|
if let Some(cache) = &self.local_http_cache {
|
||||||
|
if matches!(specifier.scheme(), "http" | "https") {
|
||||||
|
if let Some(file_url) = cache.get_file_url(specifier) {
|
||||||
|
return Ok(LspClientUrl(file_url));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut inner = self.inner.lock();
|
||||||
if let Some(url) = inner.get_url(specifier).cloned() {
|
if let Some(url) = inner.get_url(specifier).cloned() {
|
||||||
Ok(url)
|
Ok(url)
|
||||||
} else {
|
} else {
|
||||||
|
@ -183,7 +199,16 @@ 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 {
|
||||||
let mut inner = self.0.lock();
|
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) {
|
||||||
|
return remote_url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut inner = self.inner.lock();
|
||||||
if let Some(specifier) = inner.get_specifier(url).cloned() {
|
if let Some(specifier) = inner.get_specifier(url).cloned() {
|
||||||
specifier
|
specifier
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -573,7 +573,7 @@ fn lsp_import_map_config_file_auto_discovered_symlink() {
|
||||||
client.did_change_watched_files(json!({
|
client.did_change_watched_files(json!({
|
||||||
"changes": [{
|
"changes": [{
|
||||||
// the client will give a watched file changed event for the symlink's target
|
// the client will give a watched file changed event for the symlink's target
|
||||||
"uri": temp_dir.path().join("subdir/deno.json").canonicalize().uri(),
|
"uri": temp_dir.path().join("subdir/deno.json").canonicalize().uri_file(),
|
||||||
"type": 2
|
"type": 2
|
||||||
}]
|
}]
|
||||||
}));
|
}));
|
||||||
|
@ -601,7 +601,7 @@ fn lsp_import_map_config_file_auto_discovered_symlink() {
|
||||||
client.did_change_watched_files(json!({
|
client.did_change_watched_files(json!({
|
||||||
"changes": [{
|
"changes": [{
|
||||||
// now still say that the target path has changed
|
// now still say that the target path has changed
|
||||||
"uri": temp_dir.path().join("subdir/deno.json").canonicalize().uri(),
|
"uri": temp_dir.path().join("subdir/deno.json").canonicalize().uri_file(),
|
||||||
"type": 2
|
"type": 2
|
||||||
}]
|
}]
|
||||||
}));
|
}));
|
||||||
|
@ -8777,13 +8777,13 @@ fn lsp_deno_modules_dir() {
|
||||||
|
|
||||||
let mut client = context.new_lsp_command().build();
|
let mut client = context.new_lsp_command().build();
|
||||||
client.initialize_default();
|
client.initialize_default();
|
||||||
let file_uri = temp_dir.uri().join("file.ts").unwrap();
|
let local_file_uri = temp_dir.uri().join("file.ts").unwrap();
|
||||||
client.did_open(json!({
|
client.did_open(json!({
|
||||||
"textDocument": {
|
"textDocument": {
|
||||||
"uri": file_uri,
|
"uri": local_file_uri,
|
||||||
"languageId": "typescript",
|
"languageId": "typescript",
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"text": "import { returnsHi } from 'http://localhost:4545/subdir/mod1.ts'; console.log(returnsHi());",
|
"text": "import { returnsHi } from 'http://localhost:4545/subdir/mod1.ts';\nconst test: string = returnsHi();\nconsole.log(test);",
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
let cache = |client: &mut LspClient| {
|
let cache = |client: &mut LspClient| {
|
||||||
|
@ -8791,7 +8791,7 @@ fn lsp_deno_modules_dir() {
|
||||||
"deno/cache",
|
"deno/cache",
|
||||||
json!({
|
json!({
|
||||||
"referrer": {
|
"referrer": {
|
||||||
"uri": file_uri,
|
"uri": local_file_uri,
|
||||||
},
|
},
|
||||||
"uris": [
|
"uris": [
|
||||||
{
|
{
|
||||||
|
@ -8850,11 +8850,113 @@ fn lsp_deno_modules_dir() {
|
||||||
assert_eq!(diagnostics.all().len(), 0, "{:#?}", diagnostics); // cached
|
assert_eq!(diagnostics.all().len(), 0, "{:#?}", diagnostics); // cached
|
||||||
|
|
||||||
// no caching necessary because it was already cached. It should exist now
|
// no caching necessary because it was already cached. It should exist now
|
||||||
|
|
||||||
assert!(temp_dir
|
assert!(temp_dir
|
||||||
.path()
|
.path()
|
||||||
.join("deno_modules/http_localhost_4545/subdir/mod1.ts")
|
.join("deno_modules/http_localhost_4545/subdir/mod1.ts")
|
||||||
.exists());
|
.exists());
|
||||||
|
|
||||||
|
// the declaration should be found in the deno_modules directory
|
||||||
|
let res = client.write_request(
|
||||||
|
"textDocument/references",
|
||||||
|
json!({
|
||||||
|
"textDocument": {
|
||||||
|
"uri": local_file_uri,
|
||||||
|
},
|
||||||
|
"position": { "line": 0, "character": 9 }, // returnsHi
|
||||||
|
"context": {
|
||||||
|
"includeDeclaration": false
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// ensure that it's using the deno_modules directory
|
||||||
|
let references = res.as_array().unwrap();
|
||||||
|
assert_eq!(references.len(), 2, "references: {:#?}", references);
|
||||||
|
let uri = references[1]
|
||||||
|
.as_object()
|
||||||
|
.unwrap()
|
||||||
|
.get("uri")
|
||||||
|
.unwrap()
|
||||||
|
.as_str()
|
||||||
|
.unwrap();
|
||||||
|
let file_path = temp_dir
|
||||||
|
.path()
|
||||||
|
.join("deno_modules/http_localhost_4545/subdir/mod1.ts");
|
||||||
|
let remote_file_uri = file_path.uri_file();
|
||||||
|
assert_eq!(uri, remote_file_uri.as_str());
|
||||||
|
|
||||||
|
let file_text = file_path.read_to_string();
|
||||||
|
let diagnostics = client.did_open(json!({
|
||||||
|
"textDocument": {
|
||||||
|
"uri": remote_file_uri,
|
||||||
|
"languageId": "typescript",
|
||||||
|
"version": 1,
|
||||||
|
"text": file_text,
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
assert_eq!(diagnostics.all(), Vec::new());
|
||||||
|
|
||||||
|
client.write_notification(
|
||||||
|
"textDocument/didChange",
|
||||||
|
json!({
|
||||||
|
"textDocument": {
|
||||||
|
"uri": remote_file_uri,
|
||||||
|
"version": 2
|
||||||
|
},
|
||||||
|
"contentChanges": [
|
||||||
|
{
|
||||||
|
"range": {
|
||||||
|
"start": { "line": 0, "character": 0 },
|
||||||
|
"end": { "line": 17, "character": 0 },
|
||||||
|
},
|
||||||
|
"text": "export function returnsHi(): number { return new Date(); }"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
let diagnostics = client.read_diagnostics();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
json!(
|
||||||
|
diagnostics
|
||||||
|
.messages_with_file_and_source(remote_file_uri.as_str(), "deno-ts")
|
||||||
|
.diagnostics
|
||||||
|
),
|
||||||
|
json!([
|
||||||
|
{
|
||||||
|
"range": {
|
||||||
|
"start": { "line": 0, "character": 38 },
|
||||||
|
"end": { "line": 0, "character": 44 }
|
||||||
|
},
|
||||||
|
"severity": 1,
|
||||||
|
"code": 2322,
|
||||||
|
"source": "deno-ts",
|
||||||
|
"message": "Type 'Date' is not assignable to type 'number'."
|
||||||
|
}
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
json!(
|
||||||
|
diagnostics
|
||||||
|
.messages_with_file_and_source(local_file_uri.as_str(), "deno-ts")
|
||||||
|
.diagnostics
|
||||||
|
),
|
||||||
|
json!([
|
||||||
|
{
|
||||||
|
"range": {
|
||||||
|
"start": { "line": 1, "character": 6 },
|
||||||
|
"end": { "line": 1, "character": 10 }
|
||||||
|
},
|
||||||
|
"severity": 1,
|
||||||
|
"code": 2322,
|
||||||
|
"source": "deno-ts",
|
||||||
|
"message": "Type 'number' is not assignable to type 'string'."
|
||||||
|
}
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
assert_eq!(diagnostics.all().len(), 2);
|
||||||
|
|
||||||
client.shutdown();
|
client.shutdown();
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,10 +47,14 @@ impl PathRef {
|
||||||
PathRef(self.as_path().parent().unwrap().to_path_buf())
|
PathRef(self.as_path().parent().unwrap().to_path_buf())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn uri(&self) -> Url {
|
pub fn uri_dir(&self) -> Url {
|
||||||
Url::from_directory_path(self.as_path()).unwrap()
|
Url::from_directory_path(self.as_path()).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn uri_file(&self) -> Url {
|
||||||
|
Url::from_file_path(self.as_path()).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn as_path(&self) -> &Path {
|
pub fn as_path(&self) -> &Path {
|
||||||
self.0.as_path()
|
self.0.as_path()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue