From 84f874715763df71bb3bbf77f0714f8afdc17bb3 Mon Sep 17 00:00:00 2001 From: Satya Rohith Date: Mon, 13 Sep 2021 09:49:23 +0530 Subject: [PATCH] fix(lsp): support data urls in `deno.importMap` option (#11397) --- cli/file_fetcher.rs | 28 ++++++--- cli/lsp/language_server.rs | 27 +++++--- cli/standalone.rs | 20 +----- cli/tests/integration/lsp_tests.rs | 26 ++++++++ .../lsp/initialize_params_import_map.json | 62 +++++++++++++++++++ 5 files changed, 127 insertions(+), 36 deletions(-) create mode 100644 cli/tests/testdata/lsp/initialize_params_import_map.json diff --git a/cli/file_fetcher.rs b/cli/file_fetcher.rs index 1e4c5d453b..3e259ed54a 100644 --- a/cli/file_fetcher.rs +++ b/cli/file_fetcher.rs @@ -143,6 +143,24 @@ fn fetch_local(specifier: &ModuleSpecifier) -> Result { }) } +/// Returns the decoded body and content-type of a provided +/// data URL. +pub fn get_source_from_data_url( + specifier: &ModuleSpecifier, +) -> Result<(String, String), AnyError> { + let data_url = DataUrl::process(specifier.as_str()) + .map_err(|e| uri_error(format!("{:?}", e)))?; + let mime = data_url.mime_type(); + let charset = mime.get_parameter("charset").map(|v| v.to_string()); + let (bytes, _) = data_url + .decode_to_vec() + .map_err(|e| uri_error(format!("{:?}", e)))?; + Ok(( + strip_shebang(get_source_from_bytes(bytes, charset)?), + format!("{}", mime), + )) +} + /// Given a vector of bytes and optionally a charset, decode the bytes to a /// string. pub fn get_source_from_bytes( @@ -340,15 +358,7 @@ impl FileFetcher { )); } - let data_url = DataUrl::process(specifier.as_str()) - .map_err(|e| uri_error(format!("{:?}", e)))?; - let mime = data_url.mime_type(); - let charset = mime.get_parameter("charset").map(|v| v.to_string()); - let (bytes, _) = data_url - .decode_to_vec() - .map_err(|e| uri_error(format!("{:?}", e)))?; - let source = strip_shebang(get_source_from_bytes(bytes, charset)?); - let content_type = format!("{}", mime); + let (source, content_type) = get_source_from_data_url(specifier)?; let (media_type, _) = map_content_type(specifier, Some(content_type.clone())); diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index 93d24f9bd8..3f1bfc923a 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -56,6 +56,7 @@ use super::urls; use crate::config_file::ConfigFile; use crate::config_file::TsConfig; use crate::deno_dir; +use crate::file_fetcher::get_source_from_data_url; use crate::fs_util; use crate::logger; use crate::tools::fmt::format_file; @@ -474,6 +475,10 @@ impl Inner { let import_map_url = if let Ok(url) = Url::from_file_path(import_map_str) { Ok(url) + } else if import_map_str.starts_with("data:") { + Url::parse(import_map_str).map_err(|_| { + anyhow!("Bad data url for import map: {:?}", import_map_str) + }) } else if let Some(root_uri) = &maybe_root_uri { let root_path = root_uri .to_file_path() @@ -488,21 +493,25 @@ impl Inner { import_map_str )) }?; - let import_map_path = import_map_url.to_file_path().map_err(|_| { - anyhow!("Cannot convert \"{}\" into a file path.", import_map_url) - })?; - info!( - " Resolved import map: \"{}\"", - import_map_path.to_string_lossy() - ); - let import_map_json = + + let import_map_json = if import_map_url.scheme() == "data" { + get_source_from_data_url(&import_map_url)?.0 + } else { + let import_map_path = import_map_url.to_file_path().map_err(|_| { + anyhow!("Cannot convert \"{}\" into a file path.", import_map_url) + })?; + info!( + " Resolved import map: \"{}\"", + import_map_path.to_string_lossy() + ); fs::read_to_string(import_map_path).await.map_err(|err| { anyhow!( "Failed to load the import map at: {}. [{}]", import_map_url, err ) - })?; + })? + }; let import_map = ImportMap::from_json(&import_map_url.to_string(), &import_map_json)?; self.maybe_import_map_uri = Some(import_map_url); diff --git a/cli/standalone.rs b/cli/standalone.rs index 800dca4cbe..a3ace49f7a 100644 --- a/cli/standalone.rs +++ b/cli/standalone.rs @@ -1,16 +1,13 @@ // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. use crate::colors; -use crate::file_fetcher::get_source_from_bytes; -use crate::file_fetcher::strip_shebang; +use crate::file_fetcher::get_source_from_data_url; use crate::flags::Flags; use crate::ops; use crate::program_state::ProgramState; use crate::version; -use data_url::DataUrl; use deno_core::error::anyhow; use deno_core::error::type_error; -use deno_core::error::uri_error; use deno_core::error::AnyError; use deno_core::error::Context; use deno_core::futures::FutureExt; @@ -123,19 +120,6 @@ fn read_string_slice( Ok(string) } -fn get_source_from_data_url( - specifier: &ModuleSpecifier, -) -> Result { - let data_url = DataUrl::process(specifier.as_str()) - .map_err(|e| uri_error(format!("{:?}", e)))?; - let mime = data_url.mime_type(); - let charset = mime.get_parameter("charset").map(|v| v.to_string()); - let (bytes, _) = data_url - .decode_to_vec() - .map_err(|e| uri_error(format!("{:?}", e)))?; - Ok(strip_shebang(get_source_from_bytes(bytes, charset)?)) -} - const SPECIFIER: &str = "file://$deno$/bundle.js"; struct EmbeddedModuleLoader(String); @@ -169,7 +153,7 @@ impl ModuleLoader for EmbeddedModuleLoader { ) -> Pin> { let module_specifier = module_specifier.clone(); let is_data_uri = get_source_from_data_url(&module_specifier).ok(); - let code = if let Some(ref source) = is_data_uri { + let code = if let Some((ref source, _)) = is_data_uri { source.to_string() } else { self.0.to_string() diff --git a/cli/tests/integration/lsp_tests.rs b/cli/tests/integration/lsp_tests.rs index 762d8bb94b..1b8d351083 100644 --- a/cli/tests/integration/lsp_tests.rs +++ b/cli/tests/integration/lsp_tests.rs @@ -316,6 +316,32 @@ fn lsp_import_map() { shutdown(&mut client); } +#[test] +fn lsp_import_map_data_url() { + let mut client = init("initialize_params_import_map.json"); + let diagnostics = did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "import example from \"example\";\n" + } + }), + ); + + let mut diagnostics = diagnostics.into_iter().flat_map(|x| x.diagnostics); + // This indicates that the import map from initialize_params_import_map.json + // is applied correctly. + assert!(diagnostics.any(|diagnostic| diagnostic.code + == Some(lsp::NumberOrString::String("no-cache".to_string())) + && diagnostic + .message + .contains("https://deno.land/x/example/mod.ts"))); + shutdown(&mut client); +} + #[test] fn lsp_hover() { let mut client = init("initialize_params.json"); diff --git a/cli/tests/testdata/lsp/initialize_params_import_map.json b/cli/tests/testdata/lsp/initialize_params_import_map.json new file mode 100644 index 0000000000..2ba7d28b05 --- /dev/null +++ b/cli/tests/testdata/lsp/initialize_params_import_map.json @@ -0,0 +1,62 @@ +{ + "processId": 0, + "clientInfo": { + "name": "test-harness", + "version": "1.0.0" + }, + "rootUri": null, + "initializationOptions": { + "enable": true, + "codeLens": { + "implementations": true, + "references": true + }, + "importMap": "data:application/json;utf8,{\"imports\": { \"example\": \"https://deno.land/x/example/mod.ts\" }}", + "lint": true, + "suggest": { + "autoImports": true, + "completeFunctionCalls": false, + "names": true, + "paths": true, + "imports": { + "hosts": { + "http://localhost:4545/": false + } + } + }, + "unstable": false + }, + "capabilities": { + "textDocument": { + "codeAction": { + "codeActionLiteralSupport": { + "codeActionKind": { + "valueSet": [ + "quickfix" + ] + } + }, + "isPreferredSupport": true, + "dataSupport": true, + "resolveSupport": { + "properties": [ + "edit" + ] + } + }, + "foldingRange": { + "lineFoldingOnly": true + }, + "synchronization": { + "dynamicRegistration": true, + "willSave": true, + "willSaveWaitUntil": true, + "didSave": true + } + }, + "workspace": { + "configuration": true, + "workspaceFolders": true + } + } +}