fix(sourcemap): using serde_json::to_string to quote sourcemap string (#2889)

This commit is contained in:
underfin 2024-04-03 12:14:45 +08:00 committed by GitHub
parent 21a5e4433f
commit 28fae2e80a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 60 additions and 17 deletions

View file

@ -33,7 +33,7 @@ fn main() -> std::io::Result<()> {
.build(&ret.program);
if let Some(source_map) = source_map {
let result = source_map.to_json_string();
let result = source_map.to_json_string().unwrap();
let hash = BASE64_STANDARD.encode(format!(
"{}\0{}{}\0{}",
source_text.len(),

View file

@ -130,5 +130,5 @@ fn test_concat_sourcemap_builder() {
])
);
assert_eq!(sm.to_json_string(), sm.to_json_string());
assert_eq!(sm.to_json_string().unwrap(), sm.to_json_string().unwrap());
}

View file

@ -1,3 +1,4 @@
use crate::error::{Error, Result};
/// Port from https://github.com/getsentry/rust-sourcemap/blob/master/src/encoder.rs
/// It is a helper for encode `SourceMap` to vlq sourcemap string, but here some different.
/// - Quote `source_content` at parallel.
@ -5,7 +6,9 @@
use crate::{token::TokenChunk, SourceMap, Token};
use rayon::prelude::*;
pub fn encode(sourcemap: &SourceMap) -> String {
// Here using `serde_json::to_string` to serialization `names/source_contents/sources`.
// It will escape the string to avoid invalid JSON string.
pub fn encode(sourcemap: &SourceMap) -> Result<String> {
let mut buf = String::new();
buf.push_str("{\"version\":3,");
if let Some(file) = sourcemap.get_file() {
@ -14,20 +17,35 @@ pub fn encode(sourcemap: &SourceMap) -> String {
buf.push_str("\",");
}
buf.push_str("\"names\":[");
buf.push_str(&sourcemap.names.iter().map(|x| format!("{x:?}")).collect::<Vec<_>>().join(","));
let names = sourcemap
.names
.iter()
.map(|x| serde_json::to_string(x.as_ref()))
.collect::<std::result::Result<Vec<_>, serde_json::Error>>()
.map_err(Error::from)?;
buf.push_str(&names.join(","));
buf.push_str("],\"sources\":[");
buf.push_str(&sourcemap.sources.iter().map(|x| format!("{x:?}")).collect::<Vec<_>>().join(","));
let sources = sourcemap
.sources
.iter()
.map(|x| serde_json::to_string(x.as_ref()))
.collect::<std::result::Result<Vec<_>, serde_json::Error>>()
.map_err(Error::from)?;
buf.push_str(&sources.join(","));
// Quote `source_content` at parallel.
if let Some(source_contents) = &sourcemap.source_contents {
buf.push_str("],\"sourcesContent\":[");
buf.push_str(
&source_contents.par_iter().map(|x| format!("{x:?}")).collect::<Vec<_>>().join(","),
);
let quote_source_contents = source_contents
.par_iter()
.map(|x| serde_json::to_string(x.as_ref()))
.collect::<std::result::Result<Vec<_>, serde_json::Error>>()
.map_err(Error::from)?;
buf.push_str(&quote_source_contents.join(","));
}
buf.push_str("],\"mappings\":\"");
buf.push_str(&serialize_sourcemap_mappings(sourcemap));
buf.push_str("\"}");
buf
Ok(buf)
}
#[allow(clippy::cast_possible_truncation)]
@ -130,9 +148,26 @@ fn test_encode() {
"mappings": "AAAA,GAAIA,GAAI,EACR,IAAIA,GAAK,EAAG,CACVC,MAAM"
}"#;
let sm = SourceMap::from_json_string(input).unwrap();
let sm2 = SourceMap::from_json_string(&sm.to_json_string()).unwrap();
let sm2 = SourceMap::from_json_string(&sm.to_json_string().unwrap()).unwrap();
for (tok1, tok2) in sm.get_tokens().zip(sm2.get_tokens()) {
assert_eq!(tok1, tok2);
}
}
#[test]
fn test_encode_escape_string() {
// '\0' should be escaped.
let sm = SourceMap::new(
None,
vec!["\0".into()],
vec!["\0".into()],
Some(vec!["\0".into()]),
vec![],
None,
);
assert_eq!(
sm.to_json_string().unwrap(),
r#"{"version":3,"names":["\u0000"],"sources":["\u0000"],"sourcesContent":["\u0000"],"mappings":""}"#
);
}

View file

@ -31,21 +31,29 @@ impl SourceMap {
}
/// Convert `SourceMap` to vlq sourcemap string.
#[allow(clippy::missing_errors_doc)]
/// # Errors
///
/// The `serde_json` deserialize Error.
pub fn from_json_string(value: &str) -> Result<Self> {
decode(value)
}
/// Convert the vlq sourcemap string to `SourceMap`.
pub fn to_json_string(&self) -> String {
/// # Errors
///
/// The `serde_json` serialization Error.
pub fn to_json_string(&self) -> Result<String> {
encode(self)
}
/// Convert `SourceMap` to vlq sourcemap data url.
pub fn to_data_url(&self) -> String {
/// # Errors
///
/// The `serde_json` serialization Error.
pub fn to_data_url(&self) -> Result<String> {
let base_64_str =
base64_simd::Base64::STANDARD.encode_to_boxed_str(self.to_json_string().as_bytes());
format!("data:application/json;charset=utf-8;base64,{base_64_str}")
base64_simd::Base64::STANDARD.encode_to_boxed_str(self.to_json_string()?.as_bytes());
Ok(format!("data:application/json;charset=utf-8;base64,{base_64_str}"))
}
pub fn get_file(&self) -> Option<&str> {

View file

@ -85,5 +85,5 @@ fn test_sourcemap_builder() {
let expected =
r#"{"version":3,"names":["x"],"sources":["baz.js"],"sourcesContent":[""],"mappings":""}"#;
assert_eq!(expected, sm.to_json_string());
assert_eq!(expected, sm.to_json_string().unwrap());
}

View file

@ -31,7 +31,7 @@ fn bench_sourcemap(criterion: &mut Criterion) {
for i in 0..1 {
concat_sourcemap_builder.add_sourcemap(&sourcemap, line * i);
}
concat_sourcemap_builder.into_sourcemap().to_json_string();
concat_sourcemap_builder.into_sourcemap().to_json_string().unwrap();
}
});
});