mirror of
https://github.com/denoland/deno.git
synced 2024-11-25 15:29:32 -05:00
175 lines
5.8 KiB
Rust
175 lines
5.8 KiB
Rust
|
// 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);
|
||
|
}
|
||
|
}
|