summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKitson Kelly <me@kitsonkelly.com>2021-02-18 15:37:05 +1100
committerGitHub <noreply@github.com>2021-02-18 15:37:05 +1100
commit2225e83da2d118678e3df1e2801af195166bc65a (patch)
tree0d33bdf77e19872a72fca4364281c6cc14ec5724
parent78e34d49120d8cc2583d8d6d28ae9b74f9765533 (diff)
fix(lsp): handle data URLs properly (#9522)
Fixes #9514 Co-authored-by: Ryan Dahl <ry@tinyclouds.org>
-rw-r--r--cli/lsp/README.md35
-rw-r--r--cli/lsp/analysis.rs11
-rw-r--r--cli/lsp/diagnostics.rs6
-rw-r--r--cli/lsp/language_server.rs105
-rw-r--r--cli/lsp/mod.rs2
-rw-r--r--cli/lsp/tsc.rs104
-rw-r--r--cli/lsp/urls.rs174
-rw-r--r--cli/media_type.rs14
-rw-r--r--cli/tests/lsp/code_lens_request_asset.json2
-rw-r--r--cli/tests/lsp/code_lens_resolve_request_asset.json12
-rw-r--r--cli/tests/lsp/definition_request_asset.json (renamed from cli/tests/lsp/hover_request_asset_01.json)4
-rw-r--r--cli/tests/lsp/did_open_notification_asset.json2
-rw-r--r--cli/tests/lsp/hover_request_asset.json (renamed from cli/tests/lsp/hover_request_asset_02.json)2
-rw-r--r--cli/tests/lsp/references_request_asset.json17
-rw-r--r--cli/tests/lsp/virtual_text_document_request.json4
-rw-r--r--cli/tsc.rs5
16 files changed, 374 insertions, 125 deletions
diff --git a/cli/lsp/README.md b/cli/lsp/README.md
index c43590f60..a32deb585 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 8a1c56537..5e3ecab23 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 8017d7e28..705f0866d 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 7c196d67e..d167b53bb 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<String>,
/// 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(&params.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(&params.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(&params.text_document.uri);
self.documents.close(&specifier);
self.navigation_trees.remove(&specifier);
@@ -802,7 +805,7 @@ impl Inner {
params: DocumentFormattingParams,
) -> LspResult<Option<Vec<TextEdit>>> {
let mark = self.performance.mark("formatting");
- let specifier = utils::normalize_url(params.text_document.uri.clone());
+ let specifier = self.url_map.normalize_url(&params.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(&params.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(&params.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(&params.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(&params.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(&params.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(&params.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(&params.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(&params.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(&params.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(&params.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<bool> {
let mark = self.performance.mark("cache");
- let referrer = utils::normalize_url(params.referrer.uri);
+ let referrer = self.url_map.normalize_url(&params.referrer.uri);
if !params.uris.is_empty() {
for identifier in &params.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<Option<String>> {
let mark = self.performance.mark("virtual_text_document");
- let specifier = utils::normalize_url(params.text_document.uri);
+ let specifier = self.url_map.normalize_url(&params.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 110bb4db2..06f81646d 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 8fc429d9d..8adcb7f2b 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<lsp::LocationLink> {
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<Url, lsp::TextDocumentEdit> =
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<Value, AnyError> {
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<Value, AnyError> {
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<Value, AnyError> {
};
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<Value, AnyError> {
{
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 000000000..a4af7d7b9
--- /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<ModuleSpecifier, Url>,
+ url_to_specifier: HashMap<Url, ModuleSpecifier>,
+}
+
+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<Url, AnyError> {
+ 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 db64b3922..761de0ca0 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 b9db86749..6aa246ce5 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 c225a62e2..027af96b6 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
index fb1c899a3..5e117cf14 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 6288bbc53..413353f29 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
index 30f404709..2ae96acd5 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 000000000..6c2430e50
--- /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 08ad7a3ca..6ffab4a35 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 535447c54..5b6d00310 100644
--- a/cli/tsc.rs
+++ b/cli/tsc.rs
@@ -383,7 +383,10 @@ fn resolve(state: &mut State, args: Value) -> Result<Value, AnyError> {
} 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