diff options
Diffstat (limited to 'cli/lsp/sources.rs')
-rw-r--r-- | cli/lsp/sources.rs | 797 |
1 files changed, 0 insertions, 797 deletions
diff --git a/cli/lsp/sources.rs b/cli/lsp/sources.rs deleted file mode 100644 index 69de6d976..000000000 --- a/cli/lsp/sources.rs +++ /dev/null @@ -1,797 +0,0 @@ -// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. - -use super::analysis; -use super::document_source::DocumentSource; -use super::text::LineIndex; -use super::tsc; -use super::urls::INVALID_SPECIFIER; - -use crate::file_fetcher::get_source_from_bytes; -use crate::file_fetcher::map_content_type; -use crate::file_fetcher::SUPPORTED_SCHEMES; -use crate::http_cache; -use crate::http_cache::HttpCache; -use crate::text_encoding; - -use deno_ast::MediaType; -use deno_core::error::anyhow; -use deno_core::error::AnyError; -use deno_core::parking_lot::Mutex; -use deno_core::serde_json; -use deno_core::ModuleSpecifier; -use import_map::ImportMap; -use std::collections::HashMap; -use std::fs; -use std::path::Path; -use std::path::PathBuf; -use std::sync::Arc; -use std::time::SystemTime; -use tsc::NavigationTree; - -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)?; - if redirect_limit >= 0 && cache_filename.is_file() { - let headers = get_remote_headers(&cache_filename)?; - if let Some(location) = headers.get("location") { - let redirect = - deno_core::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.scheme(); - if !SUPPORTED_SCHEMES.contains(&scheme) { - return None; - } - - if scheme == "data" { - Some(specifier.clone()) - } else if scheme == "file" { - let path = specifier.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)] -struct Metadata { - dependencies: Option<HashMap<String, analysis::Dependency>>, - length_utf16: usize, - maybe_navigation_tree: Option<tsc::NavigationTree>, - maybe_types: Option<analysis::ResolvedDependency>, - maybe_warning: Option<String>, - media_type: MediaType, - source: DocumentSource, - specifier: ModuleSpecifier, - version: String, -} - -impl Default for Metadata { - fn default() -> Self { - Self { - dependencies: None, - length_utf16: 0, - maybe_navigation_tree: None, - maybe_types: None, - maybe_warning: None, - media_type: MediaType::default(), - source: DocumentSource::new( - &INVALID_SPECIFIER, - MediaType::default(), - Arc::new(String::default()), - LineIndex::default(), - ), - specifier: INVALID_SPECIFIER.clone(), - version: String::default(), - } - } -} - -impl Metadata { - fn new( - specifier: &ModuleSpecifier, - source: Arc<String>, - version: &str, - media_type: MediaType, - maybe_warning: Option<String>, - maybe_import_map: &Option<ImportMap>, - ) -> Self { - let line_index = LineIndex::new(&source); - let document_source = - DocumentSource::new(specifier, media_type, source, line_index); - let (dependencies, maybe_types) = - if let Some(Ok(parsed_module)) = document_source.module() { - let (deps, maybe_types) = analysis::analyze_dependencies( - specifier, - media_type, - parsed_module, - maybe_import_map, - ); - (Some(deps), maybe_types) - } else { - (None, None) - }; - - Self { - dependencies, - length_utf16: document_source - .text_info() - .text_str() - .encode_utf16() - .count(), - maybe_navigation_tree: None, - maybe_types, - maybe_warning, - media_type: media_type.to_owned(), - source: document_source, - specifier: specifier.clone(), - version: version.to_string(), - } - } - - fn refresh(&mut self, maybe_import_map: &Option<ImportMap>) { - let (dependencies, maybe_types) = - if let Some(Ok(parsed_module)) = self.source.module() { - let (deps, maybe_types) = analysis::analyze_dependencies( - &self.specifier, - self.media_type, - parsed_module, - maybe_import_map, - ); - (Some(deps), maybe_types) - } else { - (None, None) - }; - self.dependencies = dependencies; - self.maybe_types = maybe_types; - } -} - -#[derive(Debug, Clone, Default)] -struct Inner { - http_cache: HttpCache, - maybe_import_map: Option<ImportMap>, - metadata: HashMap<ModuleSpecifier, Metadata>, - redirects: HashMap<ModuleSpecifier, ModuleSpecifier>, - remotes: HashMap<ModuleSpecifier, PathBuf>, -} - -#[derive(Debug, Clone, Default)] -pub struct Sources(Arc<Mutex<Inner>>); - -impl Sources { - pub fn new(location: &Path) -> Self { - Self(Arc::new(Mutex::new(Inner::new(location)))) - } - - pub fn contains_key(&self, specifier: &ModuleSpecifier) -> bool { - self.0.lock().contains_key(specifier) - } - - pub fn get_line_index( - &self, - specifier: &ModuleSpecifier, - ) -> Option<LineIndex> { - self.0.lock().get_line_index(specifier) - } - - pub fn get_maybe_types( - &self, - specifier: &ModuleSpecifier, - ) -> Option<analysis::ResolvedDependency> { - self.0.lock().get_maybe_types(specifier) - } - - pub fn get_maybe_warning( - &self, - specifier: &ModuleSpecifier, - ) -> Option<String> { - self.0.lock().get_maybe_warning(specifier) - } - - pub fn get_media_type( - &self, - specifier: &ModuleSpecifier, - ) -> Option<MediaType> { - self.0.lock().get_media_type(specifier) - } - - pub fn get_navigation_tree( - &self, - specifier: &ModuleSpecifier, - ) -> Option<tsc::NavigationTree> { - self.0.lock().get_navigation_tree(specifier) - } - - pub fn get_script_version( - &self, - specifier: &ModuleSpecifier, - ) -> Option<String> { - self.0.lock().get_script_version(specifier) - } - - pub fn get_source(&self, specifier: &ModuleSpecifier) -> Option<Arc<String>> { - self.0.lock().get_source(specifier) - } - - pub fn len(&self) -> usize { - self.0.lock().metadata.len() - } - - pub fn resolve_import( - &self, - specifier: &str, - referrer: &ModuleSpecifier, - ) -> Option<(ModuleSpecifier, MediaType)> { - self.0.lock().resolve_import(specifier, referrer) - } - - pub fn specifiers(&self) -> Vec<ModuleSpecifier> { - self.0.lock().metadata.keys().cloned().collect() - } - - pub fn set_import_map(&self, maybe_import_map: Option<ImportMap>) { - self.0.lock().set_import_map(maybe_import_map) - } - - pub fn set_navigation_tree( - &self, - specifier: &ModuleSpecifier, - navigation_tree: tsc::NavigationTree, - ) -> Result<(), AnyError> { - self - .0 - .lock() - .set_navigation_tree(specifier, navigation_tree) - } -} - -impl Inner { - fn new(location: &Path) -> Self { - Self { - http_cache: HttpCache::new(location), - ..Default::default() - } - } - - 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) = - resolve_specifier(specifier, &mut self.redirects, &self.http_cache) - { - if self.get_metadata(&specifier).is_some() { - return true; - } - } - false - } - - 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.source.line_index().clone()) - } - - 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_maybe_warning( - &mut self, - specifier: &ModuleSpecifier, - ) -> Option<String> { - let metadata = self.get_metadata(specifier)?; - metadata.maybe_warning - } - - 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.scheme(); - let (source, media_type, maybe_types, maybe_warning) = 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, None) - } else { - let cache_filename = self.http_cache.get_cache_filename(specifier)?; - 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) - }); - let maybe_warning = headers.get("x-deno-warning").cloned(); - (source, media_type, maybe_types, maybe_warning) - }; - let mut metadata = Metadata::new( - specifier, - Arc::new(source), - &version, - media_type, - maybe_warning, - &self.maybe_import_map, - ); - if maybe_types.is_some() { - metadata.maybe_types = maybe_types; - } - self.metadata.insert(specifier.clone(), metadata.clone()); - Some(metadata) - } - - fn get_navigation_tree( - &mut self, - specifier: &ModuleSpecifier, - ) -> Option<tsc::NavigationTree> { - let specifier = - resolve_specifier(specifier, &mut self.redirects, &self.http_cache)?; - let metadata = self.get_metadata(&specifier)?; - metadata.maybe_navigation_tree - } - - fn get_path(&mut self, specifier: &ModuleSpecifier) -> Option<PathBuf> { - if specifier.scheme() == "file" { - specifier.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)?; - 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<Arc<String>> { - let specifier = - resolve_specifier(specifier, &mut self.redirects, &self.http_cache)?; - let metadata = self.get_metadata(&specifier)?; - Some(metadata.source.text_info().text()) - } - - fn resolution_result( - &mut self, - specifier: &ModuleSpecifier, - ) -> Option<(ModuleSpecifier, MediaType)> { - 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( - &mut self, - specifier: &str, - referrer: &ModuleSpecifier, - ) -> Option<(ModuleSpecifier, MediaType)> { - 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)?; - if let Some(type_dependency) = &dependency.maybe_type { - if let analysis::ResolvedDependency::Resolved(resolved_specifier) = - type_dependency - { - // even if we have a module in the maybe_types slot, it doesn't mean - // that it is the actual module we should be using based on headers, - // so we check here and update properly. - 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 - } - } else { - let code_dependency = &dependency.maybe_code.clone()?; - if let analysis::ResolvedDependency::Resolved(resolved_specifier) = - code_dependency - { - 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 set_import_map(&mut self, maybe_import_map: Option<ImportMap>) { - for (_, metadata) in self.metadata.iter_mut() { - metadata.refresh(&maybe_import_map); - } - self.maybe_import_map = maybe_import_map; - } - - fn set_maybe_type( - &mut self, - 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()); - } - } - } - } - - fn set_navigation_tree( - &mut self, - specifier: &ModuleSpecifier, - navigation_tree: NavigationTree, - ) -> Result<(), AnyError> { - let mut metadata = self - .metadata - .get_mut(specifier) - .ok_or_else(|| anyhow!("Specifier not found {}"))?; - metadata.maybe_navigation_tree = Some(navigation_tree); - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use deno_core::resolve_path; - use deno_core::resolve_url; - use deno_core::serde_json::json; - use tempfile::TempDir; - - fn setup() -> (Sources, PathBuf) { - let temp_dir = TempDir::new().expect("could not create temp dir"); - let location = temp_dir.path().join("deps"); - let sources = Sources::new(&location); - (sources, location) - } - - #[test] - fn test_sources_get_script_version() { - let (sources, _) = setup(); - let tests = test_util::testdata_path(); - let specifier = - resolve_path(&tests.join("001_hello.js").to_string_lossy()).unwrap(); - let actual = sources.get_script_version(&specifier); - assert!(actual.is_some()); - } - - #[test] - fn test_sources_get_text() { - let (sources, _) = setup(); - let tests = test_util::testdata_path(); - let specifier = - resolve_path(&tests.join("001_hello.js").to_string_lossy()).unwrap(); - let actual = sources.get_source(&specifier); - assert!(actual.is_some()); - let actual = actual.unwrap().to_string(); - assert_eq!(actual, "console.log(\"Hello World\");\n"); - } - - #[test] - fn test_resolve_dependency_types() { - let (sources, location) = setup(); - let cache = HttpCache::new(&location); - let specifier_dep = resolve_url("https://deno.land/x/mod.ts").unwrap(); - cache - .set( - &specifier_dep, - Default::default(), - b"export * from \"https://deno.land/x/lib.js\";", - ) - .unwrap(); - let specifier_code = 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, headers_code, b"export const a = 1;") - .unwrap(); - let specifier_type = resolve_url("https://deno.land/x/lib.d.ts").unwrap(); - cache - .set( - &specifier_type, - 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] - /// This is a regression test for https://github.com/denoland/deno/issues/10031 - fn test_resolve_dependency_import_types() { - let (sources, location) = setup(); - let cache = HttpCache::new(&location); - let specifier_dep = resolve_url("https://deno.land/x/mod.ts").unwrap(); - cache - .set( - &specifier_dep, - Default::default(), - b"import type { A } from \"https://deno.land/x/lib.js\";\nconst a: A = { a: \"a\" };", - ) - .unwrap(); - let specifier_code = 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, headers_code, b"export const a = 1;") - .unwrap(); - let specifier_type = resolve_url("https://deno.land/x/lib.d.ts").unwrap(); - cache - .set( - &specifier_type, - Default::default(), - b"export const a: number;\nexport interface A { a: number; }\n", - ) - .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_warning_header() { - let (sources, location) = setup(); - let cache = HttpCache::new(&location); - let specifier = resolve_url("https://deno.land/x/lib.js").unwrap(); - let mut headers = HashMap::new(); - headers.insert( - "x-deno-warning".to_string(), - "this is a warning".to_string(), - ); - cache - .set(&specifier, headers, b"export const a = 1;") - .unwrap(); - let actual = sources.get_maybe_warning(&specifier); - assert_eq!(actual, Some("this is a warning".to_string())); - } - - #[test] - fn test_resolve_dependency_evil_redirect() { - let (sources, location) = setup(); - let cache = HttpCache::new(&location); - let evil_specifier = 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, evil_headers, b"").unwrap(); - let remote_specifier = resolve_url("https://deno.land/x/mod.ts").unwrap(); - cache - .set( - &remote_specifier, - Default::default(), - b"export * from \"./evil.ts\";", - ) - .unwrap(); - let actual = sources.resolve_import("./evil.ts", &remote_specifier); - assert_eq!(actual, None); - } - - #[test] - fn test_resolve_with_import_map() { - let (sources, location) = setup(); - let import_map_json = json!({ - "imports": { - "mylib": "https://deno.land/x/myLib/index.js" - } - }); - let import_map = ImportMap::from_json( - "https://deno.land/x/", - &import_map_json.to_string(), - ) - .unwrap(); - sources.set_import_map(Some(import_map)); - let cache = HttpCache::new(&location); - let mylib_specifier = - resolve_url("https://deno.land/x/myLib/index.js").unwrap(); - let mut mylib_headers_map = HashMap::new(); - mylib_headers_map.insert( - "content-type".to_string(), - "application/javascript".to_string(), - ); - cache - .set( - &mylib_specifier, - mylib_headers_map, - b"export const a = \"a\";\n", - ) - .unwrap(); - let referrer = resolve_url("https://deno.land/x/mod.ts").unwrap(); - cache - .set( - &referrer, - Default::default(), - b"export { a } from \"mylib\";", - ) - .unwrap(); - let actual = sources.resolve_import("mylib", &referrer); - assert_eq!(actual, Some((mylib_specifier, MediaType::JavaScript))); - } - - #[test] - fn test_update_import_map() { - let (sources, location) = setup(); - let import_map_json = json!({ - "imports": { - "otherlib": "https://deno.land/x/otherlib/index.js" - } - }); - let import_map = ImportMap::from_json( - "https://deno.land/x/", - &import_map_json.to_string(), - ) - .unwrap(); - sources.set_import_map(Some(import_map)); - let cache = HttpCache::new(&location); - let mylib_specifier = - resolve_url("https://deno.land/x/myLib/index.js").unwrap(); - let mut mylib_headers_map = HashMap::new(); - mylib_headers_map.insert( - "content-type".to_string(), - "application/javascript".to_string(), - ); - cache - .set( - &mylib_specifier, - mylib_headers_map, - b"export const a = \"a\";\n", - ) - .unwrap(); - let referrer = resolve_url("https://deno.land/x/mod.ts").unwrap(); - cache - .set( - &referrer, - Default::default(), - b"export { a } from \"mylib\";", - ) - .unwrap(); - let actual = sources.resolve_import("mylib", &referrer); - assert_eq!(actual, None); - let import_map_json = json!({ - "imports": { - "otherlib": "https://deno.land/x/otherlib/index.js", - "mylib": "https://deno.land/x/myLib/index.js" - } - }); - let import_map = ImportMap::from_json( - "https://deno.land/x/", - &import_map_json.to_string(), - ) - .unwrap(); - sources.set_import_map(Some(import_map)); - let actual = sources.resolve_import("mylib", &referrer); - assert_eq!(actual, Some((mylib_specifier, MediaType::JavaScript))); - } - - #[test] - fn test_sources_resolve_specifier_non_supported_schema() { - let (sources, _) = setup(); - let specifier = - resolve_url("foo://a/b/c.ts").expect("could not create specifier"); - let sources = sources.0.lock(); - 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()); - } -} |