mirror of
https://github.com/denoland/deno
synced 2024-08-27 12:00:17 +00:00
fix(lsp): handle cached type dependencies properly (#9500)
This commit is contained in:
parent
5873adeb5e
commit
64a1da84fe
|
@ -67,7 +67,7 @@ impl DiskCache {
|
|||
out.push(path_seg);
|
||||
}
|
||||
}
|
||||
"http" | "https" | "data" => out = url_to_filename(url),
|
||||
"http" | "https" | "data" => out = url_to_filename(url)?,
|
||||
"file" => {
|
||||
let path = match url.to_file_path() {
|
||||
Ok(path) => path,
|
||||
|
|
|
@ -338,7 +338,12 @@ impl FileFetcher {
|
|||
bytes: Vec<u8>,
|
||||
headers: &HashMap<String, String>,
|
||||
) -> Result<File, AnyError> {
|
||||
let local = self.http_cache.get_cache_filename(specifier.as_url());
|
||||
let local = self
|
||||
.http_cache
|
||||
.get_cache_filename(specifier.as_url())
|
||||
.ok_or_else(|| {
|
||||
generic_error("Cannot convert specifier to cached filename.")
|
||||
})?;
|
||||
let maybe_content_type = headers.get("content-type").cloned();
|
||||
let (media_type, maybe_charset) =
|
||||
map_content_type(specifier, maybe_content_type);
|
||||
|
@ -416,7 +421,12 @@ impl FileFetcher {
|
|||
|
||||
let (source, media_type, content_type) =
|
||||
get_source_from_data_url(specifier)?;
|
||||
let local = self.http_cache.get_cache_filename(specifier.as_url());
|
||||
let local = self
|
||||
.http_cache
|
||||
.get_cache_filename(specifier.as_url())
|
||||
.ok_or_else(|| {
|
||||
generic_error("Cannot convert specifier to cached filename.")
|
||||
})?;
|
||||
let mut headers = HashMap::new();
|
||||
headers.insert("content-type".to_string(), content_type);
|
||||
self
|
||||
|
@ -995,7 +1005,8 @@ mod tests {
|
|||
|
||||
let cache_filename = file_fetcher
|
||||
.http_cache
|
||||
.get_cache_filename(specifier.as_url());
|
||||
.get_cache_filename(specifier.as_url())
|
||||
.unwrap();
|
||||
let mut metadata =
|
||||
crate::http_cache::Metadata::read(&cache_filename).unwrap();
|
||||
metadata.headers = HashMap::new();
|
||||
|
@ -1079,7 +1090,8 @@ mod tests {
|
|||
.unwrap();
|
||||
let cache_filename = file_fetcher_01
|
||||
.http_cache
|
||||
.get_cache_filename(specifier.as_url());
|
||||
.get_cache_filename(specifier.as_url())
|
||||
.unwrap();
|
||||
|
||||
let result = file_fetcher_01
|
||||
.fetch(&specifier, &Permissions::allow_all())
|
||||
|
@ -1128,14 +1140,16 @@ mod tests {
|
|||
.unwrap();
|
||||
let cached_filename = file_fetcher
|
||||
.http_cache
|
||||
.get_cache_filename(specifier.as_url());
|
||||
.get_cache_filename(specifier.as_url())
|
||||
.unwrap();
|
||||
let redirected_specifier = ModuleSpecifier::resolve_url(
|
||||
"http://localhost:4545/cli/tests/subdir/redirects/redirect1.js",
|
||||
)
|
||||
.unwrap();
|
||||
let redirected_cached_filename = file_fetcher
|
||||
.http_cache
|
||||
.get_cache_filename(redirected_specifier.as_url());
|
||||
.get_cache_filename(redirected_specifier.as_url())
|
||||
.unwrap();
|
||||
|
||||
let result = file_fetcher
|
||||
.fetch(&specifier, &Permissions::allow_all())
|
||||
|
@ -1179,21 +1193,24 @@ mod tests {
|
|||
.unwrap();
|
||||
let cached_filename = file_fetcher
|
||||
.http_cache
|
||||
.get_cache_filename(specifier.as_url());
|
||||
.get_cache_filename(specifier.as_url())
|
||||
.unwrap();
|
||||
let redirected_01_specifier = ModuleSpecifier::resolve_url(
|
||||
"http://localhost:4546/cli/tests/subdir/redirects/redirect1.js",
|
||||
)
|
||||
.unwrap();
|
||||
let redirected_01_cached_filename = file_fetcher
|
||||
.http_cache
|
||||
.get_cache_filename(redirected_01_specifier.as_url());
|
||||
.get_cache_filename(redirected_01_specifier.as_url())
|
||||
.unwrap();
|
||||
let redirected_02_specifier = ModuleSpecifier::resolve_url(
|
||||
"http://localhost:4545/cli/tests/subdir/redirects/redirect1.js",
|
||||
)
|
||||
.unwrap();
|
||||
let redirected_02_cached_filename = file_fetcher
|
||||
.http_cache
|
||||
.get_cache_filename(redirected_02_specifier.as_url());
|
||||
.get_cache_filename(redirected_02_specifier.as_url())
|
||||
.unwrap();
|
||||
|
||||
let result = file_fetcher
|
||||
.fetch(&specifier, &Permissions::allow_all())
|
||||
|
@ -1265,7 +1282,8 @@ mod tests {
|
|||
.unwrap();
|
||||
let redirected_cache_filename = file_fetcher_01
|
||||
.http_cache
|
||||
.get_cache_filename(redirected_specifier.as_url());
|
||||
.get_cache_filename(redirected_specifier.as_url())
|
||||
.unwrap();
|
||||
|
||||
let result = file_fetcher_01
|
||||
.fetch(&specifier, &Permissions::allow_all())
|
||||
|
@ -1340,14 +1358,16 @@ mod tests {
|
|||
.unwrap();
|
||||
let cached_filename = file_fetcher
|
||||
.http_cache
|
||||
.get_cache_filename(specifier.as_url());
|
||||
.get_cache_filename(specifier.as_url())
|
||||
.unwrap();
|
||||
let redirected_specifier = ModuleSpecifier::resolve_url(
|
||||
"http://localhost:4550/cli/tests/subdir/redirects/redirect1.js",
|
||||
)
|
||||
.unwrap();
|
||||
let redirected_cached_filename = file_fetcher
|
||||
.http_cache
|
||||
.get_cache_filename(redirected_specifier.as_url());
|
||||
.get_cache_filename(redirected_specifier.as_url())
|
||||
.unwrap();
|
||||
|
||||
let result = file_fetcher
|
||||
.fetch(&specifier, &Permissions::allow_all())
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
/// at hand.
|
||||
use crate::fs_util;
|
||||
use crate::http_util::HeadersMap;
|
||||
use deno_core::error::generic_error;
|
||||
use deno_core::error::AnyError;
|
||||
use deno_core::serde_json;
|
||||
use deno_core::url::Url;
|
||||
|
@ -23,7 +24,7 @@ pub const CACHE_PERM: u32 = 0o644;
|
|||
/// This method replaces port part with a special string token (because
|
||||
/// ":" cannot be used in filename on some platforms).
|
||||
/// Ex: $DENO_DIR/deps/https/deno.land/
|
||||
fn base_url_to_filename(url: &Url) -> PathBuf {
|
||||
fn base_url_to_filename(url: &Url) -> Option<PathBuf> {
|
||||
let mut out = PathBuf::new();
|
||||
|
||||
let scheme = url.scheme();
|
||||
|
@ -40,14 +41,12 @@ fn base_url_to_filename(url: &Url) -> PathBuf {
|
|||
}
|
||||
"data" => (),
|
||||
scheme => {
|
||||
unimplemented!(
|
||||
"Don't know how to create cache name for scheme: {}",
|
||||
scheme
|
||||
);
|
||||
error!("Don't know how to create cache name for scheme: {}", scheme);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
out
|
||||
Some(out)
|
||||
}
|
||||
|
||||
/// Turn provided `url` into a hashed filename.
|
||||
|
@ -57,8 +56,8 @@ fn base_url_to_filename(url: &Url) -> PathBuf {
|
|||
/// strings.
|
||||
///
|
||||
/// NOTE: this method is `pub` because it's used in integration_tests
|
||||
pub fn url_to_filename(url: &Url) -> PathBuf {
|
||||
let mut cache_filename = base_url_to_filename(url);
|
||||
pub fn url_to_filename(url: &Url) -> Option<PathBuf> {
|
||||
let mut cache_filename = base_url_to_filename(url)?;
|
||||
|
||||
let mut rest_str = url.path().to_string();
|
||||
if let Some(query) = url.query() {
|
||||
|
@ -70,7 +69,7 @@ pub fn url_to_filename(url: &Url) -> PathBuf {
|
|||
// in case of static resources doesn't make much sense
|
||||
let hashed_filename = crate::checksum::gen(&[rest_str.as_bytes()]);
|
||||
cache_filename.push(hashed_filename);
|
||||
cache_filename
|
||||
Some(cache_filename)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
|
@ -133,15 +132,18 @@ impl HttpCache {
|
|||
})
|
||||
}
|
||||
|
||||
pub(crate) fn get_cache_filename(&self, url: &Url) -> PathBuf {
|
||||
self.location.join(url_to_filename(url))
|
||||
pub(crate) fn get_cache_filename(&self, url: &Url) -> Option<PathBuf> {
|
||||
Some(self.location.join(url_to_filename(url)?))
|
||||
}
|
||||
|
||||
// TODO(bartlomieju): this method should check headers file
|
||||
// and validate against ETAG/Last-modified-as headers.
|
||||
// ETAG check is currently done in `cli/file_fetcher.rs`.
|
||||
pub fn get(&self, url: &Url) -> Result<(File, HeadersMap), AnyError> {
|
||||
let cache_filename = self.location.join(url_to_filename(url));
|
||||
let cache_filename = self.location.join(
|
||||
url_to_filename(url)
|
||||
.ok_or_else(|| generic_error("Can't convert url to filename."))?,
|
||||
);
|
||||
let metadata_filename = Metadata::filename(&cache_filename);
|
||||
let file = File::open(cache_filename)?;
|
||||
let metadata = fs::read_to_string(metadata_filename)?;
|
||||
|
@ -155,7 +157,10 @@ impl HttpCache {
|
|||
headers_map: HeadersMap,
|
||||
content: &[u8],
|
||||
) -> Result<(), AnyError> {
|
||||
let cache_filename = self.location.join(url_to_filename(url));
|
||||
let cache_filename = self.location.join(
|
||||
url_to_filename(url)
|
||||
.ok_or_else(|| generic_error("Can't convert url to filename."))?,
|
||||
);
|
||||
// Create parent directory
|
||||
let parent_filename = cache_filename
|
||||
.parent()
|
||||
|
@ -266,7 +271,7 @@ mod tests {
|
|||
|
||||
for (url, expected) in test_cases.iter() {
|
||||
let u = Url::parse(url).unwrap();
|
||||
let p = url_to_filename(&u);
|
||||
let p = url_to_filename(&u).unwrap();
|
||||
assert_eq!(p, PathBuf::from(expected));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1946,8 +1946,8 @@ impl Inner {
|
|||
}
|
||||
}
|
||||
_ => {
|
||||
if let Some(text) = self.sources.get_text(&specifier) {
|
||||
Some(text)
|
||||
if let Some(source) = self.sources.get_source(&specifier) {
|
||||
Some(source)
|
||||
} else {
|
||||
error!("The cached sources was not found: {}", specifier);
|
||||
None
|
||||
|
|
|
@ -40,9 +40,68 @@ pub async fn cache(
|
|||
builder.add(specifier, false).await
|
||||
}
|
||||
|
||||
fn get_remote_headers(
|
||||
cache_filename: &Path,
|
||||
) -> Option<HashMap<String, String>> {
|
||||
let metadata_path = http_cache::Metadata::filename(cache_filename);
|
||||
let metadata_str = fs::read_to_string(metadata_path).ok()?;
|
||||
let metadata: http_cache::Metadata =
|
||||
serde_json::from_str(&metadata_str).ok()?;
|
||||
Some(metadata.headers)
|
||||
}
|
||||
|
||||
fn resolve_remote_specifier(
|
||||
specifier: &ModuleSpecifier,
|
||||
http_cache: &HttpCache,
|
||||
redirect_limit: isize,
|
||||
) -> Option<ModuleSpecifier> {
|
||||
let cache_filename = http_cache.get_cache_filename(specifier.as_url())?;
|
||||
if redirect_limit >= 0 && cache_filename.is_file() {
|
||||
let headers = get_remote_headers(&cache_filename)?;
|
||||
if let Some(location) = headers.get("location") {
|
||||
let redirect =
|
||||
ModuleSpecifier::resolve_import(location, specifier.as_str()).ok()?;
|
||||
resolve_remote_specifier(&redirect, http_cache, redirect_limit - 1)
|
||||
} else {
|
||||
Some(specifier.clone())
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_specifier(
|
||||
specifier: &ModuleSpecifier,
|
||||
redirects: &mut HashMap<ModuleSpecifier, ModuleSpecifier>,
|
||||
http_cache: &HttpCache,
|
||||
) -> Option<ModuleSpecifier> {
|
||||
let scheme = specifier.as_url().scheme();
|
||||
if !SUPPORTED_SCHEMES.contains(&scheme) {
|
||||
return None;
|
||||
}
|
||||
|
||||
if scheme == "data" {
|
||||
Some(specifier.clone())
|
||||
} else if scheme == "file" {
|
||||
let path = specifier.as_url().to_file_path().ok()?;
|
||||
if path.is_file() {
|
||||
Some(specifier.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else if let Some(specifier) = redirects.get(specifier) {
|
||||
Some(specifier.clone())
|
||||
} else {
|
||||
let redirect = resolve_remote_specifier(specifier, http_cache, 10)?;
|
||||
redirects.insert(specifier.clone(), redirect.clone());
|
||||
Some(redirect)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct Metadata {
|
||||
dependencies: Option<HashMap<String, analysis::Dependency>>,
|
||||
length_utf16: usize,
|
||||
line_index: LineIndex,
|
||||
maybe_types: Option<analysis::ResolvedDependency>,
|
||||
media_type: MediaType,
|
||||
|
@ -50,6 +109,39 @@ struct Metadata {
|
|||
version: String,
|
||||
}
|
||||
|
||||
impl Metadata {
|
||||
fn new(
|
||||
specifier: &ModuleSpecifier,
|
||||
source: &str,
|
||||
version: &str,
|
||||
media_type: &MediaType,
|
||||
maybe_import_map: &Option<ImportMap>,
|
||||
) -> Self {
|
||||
let (dependencies, maybe_types) = if let Some((dependencies, maybe_types)) =
|
||||
analysis::analyze_dependencies(
|
||||
specifier,
|
||||
source,
|
||||
media_type,
|
||||
maybe_import_map,
|
||||
) {
|
||||
(Some(dependencies), maybe_types)
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
let line_index = LineIndex::new(source);
|
||||
|
||||
Self {
|
||||
dependencies,
|
||||
length_utf16: source.encode_utf16().count(),
|
||||
line_index,
|
||||
maybe_types,
|
||||
media_type: media_type.to_owned(),
|
||||
source: source.to_string(),
|
||||
version: version.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct Inner {
|
||||
http_cache: HttpCache,
|
||||
|
@ -71,6 +163,10 @@ impl Sources {
|
|||
self.0.lock().unwrap().contains_key(specifier)
|
||||
}
|
||||
|
||||
/// Provides the length of the source content, calculated in a way that should
|
||||
/// match the behavior of JavaScript, where strings are stored effectively as
|
||||
/// `&[u16]` and when counting "chars" we need to represent the string as a
|
||||
/// UTF-16 string in Rust.
|
||||
pub fn get_length_utf16(&self, specifier: &ModuleSpecifier) -> Option<usize> {
|
||||
self.0.lock().unwrap().get_length_utf16(specifier)
|
||||
}
|
||||
|
@ -103,8 +199,8 @@ impl Sources {
|
|||
self.0.lock().unwrap().get_script_version(specifier)
|
||||
}
|
||||
|
||||
pub fn get_text(&self, specifier: &ModuleSpecifier) -> Option<String> {
|
||||
self.0.lock().unwrap().get_text(specifier)
|
||||
pub fn get_source(&self, specifier: &ModuleSpecifier) -> Option<String> {
|
||||
self.0.lock().unwrap().get_source(specifier)
|
||||
}
|
||||
|
||||
pub fn resolve_import(
|
||||
|
@ -114,14 +210,6 @@ impl Sources {
|
|||
) -> Option<(ModuleSpecifier, MediaType)> {
|
||||
self.0.lock().unwrap().resolve_import(specifier, referrer)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn resolve_specifier(
|
||||
&self,
|
||||
specifier: &ModuleSpecifier,
|
||||
) -> Option<ModuleSpecifier> {
|
||||
self.0.lock().unwrap().resolve_specifier(specifier)
|
||||
}
|
||||
}
|
||||
|
||||
impl Inner {
|
||||
|
@ -132,184 +220,7 @@ impl Inner {
|
|||
}
|
||||
}
|
||||
|
||||
fn contains_key(&mut self, specifier: &ModuleSpecifier) -> bool {
|
||||
if let Some(specifier) = self.resolve_specifier(specifier) {
|
||||
if self.get_metadata(&specifier).is_some() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Provides the length of the source content, calculated in a way that should
|
||||
/// match the behavior of JavaScript, where strings are stored effectively as
|
||||
/// `&[u16]` and when counting "chars" we need to represent the string as a
|
||||
/// UTF-16 string in Rust.
|
||||
fn get_length_utf16(&mut self, specifier: &ModuleSpecifier) -> Option<usize> {
|
||||
let specifier = self.resolve_specifier(specifier)?;
|
||||
let metadata = self.get_metadata(&specifier)?;
|
||||
Some(metadata.source.encode_utf16().count())
|
||||
}
|
||||
|
||||
fn get_line_index(
|
||||
&mut self,
|
||||
specifier: &ModuleSpecifier,
|
||||
) -> Option<LineIndex> {
|
||||
let specifier = self.resolve_specifier(specifier)?;
|
||||
let metadata = self.get_metadata(&specifier)?;
|
||||
Some(metadata.line_index)
|
||||
}
|
||||
|
||||
fn get_maybe_types(
|
||||
&mut self,
|
||||
specifier: &ModuleSpecifier,
|
||||
) -> Option<analysis::ResolvedDependency> {
|
||||
let metadata = self.get_metadata(specifier)?;
|
||||
metadata.maybe_types
|
||||
}
|
||||
|
||||
fn get_media_type(
|
||||
&mut self,
|
||||
specifier: &ModuleSpecifier,
|
||||
) -> Option<MediaType> {
|
||||
let specifier = self.resolve_specifier(specifier)?;
|
||||
let metadata = self.get_metadata(&specifier)?;
|
||||
Some(metadata.media_type)
|
||||
}
|
||||
|
||||
fn get_metadata(&mut self, specifier: &ModuleSpecifier) -> Option<Metadata> {
|
||||
if let Some(metadata) = self.metadata.get(specifier).cloned() {
|
||||
if metadata.version == self.get_script_version(specifier)? {
|
||||
return Some(metadata);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(@kitsonk) this needs to be refactored, lots of duplicate logic and
|
||||
// is really difficult to follow.
|
||||
let version = self.get_script_version(specifier)?;
|
||||
let path = self.get_path(specifier)?;
|
||||
if let Ok(bytes) = fs::read(path) {
|
||||
if specifier.as_url().scheme() == "file" {
|
||||
let charset = text_encoding::detect_charset(&bytes).to_string();
|
||||
if let Ok(source) = get_source_from_bytes(bytes, Some(charset)) {
|
||||
let media_type = MediaType::from(specifier);
|
||||
let mut maybe_types = None;
|
||||
let maybe_import_map = self.maybe_import_map.clone();
|
||||
let dependencies = if let Some((dependencies, mt)) =
|
||||
analysis::analyze_dependencies(
|
||||
&specifier,
|
||||
&source,
|
||||
&media_type,
|
||||
&maybe_import_map,
|
||||
) {
|
||||
maybe_types = mt;
|
||||
Some(dependencies)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let line_index = LineIndex::new(&source);
|
||||
let metadata = Metadata {
|
||||
dependencies,
|
||||
line_index,
|
||||
maybe_types,
|
||||
media_type,
|
||||
source,
|
||||
version,
|
||||
};
|
||||
self.metadata.insert(specifier.clone(), metadata.clone());
|
||||
Some(metadata)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
let headers = self.get_remote_headers(specifier)?;
|
||||
let maybe_content_type = headers.get("content-type").cloned();
|
||||
let (media_type, maybe_charset) =
|
||||
map_content_type(specifier, maybe_content_type);
|
||||
if let Ok(source) = get_source_from_bytes(bytes, maybe_charset) {
|
||||
let mut maybe_types =
|
||||
if let Some(types) = headers.get("x-typescript-types") {
|
||||
Some(analysis::resolve_import(
|
||||
types,
|
||||
&specifier,
|
||||
&self.maybe_import_map,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let maybe_import_map = self.maybe_import_map.clone();
|
||||
let dependencies = if let Some((dependencies, mt)) =
|
||||
analysis::analyze_dependencies(
|
||||
&specifier,
|
||||
&source,
|
||||
&media_type,
|
||||
&maybe_import_map,
|
||||
) {
|
||||
if maybe_types.is_none() {
|
||||
maybe_types = mt;
|
||||
}
|
||||
Some(dependencies)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let line_index = LineIndex::new(&source);
|
||||
let metadata = Metadata {
|
||||
dependencies,
|
||||
line_index,
|
||||
maybe_types,
|
||||
media_type,
|
||||
source,
|
||||
version,
|
||||
};
|
||||
self.metadata.insert(specifier.clone(), metadata.clone());
|
||||
Some(metadata)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn get_path(&mut self, specifier: &ModuleSpecifier) -> Option<PathBuf> {
|
||||
let specifier = self.resolve_specifier(specifier)?;
|
||||
if specifier.as_url().scheme() == "file" {
|
||||
if let Ok(path) = specifier.as_url().to_file_path() {
|
||||
Some(path)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else if let Some(path) = self.remotes.get(&specifier) {
|
||||
Some(path.clone())
|
||||
} else {
|
||||
let path = self.http_cache.get_cache_filename(&specifier.as_url());
|
||||
if path.is_file() {
|
||||
self.remotes.insert(specifier.clone(), path.clone());
|
||||
Some(path)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_remote_headers(
|
||||
&self,
|
||||
specifier: &ModuleSpecifier,
|
||||
) -> Option<HashMap<String, String>> {
|
||||
let cache_filename = self.http_cache.get_cache_filename(specifier.as_url());
|
||||
let metadata_path = http_cache::Metadata::filename(&cache_filename);
|
||||
if let Ok(metadata) = fs::read_to_string(metadata_path) {
|
||||
if let Ok(metadata) =
|
||||
serde_json::from_str::<'_, http_cache::Metadata>(&metadata)
|
||||
{
|
||||
return Some(metadata.headers);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn get_script_version(
|
||||
fn calculate_script_version(
|
||||
&mut self,
|
||||
specifier: &ModuleSpecifier,
|
||||
) -> Option<String> {
|
||||
|
@ -326,24 +237,142 @@ impl Inner {
|
|||
}
|
||||
}
|
||||
|
||||
fn get_text(&mut self, specifier: &ModuleSpecifier) -> Option<String> {
|
||||
let specifier = self.resolve_specifier(specifier)?;
|
||||
fn contains_key(&mut self, specifier: &ModuleSpecifier) -> bool {
|
||||
if let Some(specifier) =
|
||||
resolve_specifier(specifier, &mut self.redirects, &self.http_cache)
|
||||
{
|
||||
if self.get_metadata(&specifier).is_some() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn get_length_utf16(&mut self, specifier: &ModuleSpecifier) -> Option<usize> {
|
||||
let specifier =
|
||||
resolve_specifier(specifier, &mut self.redirects, &self.http_cache)?;
|
||||
let metadata = self.get_metadata(&specifier)?;
|
||||
Some(metadata.length_utf16)
|
||||
}
|
||||
|
||||
fn get_line_index(
|
||||
&mut self,
|
||||
specifier: &ModuleSpecifier,
|
||||
) -> Option<LineIndex> {
|
||||
let specifier =
|
||||
resolve_specifier(specifier, &mut self.redirects, &self.http_cache)?;
|
||||
let metadata = self.get_metadata(&specifier)?;
|
||||
Some(metadata.line_index)
|
||||
}
|
||||
|
||||
fn get_maybe_types(
|
||||
&mut self,
|
||||
specifier: &ModuleSpecifier,
|
||||
) -> Option<analysis::ResolvedDependency> {
|
||||
let specifier =
|
||||
resolve_specifier(specifier, &mut self.redirects, &self.http_cache)?;
|
||||
let metadata = self.get_metadata(&specifier)?;
|
||||
metadata.maybe_types
|
||||
}
|
||||
|
||||
fn get_media_type(
|
||||
&mut self,
|
||||
specifier: &ModuleSpecifier,
|
||||
) -> Option<MediaType> {
|
||||
let specifier =
|
||||
resolve_specifier(specifier, &mut self.redirects, &self.http_cache)?;
|
||||
let metadata = self.get_metadata(&specifier)?;
|
||||
Some(metadata.media_type)
|
||||
}
|
||||
|
||||
fn get_metadata(&mut self, specifier: &ModuleSpecifier) -> Option<Metadata> {
|
||||
if let Some(metadata) = self.metadata.get(specifier).cloned() {
|
||||
if metadata.version == self.calculate_script_version(specifier)? {
|
||||
return Some(metadata);
|
||||
}
|
||||
}
|
||||
|
||||
let version = self.calculate_script_version(specifier)?;
|
||||
let path = self.get_path(specifier)?;
|
||||
let bytes = fs::read(path).ok()?;
|
||||
let scheme = specifier.as_url().scheme();
|
||||
let (source, media_type, maybe_types) = if scheme == "file" {
|
||||
let maybe_charset =
|
||||
Some(text_encoding::detect_charset(&bytes).to_string());
|
||||
let source = get_source_from_bytes(bytes, maybe_charset).ok()?;
|
||||
(source, MediaType::from(specifier), None)
|
||||
} else {
|
||||
let cache_filename =
|
||||
self.http_cache.get_cache_filename(specifier.as_url())?;
|
||||
let headers = get_remote_headers(&cache_filename)?;
|
||||
let maybe_content_type = headers.get("content-type").cloned();
|
||||
let (media_type, maybe_charset) =
|
||||
map_content_type(specifier, maybe_content_type);
|
||||
let source = get_source_from_bytes(bytes, maybe_charset).ok()?;
|
||||
let maybe_types = headers.get("x-typescript-types").map(|s| {
|
||||
analysis::resolve_import(s, &specifier, &self.maybe_import_map)
|
||||
});
|
||||
(source, media_type, maybe_types)
|
||||
};
|
||||
let mut metadata = Metadata::new(
|
||||
specifier,
|
||||
&source,
|
||||
&version,
|
||||
&media_type,
|
||||
&self.maybe_import_map,
|
||||
);
|
||||
if metadata.maybe_types.is_none() {
|
||||
metadata.maybe_types = maybe_types;
|
||||
}
|
||||
self.metadata.insert(specifier.clone(), metadata.clone());
|
||||
Some(metadata)
|
||||
}
|
||||
|
||||
fn get_path(&mut self, specifier: &ModuleSpecifier) -> Option<PathBuf> {
|
||||
if specifier.as_url().scheme() == "file" {
|
||||
specifier.as_url().to_file_path().ok()
|
||||
} else if let Some(path) = self.remotes.get(&specifier) {
|
||||
Some(path.clone())
|
||||
} else {
|
||||
let path = self.http_cache.get_cache_filename(&specifier.as_url())?;
|
||||
if path.is_file() {
|
||||
self.remotes.insert(specifier.clone(), path.clone());
|
||||
Some(path)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_script_version(
|
||||
&mut self,
|
||||
specifier: &ModuleSpecifier,
|
||||
) -> Option<String> {
|
||||
let specifier =
|
||||
resolve_specifier(specifier, &mut self.redirects, &self.http_cache)?;
|
||||
let metadata = self.get_metadata(&specifier)?;
|
||||
Some(metadata.version)
|
||||
}
|
||||
|
||||
fn get_source(&mut self, specifier: &ModuleSpecifier) -> Option<String> {
|
||||
let specifier =
|
||||
resolve_specifier(specifier, &mut self.redirects, &self.http_cache)?;
|
||||
let metadata = self.get_metadata(&specifier)?;
|
||||
Some(metadata.source)
|
||||
}
|
||||
|
||||
fn resolution_result(
|
||||
&mut self,
|
||||
resolved_specifier: &ModuleSpecifier,
|
||||
specifier: &ModuleSpecifier,
|
||||
) -> Option<(ModuleSpecifier, MediaType)> {
|
||||
let resolved_specifier = self.resolve_specifier(resolved_specifier)?;
|
||||
let media_type =
|
||||
if let Some(metadata) = self.metadata.get(&resolved_specifier) {
|
||||
metadata.media_type
|
||||
} else {
|
||||
MediaType::from(&resolved_specifier)
|
||||
};
|
||||
Some((resolved_specifier, media_type))
|
||||
let specifier =
|
||||
resolve_specifier(specifier, &mut self.redirects, &self.http_cache)?;
|
||||
let media_type = if let Some(metadata) = self.metadata.get(&specifier) {
|
||||
metadata.media_type
|
||||
} else {
|
||||
MediaType::from(&specifier)
|
||||
};
|
||||
Some((specifier, media_type))
|
||||
}
|
||||
|
||||
fn resolve_import(
|
||||
|
@ -351,7 +380,8 @@ impl Inner {
|
|||
specifier: &str,
|
||||
referrer: &ModuleSpecifier,
|
||||
) -> Option<(ModuleSpecifier, MediaType)> {
|
||||
let referrer = self.resolve_specifier(referrer)?;
|
||||
let referrer =
|
||||
resolve_specifier(referrer, &mut self.redirects, &self.http_cache)?;
|
||||
let metadata = self.get_metadata(&referrer)?;
|
||||
let dependencies = &metadata.dependencies?;
|
||||
let dependency = dependencies.get(specifier)?;
|
||||
|
@ -368,62 +398,38 @@ impl Inner {
|
|||
if let analysis::ResolvedDependency::Resolved(resolved_specifier) =
|
||||
code_dependency
|
||||
{
|
||||
self.resolution_result(resolved_specifier)
|
||||
if let Some(type_dependency) = self.get_maybe_types(resolved_specifier)
|
||||
{
|
||||
self.set_maybe_type(specifier, &referrer, &type_dependency);
|
||||
if let analysis::ResolvedDependency::Resolved(type_specifier) =
|
||||
type_dependency
|
||||
{
|
||||
self.resolution_result(&type_specifier)
|
||||
} else {
|
||||
self.resolution_result(resolved_specifier)
|
||||
}
|
||||
} else {
|
||||
self.resolution_result(resolved_specifier)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_specifier(
|
||||
fn set_maybe_type(
|
||||
&mut self,
|
||||
specifier: &ModuleSpecifier,
|
||||
) -> Option<ModuleSpecifier> {
|
||||
let scheme = specifier.as_url().scheme();
|
||||
if !SUPPORTED_SCHEMES.contains(&scheme) {
|
||||
return None;
|
||||
}
|
||||
|
||||
if scheme == "file" {
|
||||
if let Ok(path) = specifier.as_url().to_file_path() {
|
||||
if path.is_file() {
|
||||
return Some(specifier.clone());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if let Some(specifier) = self.redirects.get(specifier) {
|
||||
return Some(specifier.clone());
|
||||
}
|
||||
if let Some(redirect) = self.resolve_remote_specifier(specifier, 10) {
|
||||
self.redirects.insert(specifier.clone(), redirect.clone());
|
||||
return Some(redirect);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn resolve_remote_specifier(
|
||||
&self,
|
||||
specifier: &ModuleSpecifier,
|
||||
redirect_limit: isize,
|
||||
) -> Option<ModuleSpecifier> {
|
||||
let cached_filename =
|
||||
self.http_cache.get_cache_filename(specifier.as_url());
|
||||
if redirect_limit >= 0 && cached_filename.is_file() {
|
||||
if let Some(headers) = self.get_remote_headers(specifier) {
|
||||
if let Some(redirect_to) = headers.get("location") {
|
||||
if let Ok(redirect) =
|
||||
ModuleSpecifier::resolve_import(redirect_to, specifier.as_str())
|
||||
{
|
||||
return self
|
||||
.resolve_remote_specifier(&redirect, redirect_limit - 1);
|
||||
}
|
||||
} else {
|
||||
return Some(specifier.clone());
|
||||
specifier: &str,
|
||||
referrer: &ModuleSpecifier,
|
||||
dependency: &analysis::ResolvedDependency,
|
||||
) {
|
||||
if let Some(metadata) = self.metadata.get_mut(referrer) {
|
||||
if let Some(dependencies) = &mut metadata.dependencies {
|
||||
if let Some(dep) = dependencies.get_mut(specifier) {
|
||||
dep.maybe_type = Some(dependency.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -462,7 +468,7 @@ mod tests {
|
|||
&tests.join("001_hello.js").to_string_lossy(),
|
||||
)
|
||||
.unwrap();
|
||||
let actual = sources.get_text(&specifier);
|
||||
let actual = sources.get_source(&specifier);
|
||||
assert!(actual.is_some());
|
||||
let actual = actual.unwrap();
|
||||
assert_eq!(actual, "console.log(\"Hello World\");\n");
|
||||
|
@ -483,12 +489,79 @@ mod tests {
|
|||
assert_eq!(actual, 28);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_dependency_types() {
|
||||
let (sources, location) = setup();
|
||||
let cache = HttpCache::new(&location);
|
||||
let specifier_dep =
|
||||
ModuleSpecifier::resolve_url("https://deno.land/x/mod.ts").unwrap();
|
||||
cache
|
||||
.set(
|
||||
specifier_dep.as_url(),
|
||||
Default::default(),
|
||||
b"export * from \"https://deno.land/x/lib.js\";",
|
||||
)
|
||||
.unwrap();
|
||||
let specifier_code =
|
||||
ModuleSpecifier::resolve_url("https://deno.land/x/lib.js").unwrap();
|
||||
let mut headers_code = HashMap::new();
|
||||
headers_code
|
||||
.insert("x-typescript-types".to_string(), "./lib.d.ts".to_string());
|
||||
cache
|
||||
.set(
|
||||
specifier_code.as_url(),
|
||||
headers_code,
|
||||
b"export const a = 1;",
|
||||
)
|
||||
.unwrap();
|
||||
let specifier_type =
|
||||
ModuleSpecifier::resolve_url("https://deno.land/x/lib.d.ts").unwrap();
|
||||
cache
|
||||
.set(
|
||||
specifier_type.as_url(),
|
||||
Default::default(),
|
||||
b"export const a: number;",
|
||||
)
|
||||
.unwrap();
|
||||
let actual =
|
||||
sources.resolve_import("https://deno.land/x/lib.js", &specifier_dep);
|
||||
assert_eq!(actual, Some((specifier_type, MediaType::Dts)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_dependency_evil_redirect() {
|
||||
let (sources, location) = setup();
|
||||
let cache = HttpCache::new(&location);
|
||||
let evil_specifier =
|
||||
ModuleSpecifier::resolve_url("https://deno.land/x/evil.ts").unwrap();
|
||||
let mut evil_headers = HashMap::new();
|
||||
evil_headers
|
||||
.insert("location".to_string(), "file:///etc/passwd".to_string());
|
||||
cache
|
||||
.set(evil_specifier.as_url(), evil_headers, b"")
|
||||
.unwrap();
|
||||
let remote_specifier =
|
||||
ModuleSpecifier::resolve_url("https://deno.land/x/mod.ts").unwrap();
|
||||
cache
|
||||
.set(
|
||||
remote_specifier.as_url(),
|
||||
Default::default(),
|
||||
b"export * from \"./evil.ts\";",
|
||||
)
|
||||
.unwrap();
|
||||
let actual = sources.resolve_import("./evil.ts", &remote_specifier);
|
||||
assert_eq!(actual, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sources_resolve_specifier_non_supported_schema() {
|
||||
let (sources, _) = setup();
|
||||
let specifier = ModuleSpecifier::resolve_url("foo://a/b/c.ts")
|
||||
.expect("could not create specifier");
|
||||
let actual = sources.resolve_specifier(&specifier);
|
||||
let sources = sources.0.lock().unwrap();
|
||||
let mut redirects = sources.redirects.clone();
|
||||
let http_cache = sources.http_cache.clone();
|
||||
let actual = resolve_specifier(&specifier, &mut redirects, &http_cache);
|
||||
assert!(actual.is_none());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1117,8 +1117,7 @@ fn get_text(state: &mut State, args: Value) -> Result<Value, AnyError> {
|
|||
.unwrap()
|
||||
.clone()
|
||||
} else {
|
||||
let sources = &mut state.state_snapshot.sources;
|
||||
sources.get_text(&specifier).unwrap()
|
||||
state.state_snapshot.sources.get_source(&specifier).unwrap()
|
||||
};
|
||||
state.state_snapshot.performance.measure(mark);
|
||||
Ok(json!(text::slice(&content, v.start..v.end)))
|
||||
|
|
Loading…
Reference in a new issue