diff options
author | David Sherret <dsherret@users.noreply.github.com> | 2024-06-05 11:04:16 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-06-05 17:04:16 +0200 |
commit | 7ed90a20d04982ae15a52ae2378cbffd4b6839df (patch) | |
tree | 3297d6f7227fbf1cf80e17a2a376ef4dfa52e6ad /cli/util/text_encoding.rs | |
parent | 0544d60012006b1c7799d8b6eafacec9567901ad (diff) |
fix: better handling of npm resolution occurring on workers (#24094)
Closes https://github.com/denoland/deno/issues/24063
Diffstat (limited to 'cli/util/text_encoding.rs')
-rw-r--r-- | cli/util/text_encoding.rs | 167 |
1 files changed, 140 insertions, 27 deletions
diff --git a/cli/util/text_encoding.rs b/cli/util/text_encoding.rs index 25d827eb6..d2e0832c9 100644 --- a/cli/util/text_encoding.rs +++ b/cli/util/text_encoding.rs @@ -1,45 +1,133 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +use std::ops::Range; + use base64::prelude::BASE64_STANDARD; use base64::Engine; -use deno_core::ModuleCodeString; +use deno_core::ModuleSourceCode; static SOURCE_MAP_PREFIX: &[u8] = b"//# sourceMappingURL=data:application/json;base64,"; -pub fn source_map_from_code(code: &ModuleCodeString) -> Option<Vec<u8>> { - let bytes = code.as_bytes(); - let last_line = bytes.rsplit(|u| *u == b'\n').next()?; - if last_line.starts_with(SOURCE_MAP_PREFIX) { - let input = last_line.split_at(SOURCE_MAP_PREFIX.len()).1; - let decoded_map = BASE64_STANDARD - .decode(input) - .expect("Unable to decode source map from emitted file."); - Some(decoded_map) - } else { - None - } +pub fn source_map_from_code(code: &[u8]) -> Option<Vec<u8>> { + let range = find_source_map_range(code)?; + let source_map_range = &code[range]; + let input = source_map_range.split_at(SOURCE_MAP_PREFIX.len()).1; + let decoded_map = BASE64_STANDARD.decode(input).ok()?; + Some(decoded_map) } /// Truncate the source code before the source map. -pub fn code_without_source_map(mut code: ModuleCodeString) -> ModuleCodeString { - let bytes = code.as_bytes(); - for i in (0..bytes.len()).rev() { - if bytes[i] == b'\n' { - if bytes[i + 1..].starts_with(SOURCE_MAP_PREFIX) { - code.truncate(i + 1); +pub fn code_without_source_map(code: ModuleSourceCode) -> ModuleSourceCode { + use deno_core::ModuleCodeBytes; + + match code { + ModuleSourceCode::String(mut code) => { + if let Some(range) = find_source_map_range(code.as_bytes()) { + code.truncate(range.start); } - return code; + ModuleSourceCode::String(code) } + ModuleSourceCode::Bytes(code) => { + if let Some(range) = find_source_map_range(code.as_bytes()) { + let source_map_index = range.start; + ModuleSourceCode::Bytes(match code { + ModuleCodeBytes::Static(bytes) => { + ModuleCodeBytes::Static(&bytes[..source_map_index]) + } + ModuleCodeBytes::Boxed(bytes) => { + // todo(dsherret): should be possible without cloning + ModuleCodeBytes::Boxed( + bytes[..source_map_index].to_vec().into_boxed_slice(), + ) + } + ModuleCodeBytes::Arc(bytes) => ModuleCodeBytes::Boxed( + bytes[..source_map_index].to_vec().into_boxed_slice(), + ), + }) + } else { + ModuleSourceCode::Bytes(code) + } + } + } +} + +fn find_source_map_range(code: &[u8]) -> Option<Range<usize>> { + fn last_non_blank_line_range(code: &[u8]) -> Option<Range<usize>> { + let mut hit_non_whitespace = false; + let mut range_end = code.len(); + for i in (0..code.len()).rev() { + match code[i] { + b' ' | b'\t' => { + if !hit_non_whitespace { + range_end = i; + } + } + b'\n' | b'\r' => { + if hit_non_whitespace { + return Some(i + 1..range_end); + } + range_end = i; + } + _ => { + hit_non_whitespace = true; + } + } + } + None + } + + let range = last_non_blank_line_range(code)?; + if code[range.start..range.end].starts_with(SOURCE_MAP_PREFIX) { + Some(range) + } else { + None } - code } #[cfg(test)] mod tests { + use std::sync::Arc; + + use deno_core::ModuleCodeBytes; + use deno_core::ModuleCodeString; + use super::*; #[test] + fn test_source_map_from_code() { + let to_string = + |bytes: Vec<u8>| -> String { String::from_utf8(bytes).unwrap() }; + assert_eq!( + source_map_from_code( + b"test\n//# sourceMappingURL=data:application/json;base64,dGVzdGluZ3Rlc3Rpbmc=", + ).map(to_string), + Some("testingtesting".to_string()) + ); + assert_eq!( + source_map_from_code( + b"test\n//# sourceMappingURL=data:application/json;base64,dGVzdGluZ3Rlc3Rpbmc=\n \n", + ).map(to_string), + Some("testingtesting".to_string()) + ); + assert_eq!( + source_map_from_code( + b"test\n//# sourceMappingURL=data:application/json;base64,dGVzdGluZ3Rlc3Rpbmc=\n test\n", + ), + None + ); + assert_eq!( + source_map_from_code( + b"\"use strict\"; + +throw new Error(\"Hello world!\"); +//# sourceMappingURL=data:application/json;base64,{", + ), + None + ); + } + + #[test] fn test_source_without_source_map() { run_test("", ""); run_test("\n", "\n"); @@ -62,14 +150,39 @@ mod tests { "\n//# sourceMappingURL=data:application/json;base64,test", "\n", ); + run_test( + "test\n//# sourceMappingURL=data:application/json;base64,test\n\n", + "test\n", + ); + run_test( + "test\n//# sourceMappingURL=data:application/json;base64,test\n \n ", + "test\n", + ); fn run_test(input: &'static str, output: &'static str) { - assert_eq!( - code_without_source_map(ModuleCodeString::from_static(input)) - .as_str() - .to_owned(), - output - ); + let forms = [ + ModuleSourceCode::String(ModuleCodeString::from_static(input)), + ModuleSourceCode::String({ + let text: Arc<str> = input.into(); + text.into() + }), + ModuleSourceCode::String({ + let text: String = input.into(); + text.into() + }), + ModuleSourceCode::Bytes(ModuleCodeBytes::Static(input.as_bytes())), + ModuleSourceCode::Bytes(ModuleCodeBytes::Boxed( + input.as_bytes().to_vec().into_boxed_slice(), + )), + ModuleSourceCode::Bytes(ModuleCodeBytes::Arc( + input.as_bytes().to_vec().into(), + )), + ]; + for form in forms { + let result = code_without_source_map(form); + let bytes = result.as_bytes(); + assert_eq!(bytes, output.as_bytes()); + } } } } |