diff --git a/cli/lsp/README.md b/cli/lsp/README.md index c43590f60f..a32deb5856 100644 --- a/cli/lsp/README.md +++ b/cli/lsp/README.md @@ -22,13 +22,38 @@ implement these in order to have a fully functioning client that integrates well with Deno: - `deno/cache` - This command will instruct Deno to attempt to cache a module - and all of its dependencies. It expects an argument of - `{ textDocument: TextDocumentIdentifier }` to be passed. + and all of its dependencies. If a `referrer` only is passed, then all + dependencies for the module specifier will be loaded. If there are values in + the `uris`, then only those `uris` will be cached. + + It expects parameters of: + + ```ts + interface CacheParams { + referrer: TextDocumentIdentifier; + uris: TextDocumentIdentifier[]; + } + ``` - `deno/performance` - Requests the return of the timing averages for the internal instrumentation of Deno. + + It does not expect any parameters. - `deno/virtualTextDocument` - Requests a virtual text document from the LSP, which is a read only document that can be displayed in the client. This allows clients to access documents in the Deno cache, like remote modules and - TypeScript library files built into Deno. It also supports a special URL of - `deno:/status.md` which provides a markdown formatted text document that - contains details about the status of the LSP for display to a user. + TypeScript library files built into Deno. The Deno language server will encode + all internal files under the custom schema `deno:`, so clients should route + all requests for the `deno:` schema back to the `deno/virtualTextDocument` + API. + + It also supports a special URL of `deno:/status.md` which provides a markdown + formatted text document that contains details about the status of the LSP for + display to a user. + + It expects parameters of: + + ```ts + interface VirtualTextDocumentParams { + textDocument: TextDocumentIdentifier; + } + ``` diff --git a/cli/lsp/analysis.rs b/cli/lsp/analysis.rs index 8a1c565376..5e3ecab23d 100644 --- a/cli/lsp/analysis.rs +++ b/cli/lsp/analysis.rs @@ -456,11 +456,14 @@ impl CodeActionCollection { ) -> Result<(), AnyError> { if let Some(data) = diagnostic.data.clone() { let fix_data: DenoFixData = serde_json::from_value(data)?; + let title = if matches!(&diagnostic.code, Some(lsp::NumberOrString::String(code)) if code == "no-cache-data") + { + "Cache the data URL and its dependencies.".to_string() + } else { + format!("Cache \"{}\" and its dependencies.", fix_data.specifier) + }; let code_action = lsp::CodeAction { - title: format!( - "Cache \"{}\" and its dependencies.", - fix_data.specifier - ), + title, kind: Some(lsp::CodeActionKind::QUICKFIX), diagnostics: Some(vec![diagnostic.clone()]), edit: None, diff --git a/cli/lsp/diagnostics.rs b/cli/lsp/diagnostics.rs index 8017d7e28c..705f0866de 100644 --- a/cli/lsp/diagnostics.rs +++ b/cli/lsp/diagnostics.rs @@ -295,9 +295,11 @@ pub async fn generate_dependency_diagnostics( } ResolvedDependency::Resolved(specifier) => { if !(state_snapshot.documents.contains_key(&specifier) || sources.contains_key(&specifier)) { - let is_local = specifier.scheme() == "file"; - let (code, message) = if is_local { + let scheme = specifier.scheme(); + let (code, message) = if scheme == "file" { (Some(lsp::NumberOrString::String("no-local".to_string())), format!("Unable to load a local module: \"{}\".\n Please check the file path.", specifier)) + } else if scheme == "data" { + (Some(lsp::NumberOrString::String("no-cache-data".to_string())), "Uncached data URL.".to_string()) } else { (Some(lsp::NumberOrString::String("no-cache".to_string())), format!("Unable to load the remote module: \"{}\".", specifier)) }; diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index 7c196d67eb..d167b53bb8 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -53,7 +53,7 @@ use super::tsc; use super::tsc::AssetDocument; use super::tsc::Assets; use super::tsc::TsServer; -use super::utils; +use super::urls; lazy_static! { static ref ABSTRACT_MODIFIER: Regex = Regex::new(r"\babstract\b").unwrap(); @@ -101,6 +101,8 @@ pub(crate) struct Inner { ts_fixable_diagnostics: Vec, /// An abstraction that handles interactions with TypeScript. ts_server: TsServer, + /// A map of specifiers and URLs used to translate over the LSP. + pub url_map: urls::LspUrlMap, } impl LanguageServer { @@ -131,6 +133,7 @@ impl Inner { sources, ts_fixable_diagnostics: Default::default(), ts_server: TsServer::new(), + url_map: Default::default(), } } @@ -661,7 +664,7 @@ impl Inner { // already managed by the language service return; } - let specifier = utils::normalize_url(params.text_document.uri); + let specifier = self.url_map.normalize_url(¶ms.text_document.uri); self.documents.open( specifier.clone(), params.text_document.version, @@ -678,7 +681,7 @@ impl Inner { async fn did_change(&mut self, params: DidChangeTextDocumentParams) { let mark = self.performance.mark("did_change"); - let specifier = utils::normalize_url(params.text_document.uri); + let specifier = self.url_map.normalize_url(¶ms.text_document.uri); match self.documents.change( &specifier, params.text_document.version, @@ -704,7 +707,7 @@ impl Inner { // already managed by the language service return; } - let specifier = utils::normalize_url(params.text_document.uri); + let specifier = self.url_map.normalize_url(¶ms.text_document.uri); self.documents.close(&specifier); self.navigation_trees.remove(&specifier); @@ -802,7 +805,7 @@ impl Inner { params: DocumentFormattingParams, ) -> LspResult>> { let mark = self.performance.mark("formatting"); - let specifier = utils::normalize_url(params.text_document.uri.clone()); + let specifier = self.url_map.normalize_url(¶ms.text_document.uri); let file_text = self .documents .content(&specifier) @@ -858,9 +861,9 @@ impl Inner { return Ok(None); } let mark = self.performance.mark("hover"); - let specifier = utils::normalize_url( - params.text_document_position_params.text_document.uri, - ); + let specifier = self + .url_map + .normalize_url(¶ms.text_document_position_params.text_document.uri); let line_index = if let Some(line_index) = self.get_line_index_sync(&specifier) { line_index @@ -898,7 +901,7 @@ impl Inner { } let mark = self.performance.mark("code_action"); - let specifier = utils::normalize_url(params.text_document.uri); + let specifier = self.url_map.normalize_url(¶ms.text_document.uri); let fixable_diagnostics: Vec<&Diagnostic> = params .context .diagnostics @@ -915,7 +918,9 @@ impl Inner { _ => false, }, "deno" => match &d.code { - Some(NumberOrString::String(code)) => code == "no-cache", + Some(NumberOrString::String(code)) => { + code == "no-cache" || code == "no-cache-data" + } _ => false, }, _ => false, @@ -1053,7 +1058,7 @@ impl Inner { } let mark = self.performance.mark("code_lens"); - let specifier = utils::normalize_url(params.text_document.uri); + let specifier = self.url_map.normalize_url(¶ms.text_document.uri); let line_index = self.get_line_index_sync(&specifier).unwrap(); let navigation_tree = self.get_navigation_tree(&specifier).await.map_err(|err| { @@ -1209,7 +1214,7 @@ impl Inner { LspError::internal_error() })?; let implementation_location = - implementation.to_location(&line_index); + implementation.to_location(&line_index, self); if !(implementation_specifier == code_lens_data.specifier && implementation_location.range.start == params.range.start) { @@ -1222,11 +1227,13 @@ impl Inner { } else { "1 implementation".to_string() }; - let url = utils::normalize_specifier(&code_lens_data.specifier) + let url = self + .url_map + .normalize_specifier(&code_lens_data.specifier) .map_err(|err| { - error!("{}", err); - LspError::internal_error() - })?; + error!("{}", err); + LspError::internal_error() + })?; Command { title, command: "deno.showReferences".to_string(), @@ -1300,7 +1307,7 @@ impl Inner { error!("Unable to get line index: {}", err); LspError::internal_error() })?; - locations.push(reference.to_location(&line_index)); + locations.push(reference.to_location(&line_index, self)); } let command = if !locations.is_empty() { let title = if locations.len() > 1 { @@ -1308,11 +1315,13 @@ impl Inner { } else { "1 reference".to_string() }; - let url = utils::normalize_specifier(&code_lens_data.specifier) + let url = self + .url_map + .normalize_specifier(&code_lens_data.specifier) .map_err(|err| { - error!("{}", err); - LspError::internal_error() - })?; + error!("{}", err); + LspError::internal_error() + })?; Command { title, command: "deno.showReferences".to_string(), @@ -1366,9 +1375,9 @@ impl Inner { return Ok(None); } let mark = self.performance.mark("document_highlight"); - let specifier = utils::normalize_url( - params.text_document_position_params.text_document.uri, - ); + let specifier = self + .url_map + .normalize_url(¶ms.text_document_position_params.text_document.uri); let line_index = if let Some(line_index) = self.get_line_index_sync(&specifier) { line_index @@ -1412,8 +1421,9 @@ impl Inner { return Ok(None); } let mark = self.performance.mark("references"); - let specifier = - utils::normalize_url(params.text_document_position.text_document.uri); + let specifier = self + .url_map + .normalize_url(¶ms.text_document_position.text_document.uri); let line_index = if let Some(line_index) = self.get_line_index_sync(&specifier) { line_index @@ -1444,7 +1454,7 @@ impl Inner { // TODO(lucacasonato): handle error correctly let line_index = self.get_line_index(reference_specifier).await.unwrap(); - results.push(reference.to_location(&line_index)); + results.push(reference.to_location(&line_index, self)); } self.performance.measure(mark); @@ -1463,9 +1473,9 @@ impl Inner { return Ok(None); } let mark = self.performance.mark("goto_definition"); - let specifier = utils::normalize_url( - params.text_document_position_params.text_document.uri, - ); + let specifier = self + .url_map + .normalize_url(¶ms.text_document_position_params.text_document.uri); let line_index = if let Some(line_index) = self.get_line_index_sync(&specifier) { line_index @@ -1503,8 +1513,9 @@ impl Inner { return Ok(None); } let mark = self.performance.mark("completion"); - let specifier = - utils::normalize_url(params.text_document_position.text_document.uri); + let specifier = self + .url_map + .normalize_url(¶ms.text_document_position.text_document.uri); // TODO(lucacasonato): handle error correctly let line_index = if let Some(line_index) = self.get_line_index_sync(&specifier) { @@ -1548,9 +1559,9 @@ impl Inner { return Ok(None); } let mark = self.performance.mark("goto_implementation"); - let specifier = utils::normalize_url( - params.text_document_position_params.text_document.uri, - ); + let specifier = self + .url_map + .normalize_url(¶ms.text_document_position_params.text_document.uri); let line_index = if let Some(line_index) = self.get_line_index_sync(&specifier) { line_index @@ -1605,8 +1616,9 @@ impl Inner { return Ok(None); } let mark = self.performance.mark("rename"); - let specifier = - utils::normalize_url(params.text_document_position.text_document.uri); + let specifier = self + .url_map + .normalize_url(¶ms.text_document_position.text_document.uri); let line_index = if let Some(line_index) = self.get_line_index_sync(&specifier) { @@ -1710,9 +1722,9 @@ impl Inner { return Ok(None); } let mark = self.performance.mark("signature_help"); - let specifier = utils::normalize_url( - params.text_document_position_params.text_document.uri, - ); + let specifier = self + .url_map + .normalize_url(¶ms.text_document_position_params.text_document.uri); let line_index = if let Some(line_index) = self.get_line_index_sync(&specifier) { line_index @@ -1930,10 +1942,10 @@ impl Inner { /// in the Deno cache, including any of their dependencies. async fn cache(&mut self, params: CacheParams) -> LspResult { let mark = self.performance.mark("cache"); - let referrer = utils::normalize_url(params.referrer.uri); + let referrer = self.url_map.normalize_url(¶ms.referrer.uri); if !params.uris.is_empty() { for identifier in ¶ms.uris { - let specifier = utils::normalize_url(identifier.uri.clone()); + let specifier = self.url_map.normalize_url(&identifier.uri); sources::cache(&specifier, &self.maybe_import_map) .await .map_err(|err| { @@ -1976,7 +1988,7 @@ impl Inner { params: VirtualTextDocumentParams, ) -> LspResult> { let mark = self.performance.mark("virtual_text_document"); - let specifier = utils::normalize_url(params.text_document.uri); + let specifier = self.url_map.normalize_url(¶ms.text_document.uri); let contents = if specifier.as_str() == "deno:/status.md" { let mut contents = String::new(); @@ -2170,15 +2182,15 @@ mod tests { ("initialize_request.json", LspResponse::RequestAny), ("initialized_notification.json", LspResponse::None), ("did_open_notification_asset.json", LspResponse::None), - ("hover_request_asset_01.json", LspResponse::RequestAny), + ("definition_request_asset.json", LspResponse::RequestAny), ( "virtual_text_document_request.json", LspResponse::RequestAny, ), ( - "hover_request_asset_02.json", + "hover_request_asset.json", LspResponse::Request( - 4, + 5, json!({ "contents": [ { @@ -2807,6 +2819,7 @@ mod tests { ("initialize_request.json", LspResponse::RequestAny), ("initialized_notification.json", LspResponse::None), ("did_open_notification_asset.json", LspResponse::None), + ("references_request_asset.json", LspResponse::RequestAny), ( "virtual_text_document_request.json", LspResponse::RequestAny, diff --git a/cli/lsp/mod.rs b/cli/lsp/mod.rs index 110bb4db2d..06f81646df 100644 --- a/cli/lsp/mod.rs +++ b/cli/lsp/mod.rs @@ -13,7 +13,7 @@ mod performance; mod sources; mod text; mod tsc; -mod utils; +mod urls; pub async fn start() -> Result<(), AnyError> { let stdin = tokio::io::stdin(); diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs index 8fc429d9d1..8adcb7f2b1 100644 --- a/cli/lsp/tsc.rs +++ b/cli/lsp/tsc.rs @@ -7,7 +7,6 @@ use super::language_server; use super::language_server::StateSnapshot; use super::text; use super::text::LineIndex; -use super::utils; use crate::media_type::MediaType; use crate::tokio_util::create_basic_runtime; @@ -480,40 +479,41 @@ impl DocumentSpan { language_server: &mut language_server::Inner, ) -> Option { let target_specifier = resolve_url(&self.file_name).unwrap(); - if let Ok(target_line_index) = - language_server.get_line_index(target_specifier).await - { - let target_uri = utils::normalize_file_name(&self.file_name).unwrap(); - let (target_range, target_selection_range) = - if let Some(context_span) = &self.context_span { - ( - context_span.to_range(&target_line_index), - self.text_span.to_range(&target_line_index), - ) - } else { - ( - self.text_span.to_range(&target_line_index), - self.text_span.to_range(&target_line_index), - ) - }; - let origin_selection_range = - if let Some(original_context_span) = &self.original_context_span { - Some(original_context_span.to_range(line_index)) - } else if let Some(original_text_span) = &self.original_text_span { - Some(original_text_span.to_range(line_index)) - } else { - None - }; - let link = lsp::LocationLink { - origin_selection_range, - target_uri, - target_range, - target_selection_range, + let target_line_index = language_server + .get_line_index(target_specifier.clone()) + .await + .ok()?; + let target_uri = language_server + .url_map + .normalize_specifier(&target_specifier) + .unwrap(); + let (target_range, target_selection_range) = + if let Some(context_span) = &self.context_span { + ( + context_span.to_range(&target_line_index), + self.text_span.to_range(&target_line_index), + ) + } else { + ( + self.text_span.to_range(&target_line_index), + self.text_span.to_range(&target_line_index), + ) }; - Some(link) - } else { - None - } + let origin_selection_range = + if let Some(original_context_span) = &self.original_context_span { + Some(original_context_span.to_range(line_index)) + } else if let Some(original_text_span) = &self.original_text_span { + Some(original_text_span.to_range(line_index)) + } else { + None + }; + let link = lsp::LocationLink { + origin_selection_range, + target_uri, + target_range, + target_selection_range, + }; + Some(link) } } @@ -589,9 +589,16 @@ pub struct ImplementationLocation { } impl ImplementationLocation { - pub fn to_location(&self, line_index: &LineIndex) -> lsp::Location { - let uri = - utils::normalize_file_name(&self.document_span.file_name).unwrap(); + pub(crate) fn to_location( + &self, + line_index: &LineIndex, + language_server: &mut language_server::Inner, + ) -> lsp::Location { + let specifier = resolve_url(&self.document_span.file_name).unwrap(); + let uri = language_server + .url_map + .normalize_specifier(&specifier) + .unwrap(); lsp::Location { uri, range: self.document_span.text_span.to_range(line_index), @@ -633,8 +640,8 @@ impl RenameLocations { let mut text_document_edit_map: HashMap = HashMap::new(); for location in self.locations.iter() { - let uri = utils::normalize_file_name(&location.document_span.file_name)?; let specifier = resolve_url(&location.document_span.file_name)?; + let uri = language_server.url_map.normalize_specifier(&specifier)?; // ensure TextDocumentEdit for `location.file_name`. if text_document_edit_map.get(&uri).is_none() { @@ -852,9 +859,16 @@ pub struct ReferenceEntry { } impl ReferenceEntry { - pub fn to_location(&self, line_index: &LineIndex) -> lsp::Location { - let uri = - utils::normalize_file_name(&self.document_span.file_name).unwrap(); + pub(crate) fn to_location( + &self, + line_index: &LineIndex, + language_server: &mut language_server::Inner, + ) -> lsp::Location { + let specifier = resolve_url(&self.document_span.file_name).unwrap(); + let uri = language_server + .url_map + .normalize_specifier(&specifier) + .unwrap(); lsp::Location { uri, range: self.document_span.text_span.to_range(line_index), @@ -1237,7 +1251,7 @@ fn resolve(state: &mut State, args: Value) -> Result { if specifier.starts_with("asset:///") { resolved.push(Some(( specifier.clone(), - MediaType::from(specifier).as_ts_extension(), + MediaType::from(specifier).as_ts_extension().into(), ))) } else if let Some(dependency) = dependencies.get(specifier) { let resolved_import = @@ -1259,7 +1273,7 @@ fn resolve(state: &mut State, args: Value) -> Result { let media_type = MediaType::from(&resolved_specifier); resolved.push(Some(( resolved_specifier.to_string(), - media_type.as_ts_extension(), + media_type.as_ts_extension().into(), ))); } else if sources.contains_key(&resolved_specifier) { let media_type = if let Some(media_type) = @@ -1271,7 +1285,7 @@ fn resolve(state: &mut State, args: Value) -> Result { }; resolved.push(Some(( resolved_specifier.to_string(), - media_type.as_ts_extension(), + media_type.as_ts_extension().into(), ))); } else { resolved.push(None); @@ -1289,7 +1303,7 @@ fn resolve(state: &mut State, args: Value) -> Result { { resolved.push(Some(( resolved_specifier.to_string(), - media_type.as_ts_extension(), + media_type.as_ts_extension().into(), ))); } else { resolved.push(None); diff --git a/cli/lsp/urls.rs b/cli/lsp/urls.rs new file mode 100644 index 0000000000..a4af7d7b93 --- /dev/null +++ b/cli/lsp/urls.rs @@ -0,0 +1,174 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +use crate::file_fetcher::map_content_type; +use crate::media_type::MediaType; + +use deno_core::error::AnyError; +use deno_core::url::Url; +use deno_core::ModuleSpecifier; +use std::collections::HashMap; + +/// This is a partial guess as how URLs get encoded from the LSP. We want to +/// encode URLs sent to the LSP in the same way that they would be encoded back +/// so that we have the same return encoding. +const LSP_ENCODING: &percent_encoding::AsciiSet = &percent_encoding::CONTROLS + .add(b':') + .add(b';') + .add(b'=') + .add(b'@') + .add(b'[') + .add(b'\\') + .add(b']') + .add(b'^') + .add(b'|'); + +fn hash_data_specifier(specifier: &ModuleSpecifier) -> String { + let mut file_name_str = specifier.path().to_string(); + if let Some(query) = specifier.query() { + file_name_str.push('?'); + file_name_str.push_str(query); + } + crate::checksum::gen(&[file_name_str.as_bytes()]) +} + +fn data_url_media_type(specifier: &ModuleSpecifier) -> MediaType { + let path = specifier.path(); + let mut parts = path.splitn(2, ','); + let media_type_part = + percent_encoding::percent_decode_str(parts.next().unwrap()) + .decode_utf8_lossy(); + let (media_type, _) = + map_content_type(specifier, Some(media_type_part.into())); + media_type +} + +/// 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 +/// to allow the Deno language server to manage these as virtual documents. +#[derive(Debug, Default)] +pub struct LspUrlMap { + specifier_to_url: HashMap, + url_to_specifier: HashMap, +} + +impl LspUrlMap { + fn put(&mut self, specifier: ModuleSpecifier, url: Url) { + self.specifier_to_url.insert(specifier.clone(), url.clone()); + self.url_to_specifier.insert(url, specifier); + } + + fn get_url(&self, specifier: &ModuleSpecifier) -> Option<&Url> { + self.specifier_to_url.get(specifier) + } + + fn get_specifier(&self, url: &Url) -> Option<&ModuleSpecifier> { + self.url_to_specifier.get(url) + } + + /// 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. + pub fn normalize_specifier( + &mut self, + specifier: &ModuleSpecifier, + ) -> Result { + if let Some(url) = self.get_url(specifier) { + Ok(url.clone()) + } else { + let url = if specifier.scheme() == "file" { + specifier.clone() + } else { + let specifier_str = if specifier.scheme() == "data" { + let media_type = data_url_media_type(specifier); + let extension = if media_type == MediaType::Unknown { + "" + } else { + media_type.as_ts_extension() + }; + format!( + "deno:/{}/data_url{}", + hash_data_specifier(specifier), + extension + ) + } else { + let path = specifier.as_str().replacen("://", "/", 1); + let path = percent_encoding::utf8_percent_encode(&path, LSP_ENCODING); + format!("deno:/{}", path) + }; + let url = Url::parse(&specifier_str)?; + self.put(specifier.clone(), url.clone()); + url + }; + Ok(url) + } + } + + /// Normalize URLs from the client, where "virtual" `deno:///` URLs are + /// converted into proper module specifiers. + pub fn normalize_url(&self, url: &Url) -> ModuleSpecifier { + if let Some(specifier) = self.get_specifier(url) { + specifier.clone() + } else { + url.clone() + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use deno_core::resolve_url; + + #[test] + fn test_hash_data_specifier() { + let fixture = resolve_url("data:application/typescript;base64,ZXhwb3J0IGNvbnN0IGEgPSAiYSI7CgpleHBvcnQgZW51bSBBIHsKICBBLAogIEIsCiAgQywKfQo=").unwrap(); + let actual = hash_data_specifier(&fixture); + assert_eq!( + actual, + "c21c7fc382b2b0553dc0864aa81a3acacfb7b3d1285ab5ae76da6abec213fb37" + ); + } + + #[test] + fn test_data_url_media_type() { + let fixture = resolve_url("data:application/typescript;base64,ZXhwb3J0IGNvbnN0IGEgPSAiYSI7CgpleHBvcnQgZW51bSBBIHsKICBBLAogIEIsCiAgQywKfQo=").unwrap(); + let actual = data_url_media_type(&fixture); + assert_eq!(actual, MediaType::TypeScript); + + let fixture = resolve_url("data:application/javascript;base64,ZXhwb3J0IGNvbnN0IGEgPSAiYSI7CgpleHBvcnQgZW51bSBBIHsKICBBLAogIEIsCiAgQywKfQo=").unwrap(); + let actual = data_url_media_type(&fixture); + assert_eq!(actual, MediaType::JavaScript); + + let fixture = resolve_url("data:text/plain;base64,ZXhwb3J0IGNvbnN0IGEgPSAiYSI7CgpleHBvcnQgZW51bSBBIHsKICBBLAogIEIsCiAgQywKfQo=").unwrap(); + let actual = data_url_media_type(&fixture); + assert_eq!(actual, MediaType::Unknown); + } + + #[test] + fn test_lsp_url_map() { + let mut map = LspUrlMap::default(); + let fixture = resolve_url("https://deno.land/x/pkg@1.0.0/mod.ts").unwrap(); + let actual_url = map + .normalize_specifier(&fixture) + .expect("could not handle specifier"); + let expected_url = + Url::parse("deno:/https/deno.land/x/pkg%401.0.0/mod.ts").unwrap(); + assert_eq!(actual_url, expected_url); + + let actual_specifier = map.normalize_url(&actual_url); + assert_eq!(actual_specifier, fixture); + } + + #[test] + fn test_lsp_url_map_data() { + let mut map = LspUrlMap::default(); + let fixture = resolve_url("data:application/typescript;base64,ZXhwb3J0IGNvbnN0IGEgPSAiYSI7CgpleHBvcnQgZW51bSBBIHsKICBBLAogIEIsCiAgQywKfQo=").unwrap(); + let actual_url = map + .normalize_specifier(&fixture) + .expect("could not handle specifier"); + let expected_url = Url::parse("deno:/c21c7fc382b2b0553dc0864aa81a3acacfb7b3d1285ab5ae76da6abec213fb37/data_url.ts").unwrap(); + assert_eq!(actual_url, expected_url); + + let actual_specifier = map.normalize_url(&actual_url); + assert_eq!(actual_specifier, fixture); + } +} diff --git a/cli/media_type.rs b/cli/media_type.rs index db64b3922c..761de0ca03 100644 --- a/cli/media_type.rs +++ b/cli/media_type.rs @@ -121,8 +121,8 @@ impl MediaType { /// /// *NOTE* This is defined in TypeScript as a string based enum. Changes to /// that enum in TypeScript should be reflected here. - pub fn as_ts_extension(&self) -> String { - let ext = match self { + pub fn as_ts_extension(&self) -> &str { + match self { MediaType::JavaScript => ".js", MediaType::JSX => ".jsx", MediaType::TypeScript => ".ts", @@ -138,13 +138,11 @@ impl MediaType { // JS for mapping purposes, though in reality, it is unlikely to ever be // passed to the compiler. MediaType::SourceMap => ".js", - // TypeScript doesn't have an "unknown", so we will treat WASM as JS for - // mapping purposes, though in reality, it is unlikely to ever be passed - // to the compiler. + // TypeScript doesn't have an "unknown", so we will treat unknowns as JS + // for mapping purposes, though in reality, it is unlikely to ever be + // passed to the compiler. MediaType::Unknown => ".js", - }; - - ext.into() + } } /// Map the media type to a `ts.ScriptKind` diff --git a/cli/tests/lsp/code_lens_request_asset.json b/cli/tests/lsp/code_lens_request_asset.json index b9db86749e..6aa246ce5c 100644 --- a/cli/tests/lsp/code_lens_request_asset.json +++ b/cli/tests/lsp/code_lens_request_asset.json @@ -4,7 +4,7 @@ "method": "textDocument/codeLens", "params": { "textDocument": { - "uri": "deno:/asset//lib.es2015.symbol.wellknown.d.ts" + "uri": "deno:/asset//lib.deno.shared_globals.d.ts" } } } diff --git a/cli/tests/lsp/code_lens_resolve_request_asset.json b/cli/tests/lsp/code_lens_resolve_request_asset.json index c225a62e2c..027af96b61 100644 --- a/cli/tests/lsp/code_lens_resolve_request_asset.json +++ b/cli/tests/lsp/code_lens_resolve_request_asset.json @@ -5,17 +5,17 @@ "params": { "range": { "start": { - "line": 93, - "character": 10 + "line": 416, + "character": 12 }, "end": { - "line": 93, - "character": 15 + "line": 416, + "character": 19 } }, "data": { - "specifier": "asset:///lib.es2015.symbol.wellknown.d.ts", - "source": "implementations" + "specifier": "asset:///lib.deno.shared_globals.d.ts", + "source": "references" } } } diff --git a/cli/tests/lsp/hover_request_asset_01.json b/cli/tests/lsp/definition_request_asset.json similarity index 71% rename from cli/tests/lsp/hover_request_asset_01.json rename to cli/tests/lsp/definition_request_asset.json index fb1c899a30..5e117cf147 100644 --- a/cli/tests/lsp/hover_request_asset_01.json +++ b/cli/tests/lsp/definition_request_asset.json @@ -1,14 +1,14 @@ { "jsonrpc": "2.0", "id": 4, - "method": "textDocument/hover", + "method": "textDocument/definition", "params": { "textDocument": { "uri": "file:///a/file.ts" }, "position": { "line": 0, - "character": 12 + "character": 14 } } } diff --git a/cli/tests/lsp/did_open_notification_asset.json b/cli/tests/lsp/did_open_notification_asset.json index 6288bbc534..413353f298 100644 --- a/cli/tests/lsp/did_open_notification_asset.json +++ b/cli/tests/lsp/did_open_notification_asset.json @@ -6,7 +6,7 @@ "uri": "file:///a/file.ts", "languageId": "typescript", "version": 1, - "text": "console.log(\"hello deno!\");\n" + "text": "console.log(Date.now());\n" } } } diff --git a/cli/tests/lsp/hover_request_asset_02.json b/cli/tests/lsp/hover_request_asset.json similarity index 95% rename from cli/tests/lsp/hover_request_asset_02.json rename to cli/tests/lsp/hover_request_asset.json index 30f4047096..2ae96acd5f 100644 --- a/cli/tests/lsp/hover_request_asset_02.json +++ b/cli/tests/lsp/hover_request_asset.json @@ -1,6 +1,6 @@ { "jsonrpc": "2.0", - "id": 4, + "id": 5, "method": "textDocument/hover", "params": { "textDocument": { diff --git a/cli/tests/lsp/references_request_asset.json b/cli/tests/lsp/references_request_asset.json new file mode 100644 index 0000000000..6c2430e505 --- /dev/null +++ b/cli/tests/lsp/references_request_asset.json @@ -0,0 +1,17 @@ +{ + "jsonrpc": "2.0", + "id": 2, + "method": "textDocument/references", + "params": { + "textDocument": { + "uri": "file:///a/file.ts" + }, + "position": { + "line": 0, + "character": 3 + }, + "context": { + "includeDeclaration": true + } + } +} diff --git a/cli/tests/lsp/virtual_text_document_request.json b/cli/tests/lsp/virtual_text_document_request.json index 08ad7a3ca4..6ffab4a356 100644 --- a/cli/tests/lsp/virtual_text_document_request.json +++ b/cli/tests/lsp/virtual_text_document_request.json @@ -1,10 +1,10 @@ { "jsonrpc": "2.0", - "id": 2, + "id": 4, "method": "deno/virtualTextDocument", "params": { "textDocument": { - "uri": "deno:/asset//lib.es2015.symbol.wellknown.d.ts" + "uri": "deno:/asset//lib.deno.shared_globals.d.ts" } } } diff --git a/cli/tsc.rs b/cli/tsc.rs index 535447c543..5b6d00310a 100644 --- a/cli/tsc.rs +++ b/cli/tsc.rs @@ -383,7 +383,10 @@ fn resolve(state: &mut State, args: Value) -> Result { } else { resolved_specifier.to_string() }; - resolved.push((resolved_specifier_str, media_type.as_ts_extension())); + resolved.push(( + resolved_specifier_str, + media_type.as_ts_extension().into(), + )); } // in certain situations, like certain dynamic imports, we won't have // the source file in the graph, so we will return a fake module to