mirror of
https://github.com/denoland/deno.git
synced 2024-12-23 15:49:44 -05:00
fix(lsp): handle data URLs properly (#9522)
Fixes #9514 Co-authored-by: Ryan Dahl <ry@tinyclouds.org>
This commit is contained in:
parent
78e34d4912
commit
2225e83da2
16 changed files with 374 additions and 125 deletions
|
@ -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;
|
||||
}
|
||||
```
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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))
|
||||
};
|
||||
|
|
|
@ -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(¶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<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(¶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<bool> {
|
||||
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<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(¶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,
|
||||
|
|
|
@ -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();
|
||||
|
|
104
cli/lsp/tsc.rs
104
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);
|
||||
|
|
174
cli/lsp/urls.rs
Normal file
174
cli/lsp/urls.rs
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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`
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 4,
|
||||
"id": 5,
|
||||
"method": "textDocument/hover",
|
||||
"params": {
|
||||
"textDocument": {
|
17
cli/tests/lsp/references_request_asset.json
Normal file
17
cli/tests/lsp/references_request_asset.json
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue