mirror of
https://github.com/danbulant/oxc
synced 2026-05-25 04:42:10 +00:00
The rolldown plugin hook could return an object map, cast it to string at node, and decode it has unnecessary json overhead at rust. So here export an new function to let rolldown could using `JSONSourceMap` to generate `Sourcemap`.
169 lines
6.7 KiB
Rust
169 lines
6.7 KiB
Rust
/// Port from https://github.com/getsentry/rust-sourcemap/blob/master/src/decoder.rs
|
||
/// It is a helper for decode vlq soucemap string to `SourceMap`.
|
||
use crate::error::{Error, Result};
|
||
use crate::{SourceMap, Token};
|
||
|
||
#[derive(serde::Deserialize, Default)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct JSONSourceMap {
|
||
// An optional name of the generated code that this source map is associated with.
|
||
file: Option<String>,
|
||
// A string with the encoded mapping data.
|
||
mappings: Option<String>,
|
||
// An optional source root, useful for relocating source files on a server or removing repeated values in the “sources” entry. This value is prepended to the individual entries in the “source” field.
|
||
source_root: Option<String>,
|
||
// A list of original sources used by the “mappings” entry.
|
||
sources: Option<Vec<Option<String>>>,
|
||
// An optional list of source content, useful when the “source” can’t be hosted. The contents are listed in the same order as the sources in line 5. “null” may be used if some original sources should be retrieved by name.
|
||
sources_content: Option<Vec<Option<String>>>,
|
||
// A list of symbol names used by the “mappings” entry.
|
||
names: Option<Vec<String>>,
|
||
}
|
||
|
||
pub fn decode(json: JSONSourceMap) -> Result<SourceMap> {
|
||
let file = json.file.map(Into::into);
|
||
let names =
|
||
json.names.map(|v| v.into_iter().map(Into::into).collect::<Vec<_>>()).unwrap_or_default();
|
||
let source_root = json.source_root.map(Into::into);
|
||
let sources = json
|
||
.sources
|
||
.map(|v| v.into_iter().map(Option::unwrap_or_default).map(Into::into).collect::<Vec<_>>())
|
||
.unwrap_or_default();
|
||
let source_contents = json
|
||
.sources_content
|
||
.map(|v| v.into_iter().map(Option::unwrap_or_default).map(Into::into).collect::<Vec<_>>());
|
||
let tokens = decode_mapping(&json.mappings.unwrap_or_default(), names.len(), sources.len())?;
|
||
Ok(SourceMap::new(file, names, source_root, sources, source_contents, tokens, None))
|
||
}
|
||
|
||
pub fn decode_from_string(value: &str) -> Result<SourceMap> {
|
||
decode(serde_json::from_str(value)?)
|
||
}
|
||
|
||
#[allow(clippy::cast_possible_truncation)]
|
||
fn decode_mapping(mapping: &str, names_len: usize, sources_len: usize) -> Result<Vec<Token>> {
|
||
let mut tokens = vec![];
|
||
|
||
let mut dst_col;
|
||
let mut src_id = 0;
|
||
let mut src_line = 0;
|
||
let mut src_col = 0;
|
||
let mut name_id = 0;
|
||
let mut nums = Vec::with_capacity(6);
|
||
|
||
for (dst_line, line) in mapping.split(';').enumerate() {
|
||
if line.is_empty() {
|
||
continue;
|
||
}
|
||
|
||
dst_col = 0;
|
||
|
||
for segment in line.split(',') {
|
||
if segment.is_empty() {
|
||
continue;
|
||
}
|
||
|
||
nums.clear();
|
||
parse_vlq_segment_into(segment, &mut nums)?;
|
||
dst_col = (i64::from(dst_col) + nums[0]) as u32;
|
||
|
||
let mut src = !0;
|
||
let mut name = !0;
|
||
|
||
if nums.len() > 1 {
|
||
if nums.len() != 4 && nums.len() != 5 {
|
||
return Err(Error::BadSegmentSize(nums.len() as u32));
|
||
}
|
||
src_id = (i64::from(src_id) + nums[1]) as u32;
|
||
if src_id >= sources_len as u32 {
|
||
return Err(Error::BadSourceReference(src_id));
|
||
}
|
||
|
||
src = src_id;
|
||
src_line = (i64::from(src_line) + nums[2]) as u32;
|
||
src_col = (i64::from(src_col) + nums[3]) as u32;
|
||
|
||
if nums.len() > 4 {
|
||
name_id = (i64::from(name_id) + nums[4]) as u32;
|
||
if name_id >= names_len as u32 {
|
||
return Err(Error::BadNameReference(name_id));
|
||
}
|
||
name = name_id;
|
||
}
|
||
}
|
||
|
||
tokens.push(Token::new(
|
||
dst_line as u32,
|
||
dst_col,
|
||
src_line,
|
||
src_col,
|
||
if src == !0 { None } else { Some(src) },
|
||
if name == !0 { None } else { Some(name) },
|
||
));
|
||
}
|
||
}
|
||
|
||
Ok(tokens)
|
||
}
|
||
|
||
#[rustfmt::skip]
|
||
const B64: [i8; 256] = [ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1 - 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, ];
|
||
|
||
fn parse_vlq_segment_into(segment: &str, rv: &mut Vec<i64>) -> Result<()> {
|
||
let mut cur = 0;
|
||
let mut shift = 0;
|
||
|
||
for c in segment.bytes() {
|
||
let enc = i64::from(B64[c as usize]);
|
||
let val = enc & 0b11111;
|
||
let cont = enc >> 5;
|
||
cur += val.checked_shl(shift).ok_or(Error::VlqOverflow)?;
|
||
shift += 5;
|
||
|
||
if cont == 0 {
|
||
let sign = cur & 1;
|
||
cur >>= 1;
|
||
if sign != 0 {
|
||
cur = -cur;
|
||
}
|
||
rv.push(cur);
|
||
cur = 0;
|
||
shift = 0;
|
||
}
|
||
}
|
||
|
||
if cur != 0 || shift != 0 {
|
||
Err(Error::VlqLeftover)
|
||
} else if rv.is_empty() {
|
||
Err(Error::VlqNoValues)
|
||
} else {
|
||
Ok(())
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn test_decode_sourcemap() {
|
||
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();
|
||
assert_eq!(sm.get_source_root(), Some("x"));
|
||
let mut iter = sm.get_source_view_tokens().filter(|token| token.get_name_id().is_some());
|
||
assert_eq!(iter.next().unwrap().to_tuple(), (Some("coolstuff.js"), 0, 4, Some("x")));
|
||
assert_eq!(iter.next().unwrap().to_tuple(), (Some("coolstuff.js"), 1, 4, Some("x")));
|
||
assert_eq!(iter.next().unwrap().to_tuple(), (Some("coolstuff.js"), 2, 2, Some("alert")));
|
||
assert!(iter.next().is_none());
|
||
}
|
||
|
||
#[test]
|
||
fn test_decode_sourcemap_optional_filed() {
|
||
let input = r#"{
|
||
"sources": [null],
|
||
"sourcesContent": [null]
|
||
}"#;
|
||
SourceMap::from_json_string(input).expect("should success");
|
||
}
|