mirror of
https://github.com/danbulant/oxc
synced 2026-05-21 13:18:59 +00:00
224 lines
7.6 KiB
Rust
224 lines
7.6 KiB
Rust
#[cfg(feature = "concurrent")]
|
|
use rayon::prelude::*;
|
|
|
|
use crate::error::{Error, Result};
|
|
use crate::JSONSourceMap;
|
|
/// 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.
|
|
/// - If you using `ConcatSourceMapBuilder`, serialize `tokens` to vlq `mappings` at parallel.
|
|
use crate::{token::TokenChunk, SourceMap, Token};
|
|
|
|
pub fn encode(sourcemap: &SourceMap) -> JSONSourceMap {
|
|
JSONSourceMap {
|
|
file: sourcemap.get_file().map(ToString::to_string),
|
|
mappings: Some(serialize_sourcemap_mappings(sourcemap)),
|
|
source_root: sourcemap.get_source_root().map(ToString::to_string),
|
|
sources: Some(sourcemap.sources.iter().map(ToString::to_string).map(Some).collect()),
|
|
sources_content: sourcemap
|
|
.source_contents
|
|
.as_ref()
|
|
.map(|x| x.iter().map(ToString::to_string).map(Some).collect()),
|
|
names: Some(sourcemap.names.iter().map(ToString::to_string).collect()),
|
|
}
|
|
}
|
|
|
|
// 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_to_string(sourcemap: &SourceMap) -> Result<String> {
|
|
let mut buf = String::new();
|
|
buf.push_str("{\"version\":3,");
|
|
if let Some(file) = sourcemap.get_file() {
|
|
buf.push_str("\"file\":\"");
|
|
buf.push_str(file);
|
|
buf.push_str("\",");
|
|
}
|
|
if let Some(source_root) = sourcemap.get_source_root() {
|
|
buf.push_str("\"sourceRoot\":\"");
|
|
buf.push_str(source_root);
|
|
buf.push_str("\",");
|
|
}
|
|
buf.push_str("\"names\":[");
|
|
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\":[");
|
|
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\":[");
|
|
cfg_if::cfg_if! {
|
|
if #[cfg(feature = "concurrent")] {
|
|
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)?;
|
|
} else {
|
|
let quote_source_contents = source_contents
|
|
.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("e_source_contents.join(","));
|
|
}
|
|
if let Some(x_google_ignore_list) = &sourcemap.x_google_ignore_list {
|
|
buf.push_str("],\"x_google_ignoreList\":[");
|
|
let x_google_ignore_list =
|
|
x_google_ignore_list.iter().map(ToString::to_string).collect::<Vec<_>>();
|
|
buf.push_str(&x_google_ignore_list.join(","));
|
|
}
|
|
buf.push_str("],\"mappings\":\"");
|
|
buf.push_str(&serialize_sourcemap_mappings(sourcemap));
|
|
buf.push_str("\"}");
|
|
Ok(buf)
|
|
}
|
|
|
|
#[allow(clippy::cast_possible_truncation)]
|
|
fn serialize_sourcemap_mappings(sm: &SourceMap) -> String {
|
|
sm.token_chunks.as_ref().map_or_else(
|
|
|| {
|
|
serialize_mappings(
|
|
&sm.tokens,
|
|
&TokenChunk::new(0, sm.tokens.len() as u32, 0, 0, 0, 0, 0, 0),
|
|
)
|
|
},
|
|
|token_chunks| {
|
|
// Serialize `tokens` to vlq `mappings` at parallel.
|
|
cfg_if::cfg_if! {
|
|
if #[cfg(feature = "concurrent")] {
|
|
token_chunks
|
|
.par_iter()
|
|
.map(|token_chunk| serialize_mappings(&sm.tokens, token_chunk))
|
|
.collect::<String>()
|
|
} else {
|
|
token_chunks
|
|
.iter()
|
|
.map(|token_chunk| serialize_mappings(&sm.tokens, token_chunk))
|
|
.collect::<String>()
|
|
}
|
|
}
|
|
},
|
|
)
|
|
}
|
|
|
|
fn serialize_mappings(tokens: &[Token], token_chunk: &TokenChunk) -> String {
|
|
let mut rv = String::new();
|
|
|
|
let TokenChunk {
|
|
start,
|
|
end,
|
|
mut prev_dst_line,
|
|
mut prev_dst_col,
|
|
mut prev_src_line,
|
|
mut prev_src_col,
|
|
mut prev_name_id,
|
|
mut prev_source_id,
|
|
} = *token_chunk;
|
|
|
|
for (idx, token) in tokens[start as usize..end as usize].iter().enumerate() {
|
|
let index = start as usize + idx;
|
|
if token.get_dst_line() != prev_dst_line {
|
|
prev_dst_col = 0;
|
|
while token.get_dst_line() != prev_dst_line {
|
|
rv.push(';');
|
|
prev_dst_line += 1;
|
|
}
|
|
} else if index > 0 {
|
|
if Some(token) == tokens.get(index - 1) {
|
|
continue;
|
|
}
|
|
rv.push(',');
|
|
}
|
|
|
|
encode_vlq_diff(&mut rv, token.get_dst_col(), prev_dst_col);
|
|
prev_dst_col = token.get_dst_col();
|
|
|
|
if let Some(source_id) = token.get_source_id() {
|
|
encode_vlq_diff(&mut rv, source_id, prev_source_id);
|
|
prev_source_id = source_id;
|
|
encode_vlq_diff(&mut rv, token.get_src_line(), prev_src_line);
|
|
prev_src_line = token.get_src_line();
|
|
encode_vlq_diff(&mut rv, token.get_src_col(), prev_src_col);
|
|
prev_src_col = token.get_src_col();
|
|
if let Some(name_id) = token.get_name_id() {
|
|
encode_vlq_diff(&mut rv, name_id, prev_name_id);
|
|
prev_name_id = name_id;
|
|
}
|
|
}
|
|
}
|
|
|
|
rv
|
|
}
|
|
|
|
#[inline]
|
|
fn encode_vlq_diff(out: &mut String, a: u32, b: u32) {
|
|
encode_vlq(out, i64::from(a) - i64::from(b));
|
|
}
|
|
|
|
const B64_CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
|
|
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
|
|
fn encode_vlq(out: &mut String, num: i64) {
|
|
let mut num = if num < 0 { ((-num) << 1) + 1 } else { num << 1 };
|
|
|
|
loop {
|
|
let mut digit = num & 0b11111;
|
|
num >>= 5;
|
|
if num > 0 {
|
|
digit |= 1 << 5;
|
|
}
|
|
out.push(B64_CHARS[digit as usize] as char);
|
|
if num == 0 {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_encode() {
|
|
let input = r#"{
|
|
"version": 3,
|
|
"sources": ["coolstuff.js"],
|
|
"sourceRoot": "x",
|
|
"names": ["x","alert"],
|
|
"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()).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 mut sm = SourceMap::new(
|
|
None,
|
|
vec!["\0".into()],
|
|
None,
|
|
vec!["\0".into()],
|
|
Some(vec!["\0".into()]),
|
|
vec![],
|
|
None,
|
|
);
|
|
sm.set_x_google_ignore_list(vec![0]);
|
|
assert_eq!(
|
|
sm.to_json_string().unwrap(),
|
|
r#"{"version":3,"names":["\u0000"],"sources":["\u0000"],"sourcesContent":["\u0000"],"x_google_ignoreList":[0],"mappings":""}"#
|
|
);
|
|
}
|