diff options
-rw-r--r-- | cli/disk_cache.rs | 2 | ||||
-rw-r--r-- | cli/file_fetcher.rs | 44 | ||||
-rw-r--r-- | cli/http_cache.rs | 33 | ||||
-rw-r--r-- | cli/lsp/language_server.rs | 4 | ||||
-rw-r--r-- | cli/lsp/sources.rs | 469 | ||||
-rw-r--r-- | cli/lsp/tsc.rs | 3 |
6 files changed, 326 insertions, 229 deletions
diff --git a/cli/disk_cache.rs b/cli/disk_cache.rs index 2b530cae2..2f2cc9e4f 100644 --- a/cli/disk_cache.rs +++ b/cli/disk_cache.rs @@ -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, diff --git a/cli/file_fetcher.rs b/cli/file_fetcher.rs index 3e6622408..23ace672c 100644 --- a/cli/file_fetcher.rs +++ b/cli/file_fetcher.rs @@ -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()) diff --git a/cli/http_cache.rs b/cli/http_cache.rs index aa68420b4..e6a1e8ac7 100644 --- a/cli/http_cache.rs +++ b/cli/http_cache.rs @@ -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)); } } diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index 7f7b693f9..6594492a4 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -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 diff --git a/cli/lsp/sources.rs b/cli/lsp/sources.rs index 62316e4a2..307170d72 100644 --- a/cli/lsp/sources.rs +++ b/cli/lsp/sources.rs @@ -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,8 +220,27 @@ impl Inner { } } + fn calculate_script_version( + &mut self, + specifier: &ModuleSpecifier, + ) -> Option<String> { + let path = self.get_path(specifier)?; + let metadata = fs::metadata(path).ok()?; + if let Ok(modified) = metadata.modified() { + if let Ok(n) = modified.duration_since(SystemTime::UNIX_EPOCH) { + Some(format!("{}", n.as_millis())) + } else { + Some("1".to_string()) + } + } else { + Some("1".to_string()) + } + } + fn contains_key(&mut self, specifier: &ModuleSpecifier) -> bool { - if let Some(specifier) = self.resolve_specifier(specifier) { + if let Some(specifier) = + resolve_specifier(specifier, &mut self.redirects, &self.http_cache) + { if self.get_metadata(&specifier).is_some() { return true; } @@ -141,21 +248,19 @@ impl Inner { 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 specifier = + resolve_specifier(specifier, &mut self.redirects, &self.http_cache)?; let metadata = self.get_metadata(&specifier)?; - Some(metadata.source.encode_utf16().count()) + Some(metadata.length_utf16) } fn get_line_index( &mut self, specifier: &ModuleSpecifier, ) -> Option<LineIndex> { - let specifier = self.resolve_specifier(specifier)?; + let specifier = + resolve_specifier(specifier, &mut self.redirects, &self.http_cache)?; let metadata = self.get_metadata(&specifier)?; Some(metadata.line_index) } @@ -164,7 +269,9 @@ impl Inner { &mut self, specifier: &ModuleSpecifier, ) -> Option<analysis::ResolvedDependency> { - let metadata = self.get_metadata(specifier)?; + let specifier = + resolve_specifier(specifier, &mut self.redirects, &self.http_cache)?; + let metadata = self.get_metadata(&specifier)?; metadata.maybe_types } @@ -172,118 +279,62 @@ impl Inner { &mut self, specifier: &ModuleSpecifier, ) -> Option<MediaType> { - let specifier = self.resolve_specifier(specifier)?; + 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.get_script_version(specifier)? { + if metadata.version == self.calculate_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 version = self.calculate_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 - } - } + 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 { - None + 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> { - 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 - } + 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()); + let path = self.http_cache.get_cache_filename(&specifier.as_url())?; if path.is_file() { self.remotes.insert(specifier.clone(), path.clone()); Some(path) @@ -293,57 +344,35 @@ impl Inner { } } - 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( &mut self, specifier: &ModuleSpecifier, ) -> Option<String> { - let path = self.get_path(specifier)?; - let metadata = fs::metadata(path).ok()?; - if let Ok(modified) = metadata.modified() { - if let Ok(n) = modified.duration_since(SystemTime::UNIX_EPOCH) { - Some(format!("{}", n.as_millis())) - } else { - Some("1".to_string()) - } - } else { - Some("1".to_string()) - } + let specifier = + resolve_specifier(specifier, &mut self.redirects, &self.http_cache)?; + let metadata = self.get_metadata(&specifier)?; + Some(metadata.version) } - fn get_text(&mut self, specifier: &ModuleSpecifier) -> Option<String> { - let specifier = self.resolve_specifier(specifier)?; + 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"); @@ -484,11 +490,78 @@ mod tests { } #[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()); } } diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs index f3a83d544..ef57682f1 100644 --- a/cli/lsp/tsc.rs +++ b/cli/lsp/tsc.rs @@ -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))) |