1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-22 15:06:54 -05:00

fix(lsp): handle data URLs properly (#9522)

Fixes #9514

Co-authored-by: Ryan Dahl <ry@tinyclouds.org>
This commit is contained in:
Kitson Kelly 2021-02-18 15:37:05 +11:00 committed by GitHub
parent 78e34d4912
commit 2225e83da2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 374 additions and 125 deletions

View file

@ -22,13 +22,38 @@ implement these in order to have a fully functioning client that integrates well
with Deno: with Deno:
- `deno/cache` - This command will instruct Deno to attempt to cache a module - `deno/cache` - This command will instruct Deno to attempt to cache a module
and all of its dependencies. It expects an argument of and all of its dependencies. If a `referrer` only is passed, then all
`{ textDocument: TextDocumentIdentifier }` to be passed. 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 - `deno/performance` - Requests the return of the timing averages for the
internal instrumentation of Deno. internal instrumentation of Deno.
It does not expect any parameters.
- `deno/virtualTextDocument` - Requests a virtual text document from the LSP, - `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 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 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 TypeScript library files built into Deno. The Deno language server will encode
`deno:/status.md` which provides a markdown formatted text document that all internal files under the custom schema `deno:`, so clients should route
contains details about the status of the LSP for display to a user. 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;
}
```

View file

@ -456,11 +456,14 @@ impl CodeActionCollection {
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
if let Some(data) = diagnostic.data.clone() { if let Some(data) = diagnostic.data.clone() {
let fix_data: DenoFixData = serde_json::from_value(data)?; 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 { let code_action = lsp::CodeAction {
title: format!( title,
"Cache \"{}\" and its dependencies.",
fix_data.specifier
),
kind: Some(lsp::CodeActionKind::QUICKFIX), kind: Some(lsp::CodeActionKind::QUICKFIX),
diagnostics: Some(vec![diagnostic.clone()]), diagnostics: Some(vec![diagnostic.clone()]),
edit: None, edit: None,

View file

@ -295,9 +295,11 @@ pub async fn generate_dependency_diagnostics(
} }
ResolvedDependency::Resolved(specifier) => { ResolvedDependency::Resolved(specifier) => {
if !(state_snapshot.documents.contains_key(&specifier) || sources.contains_key(&specifier)) { if !(state_snapshot.documents.contains_key(&specifier) || sources.contains_key(&specifier)) {
let is_local = specifier.scheme() == "file"; let scheme = specifier.scheme();
let (code, message) = if is_local { 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)) (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 { } else {
(Some(lsp::NumberOrString::String("no-cache".to_string())), format!("Unable to load the remote module: \"{}\".", specifier)) (Some(lsp::NumberOrString::String("no-cache".to_string())), format!("Unable to load the remote module: \"{}\".", specifier))
}; };

View file

@ -53,7 +53,7 @@ use super::tsc;
use super::tsc::AssetDocument; use super::tsc::AssetDocument;
use super::tsc::Assets; use super::tsc::Assets;
use super::tsc::TsServer; use super::tsc::TsServer;
use super::utils; use super::urls;
lazy_static! { lazy_static! {
static ref ABSTRACT_MODIFIER: Regex = Regex::new(r"\babstract\b").unwrap(); static ref ABSTRACT_MODIFIER: Regex = Regex::new(r"\babstract\b").unwrap();
@ -101,6 +101,8 @@ pub(crate) struct Inner {
ts_fixable_diagnostics: Vec<String>, ts_fixable_diagnostics: Vec<String>,
/// An abstraction that handles interactions with TypeScript. /// An abstraction that handles interactions with TypeScript.
ts_server: TsServer, ts_server: TsServer,
/// A map of specifiers and URLs used to translate over the LSP.
pub url_map: urls::LspUrlMap,
} }
impl LanguageServer { impl LanguageServer {
@ -131,6 +133,7 @@ impl Inner {
sources, sources,
ts_fixable_diagnostics: Default::default(), ts_fixable_diagnostics: Default::default(),
ts_server: TsServer::new(), ts_server: TsServer::new(),
url_map: Default::default(),
} }
} }
@ -661,7 +664,7 @@ impl Inner {
// already managed by the language service // already managed by the language service
return; return;
} }
let specifier = utils::normalize_url(params.text_document.uri); let specifier = self.url_map.normalize_url(&params.text_document.uri);
self.documents.open( self.documents.open(
specifier.clone(), specifier.clone(),
params.text_document.version, params.text_document.version,
@ -678,7 +681,7 @@ impl Inner {
async fn did_change(&mut self, params: DidChangeTextDocumentParams) { async fn did_change(&mut self, params: DidChangeTextDocumentParams) {
let mark = self.performance.mark("did_change"); 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( match self.documents.change(
&specifier, &specifier,
params.text_document.version, params.text_document.version,
@ -704,7 +707,7 @@ impl Inner {
// already managed by the language service // already managed by the language service
return; 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.documents.close(&specifier);
self.navigation_trees.remove(&specifier); self.navigation_trees.remove(&specifier);
@ -802,7 +805,7 @@ impl Inner {
params: DocumentFormattingParams, params: DocumentFormattingParams,
) -> LspResult<Option<Vec<TextEdit>>> { ) -> LspResult<Option<Vec<TextEdit>>> {
let mark = self.performance.mark("formatting"); 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 let file_text = self
.documents .documents
.content(&specifier) .content(&specifier)
@ -858,9 +861,9 @@ impl Inner {
return Ok(None); return Ok(None);
} }
let mark = self.performance.mark("hover"); let mark = self.performance.mark("hover");
let specifier = utils::normalize_url( let specifier = self
params.text_document_position_params.text_document.uri, .url_map
); .normalize_url(&params.text_document_position_params.text_document.uri);
let line_index = let line_index =
if let Some(line_index) = self.get_line_index_sync(&specifier) { if let Some(line_index) = self.get_line_index_sync(&specifier) {
line_index line_index
@ -898,7 +901,7 @@ impl Inner {
} }
let mark = self.performance.mark("code_action"); 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 let fixable_diagnostics: Vec<&Diagnostic> = params
.context .context
.diagnostics .diagnostics
@ -915,7 +918,9 @@ impl Inner {
_ => false, _ => false,
}, },
"deno" => match &d.code { "deno" => match &d.code {
Some(NumberOrString::String(code)) => code == "no-cache", Some(NumberOrString::String(code)) => {
code == "no-cache" || code == "no-cache-data"
}
_ => false, _ => false,
}, },
_ => false, _ => false,
@ -1053,7 +1058,7 @@ impl Inner {
} }
let mark = self.performance.mark("code_lens"); 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 line_index = self.get_line_index_sync(&specifier).unwrap();
let navigation_tree = let navigation_tree =
self.get_navigation_tree(&specifier).await.map_err(|err| { self.get_navigation_tree(&specifier).await.map_err(|err| {
@ -1209,7 +1214,7 @@ impl Inner {
LspError::internal_error() LspError::internal_error()
})?; })?;
let implementation_location = let implementation_location =
implementation.to_location(&line_index); implementation.to_location(&line_index, self);
if !(implementation_specifier == code_lens_data.specifier if !(implementation_specifier == code_lens_data.specifier
&& implementation_location.range.start == params.range.start) && implementation_location.range.start == params.range.start)
{ {
@ -1222,11 +1227,13 @@ impl Inner {
} else { } else {
"1 implementation".to_string() "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| { .map_err(|err| {
error!("{}", err); error!("{}", err);
LspError::internal_error() LspError::internal_error()
})?; })?;
Command { Command {
title, title,
command: "deno.showReferences".to_string(), command: "deno.showReferences".to_string(),
@ -1300,7 +1307,7 @@ impl Inner {
error!("Unable to get line index: {}", err); error!("Unable to get line index: {}", err);
LspError::internal_error() 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 command = if !locations.is_empty() {
let title = if locations.len() > 1 { let title = if locations.len() > 1 {
@ -1308,11 +1315,13 @@ impl Inner {
} else { } else {
"1 reference".to_string() "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| { .map_err(|err| {
error!("{}", err); error!("{}", err);
LspError::internal_error() LspError::internal_error()
})?; })?;
Command { Command {
title, title,
command: "deno.showReferences".to_string(), command: "deno.showReferences".to_string(),
@ -1366,9 +1375,9 @@ impl Inner {
return Ok(None); return Ok(None);
} }
let mark = self.performance.mark("document_highlight"); let mark = self.performance.mark("document_highlight");
let specifier = utils::normalize_url( let specifier = self
params.text_document_position_params.text_document.uri, .url_map
); .normalize_url(&params.text_document_position_params.text_document.uri);
let line_index = let line_index =
if let Some(line_index) = self.get_line_index_sync(&specifier) { if let Some(line_index) = self.get_line_index_sync(&specifier) {
line_index line_index
@ -1412,8 +1421,9 @@ impl Inner {
return Ok(None); return Ok(None);
} }
let mark = self.performance.mark("references"); let mark = self.performance.mark("references");
let specifier = let specifier = self
utils::normalize_url(params.text_document_position.text_document.uri); .url_map
.normalize_url(&params.text_document_position.text_document.uri);
let line_index = let line_index =
if let Some(line_index) = self.get_line_index_sync(&specifier) { if let Some(line_index) = self.get_line_index_sync(&specifier) {
line_index line_index
@ -1444,7 +1454,7 @@ impl Inner {
// TODO(lucacasonato): handle error correctly // TODO(lucacasonato): handle error correctly
let line_index = let line_index =
self.get_line_index(reference_specifier).await.unwrap(); 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); self.performance.measure(mark);
@ -1463,9 +1473,9 @@ impl Inner {
return Ok(None); return Ok(None);
} }
let mark = self.performance.mark("goto_definition"); let mark = self.performance.mark("goto_definition");
let specifier = utils::normalize_url( let specifier = self
params.text_document_position_params.text_document.uri, .url_map
); .normalize_url(&params.text_document_position_params.text_document.uri);
let line_index = let line_index =
if let Some(line_index) = self.get_line_index_sync(&specifier) { if let Some(line_index) = self.get_line_index_sync(&specifier) {
line_index line_index
@ -1503,8 +1513,9 @@ impl Inner {
return Ok(None); return Ok(None);
} }
let mark = self.performance.mark("completion"); let mark = self.performance.mark("completion");
let specifier = let specifier = self
utils::normalize_url(params.text_document_position.text_document.uri); .url_map
.normalize_url(&params.text_document_position.text_document.uri);
// TODO(lucacasonato): handle error correctly // TODO(lucacasonato): handle error correctly
let line_index = let line_index =
if let Some(line_index) = self.get_line_index_sync(&specifier) { if let Some(line_index) = self.get_line_index_sync(&specifier) {
@ -1548,9 +1559,9 @@ impl Inner {
return Ok(None); return Ok(None);
} }
let mark = self.performance.mark("goto_implementation"); let mark = self.performance.mark("goto_implementation");
let specifier = utils::normalize_url( let specifier = self
params.text_document_position_params.text_document.uri, .url_map
); .normalize_url(&params.text_document_position_params.text_document.uri);
let line_index = let line_index =
if let Some(line_index) = self.get_line_index_sync(&specifier) { if let Some(line_index) = self.get_line_index_sync(&specifier) {
line_index line_index
@ -1605,8 +1616,9 @@ impl Inner {
return Ok(None); return Ok(None);
} }
let mark = self.performance.mark("rename"); let mark = self.performance.mark("rename");
let specifier = let specifier = self
utils::normalize_url(params.text_document_position.text_document.uri); .url_map
.normalize_url(&params.text_document_position.text_document.uri);
let line_index = let line_index =
if let Some(line_index) = self.get_line_index_sync(&specifier) { if let Some(line_index) = self.get_line_index_sync(&specifier) {
@ -1710,9 +1722,9 @@ impl Inner {
return Ok(None); return Ok(None);
} }
let mark = self.performance.mark("signature_help"); let mark = self.performance.mark("signature_help");
let specifier = utils::normalize_url( let specifier = self
params.text_document_position_params.text_document.uri, .url_map
); .normalize_url(&params.text_document_position_params.text_document.uri);
let line_index = let line_index =
if let Some(line_index) = self.get_line_index_sync(&specifier) { if let Some(line_index) = self.get_line_index_sync(&specifier) {
line_index line_index
@ -1930,10 +1942,10 @@ impl Inner {
/// in the Deno cache, including any of their dependencies. /// in the Deno cache, including any of their dependencies.
async fn cache(&mut self, params: CacheParams) -> LspResult<bool> { async fn cache(&mut self, params: CacheParams) -> LspResult<bool> {
let mark = self.performance.mark("cache"); 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() { if !params.uris.is_empty() {
for identifier in &params.uris { 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) sources::cache(&specifier, &self.maybe_import_map)
.await .await
.map_err(|err| { .map_err(|err| {
@ -1976,7 +1988,7 @@ impl Inner {
params: VirtualTextDocumentParams, params: VirtualTextDocumentParams,
) -> LspResult<Option<String>> { ) -> LspResult<Option<String>> {
let mark = self.performance.mark("virtual_text_document"); 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 contents = if specifier.as_str() == "deno:/status.md" {
let mut contents = String::new(); let mut contents = String::new();
@ -2170,15 +2182,15 @@ mod tests {
("initialize_request.json", LspResponse::RequestAny), ("initialize_request.json", LspResponse::RequestAny),
("initialized_notification.json", LspResponse::None), ("initialized_notification.json", LspResponse::None),
("did_open_notification_asset.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", "virtual_text_document_request.json",
LspResponse::RequestAny, LspResponse::RequestAny,
), ),
( (
"hover_request_asset_02.json", "hover_request_asset.json",
LspResponse::Request( LspResponse::Request(
4, 5,
json!({ json!({
"contents": [ "contents": [
{ {
@ -2807,6 +2819,7 @@ mod tests {
("initialize_request.json", LspResponse::RequestAny), ("initialize_request.json", LspResponse::RequestAny),
("initialized_notification.json", LspResponse::None), ("initialized_notification.json", LspResponse::None),
("did_open_notification_asset.json", LspResponse::None), ("did_open_notification_asset.json", LspResponse::None),
("references_request_asset.json", LspResponse::RequestAny),
( (
"virtual_text_document_request.json", "virtual_text_document_request.json",
LspResponse::RequestAny, LspResponse::RequestAny,

View file

@ -13,7 +13,7 @@ mod performance;
mod sources; mod sources;
mod text; mod text;
mod tsc; mod tsc;
mod utils; mod urls;
pub async fn start() -> Result<(), AnyError> { pub async fn start() -> Result<(), AnyError> {
let stdin = tokio::io::stdin(); let stdin = tokio::io::stdin();

View file

@ -7,7 +7,6 @@ use super::language_server;
use super::language_server::StateSnapshot; use super::language_server::StateSnapshot;
use super::text; use super::text;
use super::text::LineIndex; use super::text::LineIndex;
use super::utils;
use crate::media_type::MediaType; use crate::media_type::MediaType;
use crate::tokio_util::create_basic_runtime; use crate::tokio_util::create_basic_runtime;
@ -480,40 +479,41 @@ impl DocumentSpan {
language_server: &mut language_server::Inner, language_server: &mut language_server::Inner,
) -> Option<lsp::LocationLink> { ) -> Option<lsp::LocationLink> {
let target_specifier = resolve_url(&self.file_name).unwrap(); let target_specifier = resolve_url(&self.file_name).unwrap();
if let Ok(target_line_index) = let target_line_index = language_server
language_server.get_line_index(target_specifier).await .get_line_index(target_specifier.clone())
{ .await
let target_uri = utils::normalize_file_name(&self.file_name).unwrap(); .ok()?;
let (target_range, target_selection_range) = let target_uri = language_server
if let Some(context_span) = &self.context_span { .url_map
( .normalize_specifier(&target_specifier)
context_span.to_range(&target_line_index), .unwrap();
self.text_span.to_range(&target_line_index), let (target_range, target_selection_range) =
) if let Some(context_span) = &self.context_span {
} else { (
( context_span.to_range(&target_line_index),
self.text_span.to_range(&target_line_index), self.text_span.to_range(&target_line_index),
self.text_span.to_range(&target_line_index), )
) } else {
}; (
let origin_selection_range = self.text_span.to_range(&target_line_index),
if let Some(original_context_span) = &self.original_context_span { self.text_span.to_range(&target_line_index),
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) let origin_selection_range =
} else { if let Some(original_context_span) = &self.original_context_span {
None 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 { impl ImplementationLocation {
pub fn to_location(&self, line_index: &LineIndex) -> lsp::Location { pub(crate) fn to_location(
let uri = &self,
utils::normalize_file_name(&self.document_span.file_name).unwrap(); 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 { lsp::Location {
uri, uri,
range: self.document_span.text_span.to_range(line_index), 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> = let mut text_document_edit_map: HashMap<Url, lsp::TextDocumentEdit> =
HashMap::new(); HashMap::new();
for location in self.locations.iter() { 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 specifier = resolve_url(&location.document_span.file_name)?;
let uri = language_server.url_map.normalize_specifier(&specifier)?;
// ensure TextDocumentEdit for `location.file_name`. // ensure TextDocumentEdit for `location.file_name`.
if text_document_edit_map.get(&uri).is_none() { if text_document_edit_map.get(&uri).is_none() {
@ -852,9 +859,16 @@ pub struct ReferenceEntry {
} }
impl ReferenceEntry { impl ReferenceEntry {
pub fn to_location(&self, line_index: &LineIndex) -> lsp::Location { pub(crate) fn to_location(
let uri = &self,
utils::normalize_file_name(&self.document_span.file_name).unwrap(); 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 { lsp::Location {
uri, uri,
range: self.document_span.text_span.to_range(line_index), 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:///") { if specifier.starts_with("asset:///") {
resolved.push(Some(( resolved.push(Some((
specifier.clone(), specifier.clone(),
MediaType::from(specifier).as_ts_extension(), MediaType::from(specifier).as_ts_extension().into(),
))) )))
} else if let Some(dependency) = dependencies.get(specifier) { } else if let Some(dependency) = dependencies.get(specifier) {
let resolved_import = let resolved_import =
@ -1259,7 +1273,7 @@ fn resolve(state: &mut State, args: Value) -> Result<Value, AnyError> {
let media_type = MediaType::from(&resolved_specifier); let media_type = MediaType::from(&resolved_specifier);
resolved.push(Some(( resolved.push(Some((
resolved_specifier.to_string(), resolved_specifier.to_string(),
media_type.as_ts_extension(), media_type.as_ts_extension().into(),
))); )));
} else if sources.contains_key(&resolved_specifier) { } else if sources.contains_key(&resolved_specifier) {
let media_type = if let Some(media_type) = 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.push(Some((
resolved_specifier.to_string(), resolved_specifier.to_string(),
media_type.as_ts_extension(), media_type.as_ts_extension().into(),
))); )));
} else { } else {
resolved.push(None); resolved.push(None);
@ -1289,7 +1303,7 @@ fn resolve(state: &mut State, args: Value) -> Result<Value, AnyError> {
{ {
resolved.push(Some(( resolved.push(Some((
resolved_specifier.to_string(), resolved_specifier.to_string(),
media_type.as_ts_extension(), media_type.as_ts_extension().into(),
))); )));
} else { } else {
resolved.push(None); resolved.push(None);

174
cli/lsp/urls.rs Normal file
View file

@ -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);
}
}

View file

@ -121,8 +121,8 @@ impl MediaType {
/// ///
/// *NOTE* This is defined in TypeScript as a string based enum. Changes to /// *NOTE* This is defined in TypeScript as a string based enum. Changes to
/// that enum in TypeScript should be reflected here. /// that enum in TypeScript should be reflected here.
pub fn as_ts_extension(&self) -> String { pub fn as_ts_extension(&self) -> &str {
let ext = match self { match self {
MediaType::JavaScript => ".js", MediaType::JavaScript => ".js",
MediaType::JSX => ".jsx", MediaType::JSX => ".jsx",
MediaType::TypeScript => ".ts", MediaType::TypeScript => ".ts",
@ -138,13 +138,11 @@ impl MediaType {
// JS for mapping purposes, though in reality, it is unlikely to ever be // JS for mapping purposes, though in reality, it is unlikely to ever be
// passed to the compiler. // passed to the compiler.
MediaType::SourceMap => ".js", MediaType::SourceMap => ".js",
// TypeScript doesn't have an "unknown", so we will treat WASM as JS for // TypeScript doesn't have an "unknown", so we will treat unknowns as JS
// mapping purposes, though in reality, it is unlikely to ever be passed // for mapping purposes, though in reality, it is unlikely to ever be
// to the compiler. // passed to the compiler.
MediaType::Unknown => ".js", MediaType::Unknown => ".js",
}; }
ext.into()
} }
/// Map the media type to a `ts.ScriptKind` /// Map the media type to a `ts.ScriptKind`

View file

@ -4,7 +4,7 @@
"method": "textDocument/codeLens", "method": "textDocument/codeLens",
"params": { "params": {
"textDocument": { "textDocument": {
"uri": "deno:/asset//lib.es2015.symbol.wellknown.d.ts" "uri": "deno:/asset//lib.deno.shared_globals.d.ts"
} }
} }
} }

View file

@ -5,17 +5,17 @@
"params": { "params": {
"range": { "range": {
"start": { "start": {
"line": 93, "line": 416,
"character": 10 "character": 12
}, },
"end": { "end": {
"line": 93, "line": 416,
"character": 15 "character": 19
} }
}, },
"data": { "data": {
"specifier": "asset:///lib.es2015.symbol.wellknown.d.ts", "specifier": "asset:///lib.deno.shared_globals.d.ts",
"source": "implementations" "source": "references"
} }
} }
} }

View file

@ -1,14 +1,14 @@
{ {
"jsonrpc": "2.0", "jsonrpc": "2.0",
"id": 4, "id": 4,
"method": "textDocument/hover", "method": "textDocument/definition",
"params": { "params": {
"textDocument": { "textDocument": {
"uri": "file:///a/file.ts" "uri": "file:///a/file.ts"
}, },
"position": { "position": {
"line": 0, "line": 0,
"character": 12 "character": 14
} }
} }
} }

View file

@ -6,7 +6,7 @@
"uri": "file:///a/file.ts", "uri": "file:///a/file.ts",
"languageId": "typescript", "languageId": "typescript",
"version": 1, "version": 1,
"text": "console.log(\"hello deno!\");\n" "text": "console.log(Date.now());\n"
} }
} }
} }

View file

@ -1,6 +1,6 @@
{ {
"jsonrpc": "2.0", "jsonrpc": "2.0",
"id": 4, "id": 5,
"method": "textDocument/hover", "method": "textDocument/hover",
"params": { "params": {
"textDocument": { "textDocument": {

View file

@ -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
}
}
}

View file

@ -1,10 +1,10 @@
{ {
"jsonrpc": "2.0", "jsonrpc": "2.0",
"id": 2, "id": 4,
"method": "deno/virtualTextDocument", "method": "deno/virtualTextDocument",
"params": { "params": {
"textDocument": { "textDocument": {
"uri": "deno:/asset//lib.es2015.symbol.wellknown.d.ts" "uri": "deno:/asset//lib.deno.shared_globals.d.ts"
} }
} }
} }

View file

@ -383,7 +383,10 @@ fn resolve(state: &mut State, args: Value) -> Result<Value, AnyError> {
} else { } else {
resolved_specifier.to_string() 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 // 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 // the source file in the graph, so we will return a fake module to