From a3e5686fa6108484a5db0520854c5c045a40d6fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Thu, 5 Jun 2025 15:31:08 -0700 Subject: [PATCH 01/12] perf: Add a lazy variant (#1) In case of most transforms (for ECMAScript), the original input source map is used only to adjust mappings, and other fields are kept intact. The _lazy_ variant source maps are source maps used only to call `adjust_mappings`. It does not deserialize everything. Instead, it only deserializes the fields required to calculate the new mappings. This is done in the name of the performance, and the justification for introducing a new type is at https://github.com/vercel/next.js/pull/80177#issuecomment-2941464441 image --- Cargo.toml | 4 +- src/decoder.rs | 2 +- src/encoder.rs | 4 +- src/lazy/mod.rs | 622 ++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + src/sourceview.rs | 2 +- src/types.rs | 238 +++++++++--------- 7 files changed, 752 insertions(+), 121 deletions(-) create mode 100644 src/lazy/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 25936d6..20fac8b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,8 +24,8 @@ all-features = true [dependencies] url = "2.1.1" -serde = { version = "1.0.104", features = ["derive"] } -serde_json = "1.0.48" +serde = { version = "1.0.104", features = ["derive", "rc"] } +serde_json = { version = "1.0.48", features = ["raw_value"] } unicode-id-start = "1" if_chain = "1.0.0" scroll = { version = "0.12.0", features = ["derive"], optional = true } diff --git a/src/decoder.rs b/src/decoder.rs index 1581d00..44e221a 100644 --- a/src/decoder.rs +++ b/src/decoder.rs @@ -124,7 +124,7 @@ pub fn strip_junk_header(slice: &[u8]) -> io::Result<&[u8]> { } /// Decodes range mappping bitfield string into index -fn decode_rmi(rmi_str: &str, val: &mut BitVec) -> Result<()> { +pub(crate) fn decode_rmi(rmi_str: &str, val: &mut BitVec) -> Result<()> { val.clear(); val.resize(rmi_str.len() * 6, false); diff --git a/src/encoder.rs b/src/encoder.rs index abc3286..042e5c3 100644 --- a/src/encoder.rs +++ b/src/encoder.rs @@ -20,11 +20,11 @@ pub fn encode(sm: &M, mut w: W) -> Result<()> { Ok(()) } -fn encode_vlq_diff(out: &mut String, a: u32, b: u32) { +pub(crate) fn encode_vlq_diff(out: &mut String, a: u32, b: u32) { encode_vlq(out, i64::from(a) - i64::from(b)) } -fn encode_rmi(out: &mut Vec, data: &[u8]) { +pub(crate) fn encode_rmi(out: &mut Vec, data: &[u8]) { fn encode_byte(b: u8) -> u8 { match b { 0..=25 => b + b'A', diff --git a/src/lazy/mod.rs b/src/lazy/mod.rs new file mode 100644 index 0000000..04c56be --- /dev/null +++ b/src/lazy/mod.rs @@ -0,0 +1,622 @@ +//! This is _lazy_ because we skip deserializing all the fields that we don't need. (unlike the original crate) + +use crate::{ + decoder::{decode_rmi, strip_junk_header}, + encoder::{encode_rmi, encode_vlq_diff}, + types::adjust_mappings, + vlq::parse_vlq_segment_into, + Error, RawToken, Result, +}; +use std::{ + borrow::Cow, + collections::{BTreeSet, HashMap}, + sync::Arc, +}; + +use bitvec::{order::Lsb0, vec::BitVec, view::BitView}; +use serde::{Deserialize, Deserializer, Serialize}; +use serde_json::value::RawValue; + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct RawSourceMap<'a> { + pub(crate) version: Option, + #[serde(default, borrow)] + pub(crate) file: Option>, + #[serde(borrow)] + pub(crate) sources: MaybeRawValue<'a, Vec>>, + #[serde(default, borrow)] + pub(crate) source_root: Option>, + #[serde(default, borrow)] + pub(crate) sources_content: MaybeRawValue<'a, Vec>>>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub(crate) sections: Option>>, + #[serde(default, borrow)] + pub(crate) names: MaybeRawValue<'a, Vec>>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub(crate) range_mappings: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub(crate) mappings: Option, + #[serde(default, borrow)] + pub(crate) ignore_list: Option>>, +} + +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, Copy)] +pub struct RawSectionOffset { + pub line: u32, + pub column: u32, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct RawSection<'a> { + pub offset: RawSectionOffset, + #[serde(borrow)] + pub url: Option<&'a RawValue>, + #[serde(borrow)] + pub map: Option<&'a RawValue>, +} + +#[derive(Debug)] +pub enum DecodedMap<'a> { + Regular(SourceMap<'a>), + Index(SourceMapIndex<'a>), +} + +impl<'a> DecodedMap<'a> { + pub fn into_source_map(self) -> Result> { + match self { + DecodedMap::Regular(source_map) => Ok(source_map), + DecodedMap::Index(source_map_index) => source_map_index.flatten(), + } + } +} + +#[derive(Debug, Clone, Copy, Serialize)] +#[serde(untagged)] +pub(crate) enum MaybeRawValue<'a, T> { + RawValue(#[serde(borrow)] &'a RawValue), + Data(T), +} + +impl<'a, 'de, T> Deserialize<'de> for MaybeRawValue<'a, T> +where + 'de: 'a, + T: Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> std::result::Result + where + D: Deserializer<'de>, + { + let raw: &'de RawValue = Deserialize::deserialize(deserializer)?; + Ok(MaybeRawValue::RawValue(raw)) + } +} + +impl<'a, T> MaybeRawValue<'a, T> +where + T: Deserialize<'a>, +{ + pub fn into_data(self) -> T { + match self { + MaybeRawValue::RawValue(s) => { + serde_json::from_str(s.get()).expect("Failed to convert RawValue to Data") + } + MaybeRawValue::Data(data) => data, + } + } + + fn assert_raw_value(self) -> &'a RawValue { + match self { + MaybeRawValue::RawValue(s) => s, + MaybeRawValue::Data(_) => unreachable!("Expected RawValue, got Data"), + } + } +} + +impl Default for MaybeRawValue<'_, T> +where + T: Default, +{ + fn default() -> Self { + MaybeRawValue::Data(T::default()) + } +} + +type Str = Arc; + +type StrValue<'a> = MaybeRawValue<'a, Str>; + +#[derive(Debug)] +pub struct SourceMap<'a> { + file: Option>, + tokens: Vec, + names: MaybeRawValue<'a, Vec>>, + source_root: Option>, + sources: MaybeRawValue<'a, Vec>>, + sources_content: MaybeRawValue<'a, Vec>>>, + ignore_list: Option>>, +} + +#[derive(Debug)] +pub(crate) struct SourceMapBuilder<'a> { + file: Option>, + name_map: HashMap<&'a str, u32>, + names: Vec>, + tokens: Vec, + source_map: HashMap<&'a str, u32>, + sources: Vec>, + source_contents: Vec>>, + source_root: Option>, + ignore_list: Option>, +} + +impl<'a> SourceMapBuilder<'a> { + pub fn new(file: Option>) -> Self { + SourceMapBuilder { + file, + name_map: HashMap::new(), + names: Vec::new(), + tokens: Vec::new(), + source_map: HashMap::new(), + sources: Vec::new(), + source_contents: Vec::new(), + source_root: None, + ignore_list: None, + } + } + + pub fn add_source(&mut self, src_raw: &'a RawValue) -> u32 { + let src_str = src_raw.get(); // RawValue provides get() -> &str + let count = self.sources.len() as u32; + let id = *self.source_map.entry(src_str).or_insert(count); + if id == count { + // New source + self.sources.push(MaybeRawValue::RawValue(src_raw)); + // Ensure source_contents has a corresponding entry, defaulting to None. + // This logic ensures source_contents is always same length as sources if new one added. + self.source_contents.resize(self.sources.len(), None); + } + id + } + + pub fn add_name(&mut self, name_raw: &'a RawValue) -> u32 { + let name_str = name_raw.get(); + let count = self.names.len() as u32; + let id = *self.name_map.entry(name_str).or_insert(count); + if id == count { + // New name + self.names.push(MaybeRawValue::RawValue(name_raw)); + } + id + } + + pub fn set_source_contents(&mut self, src_id: u32, contents: Option<&'a RawValue>) { + // Ensure source_contents is large enough. src_id is 0-indexed. + if (src_id as usize) >= self.source_contents.len() { + self.source_contents.resize(src_id as usize + 1, None); + } + self.source_contents[src_id as usize] = contents.map(MaybeRawValue::RawValue); + } + + pub fn add_to_ignore_list(&mut self, src_id: u32) { + self.ignore_list + .get_or_insert_with(BTreeSet::new) + .insert(src_id); + } + + pub fn into_sourcemap(self) -> SourceMap<'a> { + SourceMap { + file: self.file, + tokens: self.tokens, + names: MaybeRawValue::Data(self.names), + source_root: self.source_root, + sources: MaybeRawValue::Data(self.sources), + sources_content: MaybeRawValue::Data(self.source_contents), + ignore_list: self.ignore_list.map(MaybeRawValue::Data), + } + } + + /// Adds a new mapping to the builder. + #[allow(clippy::too_many_arguments)] + pub fn add_raw( + &mut self, + dst_line: u32, + dst_col: u32, + src_line: u32, + src_col: u32, + source: Option, + name: Option, + is_range: bool, + ) -> RawToken { + let src_id = source.unwrap_or(!0); + let name_id = name.unwrap_or(!0); + let raw = RawToken { + dst_line, + dst_col, + src_line, + src_col, + src_id, + name_id, + is_range, + }; + self.tokens.push(raw); + raw + } +} + +#[derive(Debug)] +pub(crate) struct SourceMapSection<'a> { + offset: (u32, u32), + url: Option>, + map: Option>>>, +} + +impl<'a> SourceMapSection<'a> { + /// Create a new sourcemap index section + /// + /// - `offset`: offset as line and column + /// - `url`: optional URL of where the sourcemap is located + /// - `map`: an optional already resolved internal sourcemap + pub fn new( + offset: (u32, u32), + url: Option>, + map: Option>>, + ) -> SourceMapSection<'a> { + SourceMapSection { + offset, + url, + map: map.map(Box::new), + } + } + + /// Returns the offset as tuple + pub fn get_offset(&self) -> (u32, u32) { + self.offset + } +} + +#[derive(Debug)] +pub struct SourceMapIndex<'a> { + file: Option>, + sections: Vec>, +} + +pub fn decode(slice: &[u8]) -> Result { + let content = strip_junk_header(slice)?; + let rsm: RawSourceMap = serde_json::from_slice(content)?; + + decode_common(rsm) +} + +fn decode_common(rsm: RawSourceMap) -> Result { + if rsm.sections.is_some() { + decode_index(rsm).map(DecodedMap::Index) + } else { + decode_regular(rsm).map(DecodedMap::Regular) + } +} + +fn decode_index(rsm: RawSourceMap) -> Result { + let mut sections = vec![]; + + for raw_section in rsm.sections.unwrap_or_default() { + sections.push(SourceMapSection::new( + (raw_section.offset.line, raw_section.offset.column), + raw_section.url.map(MaybeRawValue::RawValue), + raw_section.map.map(MaybeRawValue::RawValue), + )); + } + + sections.sort_by_key(SourceMapSection::get_offset); + + // file sometimes is not a string for unexplicable reasons + let file = rsm.file; + + Ok(SourceMapIndex { file, sections }) +} + +pub fn decode_regular(rsm: RawSourceMap) -> Result { + let mut dst_col; + + // Source IDs, lines, columns, and names are "running" values. + // Each token (except the first) contains the delta from the previous value. + let mut running_src_id = 0; + let mut running_src_line = 0; + let mut running_src_col = 0; + let mut running_name_id = 0; + + let range_mappings = rsm.range_mappings.unwrap_or_default(); + let mappings = rsm.mappings.unwrap_or_default(); + let allocation_size = mappings.matches(&[',', ';'][..]).count() + 10; + let mut tokens = Vec::with_capacity(allocation_size); + + let mut nums = Vec::with_capacity(6); + let mut rmi = BitVec::new(); + + for (dst_line, (line, rmi_str)) in mappings + .split(';') + .zip(range_mappings.split(';').chain(std::iter::repeat(""))) + .enumerate() + { + if line.is_empty() { + continue; + } + + dst_col = 0; + + decode_rmi(rmi_str, &mut rmi)?; + + for (line_index, segment) in line.split(',').enumerate() { + if segment.is_empty() { + continue; + } + + nums.clear(); + parse_vlq_segment_into(segment, &mut nums)?; + match nums.len() { + 1 | 4 | 5 => {} + _ => return Err(Error::BadSegmentSize(nums.len() as u32)), + } + + dst_col = (i64::from(dst_col) + nums[0]) as u32; + + // The source file , source line, source column, and name + // may not be present in the current token. We use `u32::MAX` + // as the placeholder for missing values. + let mut current_src_id = !0; + let mut current_src_line = !0; + let mut current_src_col = !0; + let mut current_name_id = !0; + + if nums.len() > 1 { + running_src_id = (i64::from(running_src_id) + nums[1]) as u32; + + running_src_line = (i64::from(running_src_line) + nums[2]) as u32; + running_src_col = (i64::from(running_src_col) + nums[3]) as u32; + + current_src_id = running_src_id; + current_src_line = running_src_line; + current_src_col = running_src_col; + + if nums.len() > 4 { + running_name_id = (i64::from(running_name_id) + nums[4]) as u32; + current_name_id = running_name_id; + } + } + + let is_range = rmi.get(line_index).map(|v| *v).unwrap_or_default(); + + tokens.push(RawToken { + dst_line: dst_line as u32, + dst_col, + src_line: current_src_line, + src_col: current_src_col, + src_id: current_src_id, + name_id: current_name_id, + is_range, + }); + } + } + + let sm = SourceMap { + file: rsm.file, + tokens, + names: rsm.names, + source_root: rsm.source_root, + sources: rsm.sources, + sources_content: rsm.sources_content, + ignore_list: rsm.ignore_list, + }; + + Ok(sm) +} + +impl<'a> SourceMap<'a> { + /// Refer to [crate::SourceMap::adjust_mappings] for more details. + pub fn adjust_mappings(&mut self, adjustment: crate::SourceMap) { + self.tokens = adjust_mappings( + std::mem::take(&mut self.tokens), + Cow::Owned(adjustment.tokens), + ); + } + + pub fn into_raw_sourcemap(self) -> RawSourceMap<'a> { + RawSourceMap { + version: Some(3), + range_mappings: serialize_range_mappings(&self), + mappings: Some(serialize_mappings(&self)), + file: self.file, + sources: self.sources, + source_root: self.source_root, + sources_content: self.sources_content, + sections: None, + names: self.names, + ignore_list: self.ignore_list, + } + } +} + +impl<'a> SourceMapIndex<'a> { + pub fn flatten(self) -> Result> { + let mut builder = SourceMapBuilder::new(self.file); + + for section in self.sections { + let (off_line, off_col) = section.get_offset(); + + let map = match section.map { + Some(map) => match decode_common(map.into_data())? { + DecodedMap::Regular(sm) => sm, + DecodedMap::Index(idx) => idx.flatten()?, + }, + None => { + return Err(Error::CannotFlatten(format!( + "Section has an unresolved sourcemap: {}", + section + .url + .map(|v| v.into_data()) + .as_deref() + .unwrap_or("") + ))); + } + }; + + let sources = map.sources.into_data(); + let source_contents = map.sources_content.into_data(); + let ignore_list = map.ignore_list.unwrap_or_default().into_data(); + + let mut src_id_map = Vec::::with_capacity(sources.len()); + + for (original_id, (source, contents)) in + sources.into_iter().zip(source_contents).enumerate() + { + debug_assert_eq!(original_id, src_id_map.len()); + let src_id = builder.add_source(source.assert_raw_value()); + + src_id_map.push(src_id); + + if let Some(contents) = contents { + builder.set_source_contents(src_id, Some(contents.assert_raw_value())); + } + } + + let names = map.names.into_data(); + let mut name_id_map = Vec::::with_capacity(names.len()); + + for (original_id, name) in names.into_iter().enumerate() { + debug_assert_eq!(original_id, name_id_map.len()); + let name_id = builder.add_name(name.assert_raw_value()); + name_id_map.push(name_id); + } + + for token in map.tokens { + let dst_col = if token.dst_line == 0 { + token.dst_col + off_col + } else { + token.dst_col + }; + + // Use u32 -> u32 map instead of using the hash map in SourceMapBuilder for better + // performance + let original_src_id = token.src_id; + let src_id = if original_src_id == !0 { + None + } else { + src_id_map.get(original_src_id as usize).copied() + }; + + let original_name_id = token.name_id; + let name_id = if original_name_id == !0 { + None + } else { + name_id_map.get(original_name_id as usize).copied() + }; + + let raw = builder.add_raw( + token.dst_line + off_line, + dst_col, + token.src_line, + token.src_col, + src_id, + name_id, + token.is_range, + ); + + if ignore_list.contains(&token.src_id) { + builder.add_to_ignore_list(raw.src_id); + } + } + } + + Ok(builder.into_sourcemap()) + } +} + +fn serialize_range_mappings(sm: &SourceMap) -> Option { + let mut buf = Vec::new(); + let mut prev_line = 0; + let mut had_rmi = false; + let mut empty = true; + + let mut idx_of_first_in_line = 0; + + let mut rmi_data = Vec::::new(); + + for (idx, token) in sm.tokens.iter().enumerate() { + if token.is_range { + had_rmi = true; + empty = false; + + let num = idx - idx_of_first_in_line; + + rmi_data.resize(rmi_data.len() + 2, 0); + + let rmi_bits = rmi_data.view_bits_mut::(); + rmi_bits.set(num, true); + } + + while token.dst_line != prev_line { + if had_rmi { + encode_rmi(&mut buf, &rmi_data); + rmi_data.clear(); + } + + buf.push(b';'); + prev_line += 1; + had_rmi = false; + idx_of_first_in_line = idx; + } + } + if empty { + return None; + } + + if had_rmi { + encode_rmi(&mut buf, &rmi_data); + } + + Some(String::from_utf8(buf).expect("invalid utf8")) +} + +fn serialize_mappings(sm: &SourceMap) -> String { + let mut rv = String::new(); + // dst == minified == generated + let mut prev_dst_line = 0; + let mut prev_dst_col = 0; + let mut prev_src_line = 0; + let mut prev_src_col = 0; + let mut prev_name_id = 0; + let mut prev_src_id = 0; + + for (idx, token) in sm.tokens.iter().enumerate() { + if token.dst_line != prev_dst_line { + prev_dst_col = 0; + while token.dst_line != prev_dst_line { + rv.push(';'); + prev_dst_line += 1; + } + } else if idx > 0 { + if Some(&token) == sm.tokens.get(idx - 1).as_ref() { + continue; + } + rv.push(','); + } + + encode_vlq_diff(&mut rv, token.dst_col, prev_dst_col); + prev_dst_col = token.dst_col; + + if token.src_id != !0 { + encode_vlq_diff(&mut rv, token.src_id, prev_src_id); + prev_src_id = token.src_id; + encode_vlq_diff(&mut rv, token.src_line, prev_src_line); + prev_src_line = token.src_line; + encode_vlq_diff(&mut rv, token.src_col, prev_src_col); + prev_src_col = token.src_col; + if token.name_id != !0 { + encode_vlq_diff(&mut rv, token.name_id, prev_name_id); + prev_name_id = token.name_id; + } + } + } + + rv +} diff --git a/src/lib.rs b/src/lib.rs index 7aeccd7..6c3686b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -68,6 +68,7 @@ mod errors; mod hermes; mod js_identifiers; mod jsontypes; +pub mod lazy; mod sourceview; mod types; mod utils; diff --git a/src/sourceview.rs b/src/sourceview.rs index e0ff52e..e0e098d 100644 --- a/src/sourceview.rs +++ b/src/sourceview.rs @@ -128,7 +128,7 @@ impl<'a> Iterator for Lines<'a> { /// This type is used to implement fairly efficient source mapping /// operations. pub struct SourceView { - source: Arc, + pub(crate) source: Arc, processed_until: AtomicUsize, lines: Mutex>, } diff --git a/src/types.rs b/src/types.rs index 5ef415b..b56d2d6 100644 --- a/src/types.rs +++ b/src/types.rs @@ -933,137 +933,145 @@ impl SourceMap { /// /// Note that the resulting sourcemap will be at most as fine-grained as `self.`. pub fn adjust_mappings(&mut self, adjustment: &Self) { - // The algorithm works by going through the tokens in `self` in order and adjusting - // them depending on the token in `adjustment` they're "covered" by. - // For example: - // Let `l` be a token in `adjustment` mapping `(17, 23)` to `(8, 30)` and let - // `r₁ : (8, 28) -> (102, 35)`, `r₂ : (8, 40) -> (102, 50)`, and - // `r₃ : (9, 10) -> (103, 12)` be the tokens in `self` that fall in the range of `l`. - // `l` offsets these tokens by `(+9, -7)`, so `r₁, … , r₃` must be offset by the same - // amount. Thus, the adjusted sourcemap will contain the tokens - // `c₁ : (17, 23) -> (102, 35)`, `c₂ : (17, 33) -> (102, 50)`, and - // `c3 : (18, 3) -> (103, 12)`. - // - // Or, in diagram form: - // - // (17, 23) (position in the edited source file) - // ↓ l - // (8, 30) - // (8, 28) (8, 40) (9, 10) (positions in the original source file) - // ↓ r₁ ↓ r₂ ↓ r₃ - // (102, 35) (102, 50) (103, 12) (positions in the target file) - // - // becomes - // - // (17, 23) (17, 33) (18, 3) (positions in the edited source file) - // ↓ c₁ ↓ c₂ ↓ c₃ - // (102, 35) (102, 50) (103, 12) (positions in the target file) - - // Helper struct that makes it easier to compare tokens by the start and end - // of the range they cover. - #[derive(Debug, Clone, Copy)] - struct Range<'a> { - start: (u32, u32), - end: (u32, u32), - value: &'a RawToken, + self.tokens = adjust_mappings( + std::mem::take(&mut self.tokens), + Cow::Borrowed(&adjustment.tokens), + ); + } +} + +pub(crate) fn adjust_mappings( + mut self_tokens: Vec, + adjustments: Cow<[RawToken]>, +) -> Vec { + // The algorithm works by going through the tokens in `self` in order and adjusting + // them depending on the token in `adjustment` they're "covered" by. + // For example: + // Let `l` be a token in `adjustment` mapping `(17, 23)` to `(8, 30)` and let + // `r₁ : (8, 28) -> (102, 35)`, `r₂ : (8, 40) -> (102, 50)`, and + // `r₃ : (9, 10) -> (103, 12)` be the tokens in `self` that fall in the range of `l`. + // `l` offsets these tokens by `(+9, -7)`, so `r₁, … , r₃` must be offset by the same + // amount. Thus, the adjusted sourcemap will contain the tokens + // `c₁ : (17, 23) -> (102, 35)`, `c₂ : (17, 33) -> (102, 50)`, and + // `c3 : (18, 3) -> (103, 12)`. + // + // Or, in diagram form: + // + // (17, 23) (position in the edited source file) + // ↓ l + // (8, 30) + // (8, 28) (8, 40) (9, 10) (positions in the original source file) + // ↓ r₁ ↓ r₂ ↓ r₃ + // (102, 35) (102, 50) (103, 12) (positions in the target file) + // + // becomes + // + // (17, 23) (17, 33) (18, 3) (positions in the edited source file) + // ↓ c₁ ↓ c₂ ↓ c₃ + // (102, 35) (102, 50) (103, 12) (positions in the target file) + + // Helper struct that makes it easier to compare tokens by the start and end + // of the range they cover. + #[derive(Debug, Clone, Copy)] + struct Range<'a> { + start: (u32, u32), + end: (u32, u32), + value: &'a RawToken, + } + + /// Turns a list of tokens into a list of ranges, using the provided `key` function to determine the order of the tokens. + #[allow(clippy::ptr_arg)] + fn create_ranges(tokens: &mut [RawToken], key: fn(&RawToken) -> (u32, u32)) -> Vec> { + tokens.sort_unstable_by_key(key); + + let mut token_iter = tokens.iter().peekable(); + let mut ranges = Vec::new(); + + while let Some(t) = token_iter.next() { + let start = key(t); + let next_start = token_iter.peek().map_or((u32::MAX, u32::MAX), |t| key(t)); + // A token extends either to the start of the next token or the end of the line, whichever comes sooner + let end = std::cmp::min(next_start, (start.0, u32::MAX)); + ranges.push(Range { + start, + end, + value: t, + }); } - /// Turns a list of tokens into a list of ranges, using the provided `key` function to determine the order of the tokens. - #[allow(clippy::ptr_arg)] - fn create_ranges( - tokens: &mut [RawToken], - key: fn(&RawToken) -> (u32, u32), - ) -> Vec> { - tokens.sort_unstable_by_key(key); - - let mut token_iter = tokens.iter().peekable(); - let mut ranges = Vec::new(); - - while let Some(t) = token_iter.next() { - let start = key(t); - let next_start = token_iter.peek().map_or((u32::MAX, u32::MAX), |t| key(t)); - // A token extends either to the start of the next token or the end of the line, whichever comes sooner - let end = std::cmp::min(next_start, (start.0, u32::MAX)); - ranges.push(Range { - start, - end, - value: t, - }); - } + ranges + } - ranges - } + let mut new_tokens = Vec::with_capacity(self_tokens.len()); - // Turn `self.tokens` and `adjustment.tokens` into vectors of ranges so we have easy access to - // both start and end. - // We want to compare `self` and `adjustment` tokens by line/column numbers in the "original source" file. - // These line/column numbers are the `dst_line/col` for - // the `self` tokens and `src_line/col` for the `adjustment` tokens. - let mut self_tokens = std::mem::take(&mut self.tokens); - let original_ranges = create_ranges(&mut self_tokens, |t| (t.dst_line, t.dst_col)); - let mut adjustment_tokens = adjustment.tokens.clone(); - let adjustment_ranges = create_ranges(&mut adjustment_tokens, |t| (t.src_line, t.src_col)); - - let mut original_ranges_iter = original_ranges.iter(); - - let mut original_range = match original_ranges_iter.next() { - Some(r) => r, - None => return, - }; + // Turn `self.tokens` and `adjustment.tokens` into vectors of ranges so we have easy access to + // both start and end. + // We want to compare `self` and `adjustment` tokens by line/column numbers in the "original source" file. + // These line/column numbers are the `dst_line/col` for + // the `self` tokens and `src_line/col` for the `adjustment` tokens. + let original_ranges = create_ranges(&mut self_tokens, |t| (t.dst_line, t.dst_col)); + let mut adjustment_tokens = adjustments.into_owned(); + let adjustment_ranges = create_ranges(&mut adjustment_tokens, |t| (t.src_line, t.src_col)); - // Iterate over `adjustment_ranges` (sorted by `src_line/col`). For each such range, consider - // all `original_ranges` which overlap with it. - 'outer: for &adjustment_range in &adjustment_ranges { - // The `adjustment_range` offsets lines and columns by a certain amount. All `original_ranges` - // it covers will get the same offset. - let (line_diff, col_diff) = ( - adjustment_range.value.dst_line as i32 - adjustment_range.value.src_line as i32, - adjustment_range.value.dst_col as i32 - adjustment_range.value.src_col as i32, - ); + let mut original_ranges_iter = original_ranges.iter(); - // Skip `original_ranges` that are entirely before the `adjustment_range`. - while original_range.end <= adjustment_range.start { - match original_ranges_iter.next() { - Some(r) => original_range = r, - None => break 'outer, - } + let mut original_range = match original_ranges_iter.next() { + Some(r) => r, + None => return self_tokens, + }; + + // Iterate over `adjustment_ranges` (sorted by `src_line/col`). For each such range, consider + // all `original_ranges` which overlap with it. + 'outer: for &adjustment_range in &adjustment_ranges { + // The `adjustment_range` offsets lines and columns by a certain amount. All `original_ranges` + // it covers will get the same offset. + let (line_diff, col_diff) = ( + adjustment_range.value.dst_line as i32 - adjustment_range.value.src_line as i32, + adjustment_range.value.dst_col as i32 - adjustment_range.value.src_col as i32, + ); + + // Skip `original_ranges` that are entirely before the `adjustment_range`. + while original_range.end <= adjustment_range.start { + match original_ranges_iter.next() { + Some(r) => original_range = r, + None => break 'outer, } + } - // At this point `original_range.end` > `adjustment_range.start` + // At this point `original_range.end` > `adjustment_range.start` - // Iterate over `original_ranges` that fall at least partially within the `adjustment_range`. - while original_range.start < adjustment_range.end { - // If `original_range` started before `adjustment_range`, cut off the token's start. - let (dst_line, dst_col) = - std::cmp::max(original_range.start, adjustment_range.start); - let mut token = RawToken { - dst_line, - dst_col, - ..*original_range.value - }; + // Iterate over `original_ranges` that fall at least partially within the `adjustment_range`. + while original_range.start < adjustment_range.end { + // If `original_range` started before `adjustment_range`, cut off the token's start. + let (dst_line, dst_col) = std::cmp::max(original_range.start, adjustment_range.start); + let mut token = RawToken { + dst_line, + dst_col, + ..*original_range.value + }; - token.dst_line = (token.dst_line as i32 + line_diff) as u32; - token.dst_col = (token.dst_col as i32 + col_diff) as u32; + token.dst_line = (token.dst_line as i32 + line_diff) as u32; + token.dst_col = (token.dst_col as i32 + col_diff) as u32; - self.tokens.push(token); + new_tokens.push(token); - if original_range.end >= adjustment_range.end { - // There are surely no more `original_ranges` for this `adjustment_range`. - // Break the loop without advancing the `original_range`. - break; - } else { - // Advance the `original_range`. - match original_ranges_iter.next() { - Some(r) => original_range = r, - None => break 'outer, - } + if original_range.end >= adjustment_range.end { + // There are surely no more `original_ranges` for this `adjustment_range`. + // Break the loop without advancing the `original_range`. + break; + } else { + // Advance the `original_range`. + match original_ranges_iter.next() { + Some(r) => original_range = r, + None => break 'outer, } } } - - self.tokens - .sort_unstable_by_key(|t| (t.dst_line, t.dst_col)); } + + new_tokens.sort_unstable_by_key(|t| (t.dst_line, t.dst_col)); + + new_tokens } impl SourceMapIndex { From 76a5863beb1a3c55fe5a634686f0d0cb4d1527c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Thu, 5 Jun 2025 15:35:46 -0700 Subject: [PATCH 02/12] fix: Fix serialization of source maps (#2) I added some `skip_serializing_if` to `RawSourceMap` --- src/lazy/mod.rs | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/lazy/mod.rs b/src/lazy/mod.rs index 04c56be..204acb2 100644 --- a/src/lazy/mod.rs +++ b/src/lazy/mod.rs @@ -20,24 +20,25 @@ use serde_json::value::RawValue; #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct RawSourceMap<'a> { + #[serde(default, skip_serializing_if = "Option::is_none")] pub(crate) version: Option, - #[serde(default, borrow)] + #[serde(default, borrow, skip_serializing_if = "Option::is_none")] pub(crate) file: Option>, - #[serde(borrow)] + #[serde(borrow, skip_serializing_if = "MaybeRawValue::is_empty")] pub(crate) sources: MaybeRawValue<'a, Vec>>, - #[serde(default, borrow)] + #[serde(default, borrow, skip_serializing_if = "Option::is_none")] pub(crate) source_root: Option>, - #[serde(default, borrow)] + #[serde(default, borrow, skip_serializing_if = "MaybeRawValue::is_empty")] pub(crate) sources_content: MaybeRawValue<'a, Vec>>>, #[serde(default, skip_serializing_if = "Option::is_none")] pub(crate) sections: Option>>, - #[serde(default, borrow)] + #[serde(default, borrow, skip_serializing_if = "MaybeRawValue::is_empty")] pub(crate) names: MaybeRawValue<'a, Vec>>, #[serde(default, skip_serializing_if = "Option::is_none")] pub(crate) range_mappings: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub(crate) mappings: Option, - #[serde(default, borrow)] + #[serde(default, borrow, skip_serializing_if = "Option::is_none")] pub(crate) ignore_list: Option>>, } @@ -78,6 +79,15 @@ pub(crate) enum MaybeRawValue<'a, T> { Data(T), } +impl MaybeRawValue<'_, Vec> { + pub fn is_empty(&self) -> bool { + match self { + MaybeRawValue::Data(vec) => vec.is_empty(), + MaybeRawValue::RawValue(_) => false, + } + } +} + impl<'a, 'de, T> Deserialize<'de> for MaybeRawValue<'a, T> where 'de: 'a, From 804d663521d6109b3746d800918d483b97e8e108 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=EB=8F=99=EC=9C=A4=20=28Donny=29?= Date: Thu, 5 Jun 2025 15:39:43 -0700 Subject: [PATCH 03/12] fix: Keep `names` field --- src/lazy/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lazy/mod.rs b/src/lazy/mod.rs index 204acb2..a61b962 100644 --- a/src/lazy/mod.rs +++ b/src/lazy/mod.rs @@ -32,7 +32,7 @@ pub struct RawSourceMap<'a> { pub(crate) sources_content: MaybeRawValue<'a, Vec>>>, #[serde(default, skip_serializing_if = "Option::is_none")] pub(crate) sections: Option>>, - #[serde(default, borrow, skip_serializing_if = "MaybeRawValue::is_empty")] + #[serde(default, borrow)] pub(crate) names: MaybeRawValue<'a, Vec>>, #[serde(default, skip_serializing_if = "Option::is_none")] pub(crate) range_mappings: Option, From acd7bbb7a66670b5c47760553edaef1a8c61e5c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Mon, 9 Jun 2025 13:02:41 -0700 Subject: [PATCH 04/12] refactor: Use `BytesStr` instead of `Arc` (#1) `BytesStr` is a more efficient variant of `Arc` Since https://github.com/swc-project/swc/pull/10580, SWC also uses it to store the source map strings --- .github/workflows/ci.yml | 8 +- .vscode/settings.json | 3 + Cargo.toml | 13 +-- README.md | 2 +- cli/src/main.rs | 2 +- examples/read.rs | 2 +- examples/rewrite.rs | 4 +- examples/split_ram_bundle.rs | 4 +- src/builder.rs | 62 +++++++------- src/decoder.rs | 2 +- src/hermes.rs | 13 ++- src/jsontypes.rs | 3 +- src/lazy/mod.rs | 4 +- src/lib.rs | 2 +- src/ram_bundle.rs | 13 +-- src/sourceview.rs | 14 ++-- src/types.rs | 155 +++++++++++++++++------------------ src/utils.rs | 2 +- tests/test_builder.rs | 12 +-- tests/test_decoder.rs | 2 +- tests/test_detector.rs | 2 +- tests/test_encoder.rs | 2 +- tests/test_hermes.rs | 24 ++++-- tests/test_index.rs | 10 +-- tests/test_namemap.rs | 8 +- tests/test_regular.rs | 4 +- 26 files changed, 195 insertions(+), 177 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 84d42f4..5921c67 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,11 +1,13 @@ name: CI + on: + merge_group: + pull_request: + types: ["opened", "reopened", "synchronize"] push: branches: - - master - - "release/**" - pull_request: + - main env: RUSTFLAGS: -Dwarnings diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c8e0c61 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "rust-analyzer.cargo.features": "all", +} \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 20fac8b..24a73f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,11 @@ [package] -name = "sourcemap" -version = "9.2.2" -authors = ["Sentry "] +name = "swc_sourcemap" +version = "9.3.0" +authors = ["Sentry ", "swc-project "] keywords = ["javascript", "sourcemap", "sourcemaps"] -description = "Basic sourcemap handling for Rust" -homepage = "https://github.com/getsentry/rust-sourcemap" -repository = "https://github.com/getsentry/rust-sourcemap" +description = "Forked from https://github.com/getsentry/rust-sourcemap" +homepage = "https://github.com/swc-project/swc-sourcemap" +repository = "https://github.com/swc-project/swc-sourcemap" license = "BSD-3-Clause" readme = "README.md" edition = "2018" @@ -34,6 +34,7 @@ debugid = {version = "0.8.0", features = ["serde"] } base64-simd = { version = "0.8" } bitvec = "1.0.1" rustc-hash = "2.1.1" +bytes-str = { version = "0.2.4", features = ["serde"] } [features] ram_bundle = ["scroll"] diff --git a/README.md b/README.md index a0f7e6c..f057ee2 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ memory intensive. Usage: ```rust -use sourcemap::SourceMap; +use swc_sourcemap::SourceMap; let input: &[_] = b"{ \"version\":3, \"sources\":[\"coolstuff.js\"], diff --git a/cli/src/main.rs b/cli/src/main.rs index 0acf869..34e6a96 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -3,7 +3,7 @@ use std::path::PathBuf; use std::process; use argh::FromArgs; -use sourcemap::{DecodedMap, SourceView, Token}; +use swc_sourcemap::{DecodedMap, SourceView, Token}; /// Utility for working with source maps. #[derive(FromArgs, Debug)] diff --git a/examples/read.rs b/examples/read.rs index 84965ae..845ea00 100644 --- a/examples/read.rs +++ b/examples/read.rs @@ -2,7 +2,7 @@ use std::env; use std::fs; use std::io::Read; -use sourcemap::{decode, DecodedMap, RewriteOptions, SourceMap}; +use swc_sourcemap::{decode, DecodedMap, RewriteOptions, SourceMap}; fn load_from_reader(mut rdr: R) -> SourceMap { match decode(&mut rdr).unwrap() { diff --git a/examples/rewrite.rs b/examples/rewrite.rs index 87b1ce2..448982d 100644 --- a/examples/rewrite.rs +++ b/examples/rewrite.rs @@ -3,7 +3,7 @@ use std::fs; use std::io::Read; use std::path::Path; -use sourcemap::{decode, DecodedMap, RewriteOptions, SourceMap}; +use swc_sourcemap::{decode, DecodedMap, RewriteOptions, SourceMap}; fn test(sm: &SourceMap) { for (src_id, source) in sm.sources().enumerate() { @@ -14,7 +14,7 @@ fn test(sm: &SourceMap) { if f.read_to_string(&mut contents).ok().is_none() { continue; } - if Some(contents.as_str()) != sm.get_source_contents(src_id as u32) { + if Some(contents.as_str()) != sm.get_source_contents(src_id as u32).map(|v| &**v) { println!(" !!! {source}"); } } diff --git a/examples/split_ram_bundle.rs b/examples/split_ram_bundle.rs index 9aafdbe..93f8f94 100644 --- a/examples/split_ram_bundle.rs +++ b/examples/split_ram_bundle.rs @@ -3,8 +3,8 @@ use std::fs; use std::fs::File; use std::path::Path; -use sourcemap::ram_bundle::{split_ram_bundle, RamBundle, RamBundleType}; -use sourcemap::SourceMapIndex; +use swc_sourcemap::ram_bundle::{split_ram_bundle, RamBundle, RamBundleType}; +use swc_sourcemap::SourceMapIndex; const USAGE: &str = " Usage: diff --git a/src/builder.rs b/src/builder.rs index 32d0bdc..5f3d8b7 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -5,8 +5,8 @@ use std::env; use std::fs; use std::io::Read; use std::path::{Path, PathBuf}; -use std::sync::Arc; +use bytes_str::BytesStr; use debugid::DebugId; use rustc_hash::FxHashMap; use url::Url; @@ -20,14 +20,14 @@ use crate::types::{RawToken, SourceMap, Token}; /// objects is generally not very comfortable. As a general aid this /// type can help. pub struct SourceMapBuilder { - file: Option>, - name_map: FxHashMap, u32>, - names: Vec>, + file: Option, + name_map: FxHashMap, + names: Vec, tokens: Vec, - source_map: FxHashMap, u32>, - source_root: Option>, - sources: Vec>, - source_contents: Vec>>, + source_map: FxHashMap, + source_root: Option, + sources: Vec, + source_contents: Vec>, sources_mapping: Vec, ignore_list: BTreeSet, debug_id: Option, @@ -52,9 +52,9 @@ fn resolve_local_reference(base: &Url, reference: &str) -> Option { impl SourceMapBuilder { /// Creates a new source map builder and sets the file. - pub fn new(file: Option<&str>) -> SourceMapBuilder { + pub fn new(file: Option) -> SourceMapBuilder { SourceMapBuilder { - file: file.map(Into::into), + file, name_map: FxHashMap::default(), names: vec![], tokens: vec![], @@ -74,7 +74,7 @@ impl SourceMapBuilder { } /// Sets the file for the sourcemap (optional) - pub fn set_file>>(&mut self, value: Option) { + pub fn set_file>(&mut self, value: Option) { self.file = value.map(Into::into); } @@ -84,7 +84,7 @@ impl SourceMapBuilder { } /// Sets a new value for the source_root. - pub fn set_source_root>>(&mut self, value: Option) { + pub fn set_source_root>(&mut self, value: Option) { self.source_root = value.map(Into::into); } @@ -94,24 +94,24 @@ impl SourceMapBuilder { } /// Registers a new source with the builder and returns the source ID. - pub fn add_source(&mut self, src: &str) -> u32 { + pub fn add_source(&mut self, src: BytesStr) -> u32 { self.add_source_with_id(src, !0) } - fn add_source_with_id(&mut self, src: &str, old_id: u32) -> u32 { + fn add_source_with_id(&mut self, src: BytesStr, old_id: u32) -> u32 { let count = self.sources.len() as u32; - let id = *self.source_map.entry(src.into()).or_insert(count); + let id = *self.source_map.entry(src.clone()).or_insert(count); if id == count { - self.sources.push(src.into()); + self.sources.push(src); self.sources_mapping.push(old_id); } id } /// Changes the source name for an already set source. - pub fn set_source(&mut self, src_id: u32, src: &str) { + pub fn set_source(&mut self, src_id: u32, src: BytesStr) { assert!(src_id != !0, "Cannot set sources for tombstone source id"); - self.sources[src_id as usize] = src.into(); + self.sources[src_id as usize] = src; } /// Looks up a source name for an ID. @@ -124,12 +124,12 @@ impl SourceMapBuilder { } /// Sets the source contents for an already existing source. - pub fn set_source_contents(&mut self, src_id: u32, contents: Option<&str>) { + pub fn set_source_contents(&mut self, src_id: u32, contents: Option) { assert!(src_id != !0, "Cannot set sources for tombstone source id"); if self.sources.len() > self.source_contents.len() { self.source_contents.resize(self.sources.len(), None); } - self.source_contents[src_id as usize] = contents.map(Into::into); + self.source_contents[src_id as usize] = contents; } /// Returns the current source contents for a source. @@ -169,7 +169,7 @@ impl SourceMapBuilder { if let Ok(mut f) = fs::File::open(path) { let mut contents = String::new(); if f.read_to_string(&mut contents).is_ok() { - self.set_source_contents(src_id, Some(&contents)); + self.set_source_contents(src_id, Some(contents.into())); } } } @@ -178,11 +178,11 @@ impl SourceMapBuilder { } /// Registers a name with the builder and returns the name ID. - pub fn add_name(&mut self, name: &str) -> u32 { + pub fn add_name(&mut self, name: BytesStr) -> u32 { let count = self.names.len() as u32; - let id = *self.name_map.entry(name.into()).or_insert(count); + let id = *self.name_map.entry(name.clone()).or_insert(count); if id == count { - self.names.push(name.into()); + self.names.push(name); } id } @@ -195,8 +195,8 @@ impl SourceMapBuilder { dst_col: u32, src_line: u32, src_col: u32, - source: Option<&str>, - name: Option<&str>, + source: Option, + name: Option, is_range: bool, ) -> RawToken { self.add_with_id( @@ -211,9 +211,9 @@ impl SourceMapBuilder { dst_col: u32, src_line: u32, src_col: u32, - source: Option<&str>, + source: Option, source_id: u32, - name: Option<&str>, + name: Option, is_range: bool, ) -> RawToken { let src_id = match source { @@ -273,9 +273,9 @@ impl SourceMapBuilder { token.get_dst_col(), token.get_src_line(), token.get_src_col(), - token.get_source(), + token.get_source().cloned(), token.get_src_id(), - name, + name.cloned(), token.is_range(), ) } @@ -289,7 +289,7 @@ impl SourceMapBuilder { prefix.push('/'); } if source.starts_with(&prefix) { - *source = source[prefix.len()..].into(); + source.advance(prefix.len()); break; } } diff --git a/src/decoder.rs b/src/decoder.rs index 44e221a..ca9eac2 100644 --- a/src/decoder.rs +++ b/src/decoder.rs @@ -297,7 +297,7 @@ fn decode_index(rsm: RawSourceMap) -> Result { // file sometimes is not a string for unexplicable reasons let file = rsm.file.map(|val| match val { - Value::String(s) => s, + Value::String(s) => s.into(), _ => "".into(), }); diff --git a/src/hermes.rs b/src/hermes.rs index e43eedc..2e0dd64 100644 --- a/src/hermes.rs +++ b/src/hermes.rs @@ -1,3 +1,5 @@ +use bytes_str::BytesStr; + use crate::decoder::{decode, decode_regular, decode_slice}; use crate::encoder::{encode, Encodable}; use crate::errors::{Error, Result}; @@ -21,7 +23,7 @@ pub struct HermesScopeOffset { #[derive(Debug, Clone, PartialEq)] pub struct HermesFunctionMap { - names: Vec, + names: Vec, mappings: Vec, } @@ -93,14 +95,14 @@ impl SourceMapHermes { /// Given a bytecode offset, this will find the enclosing scopes function /// name. - pub fn get_original_function_name(&self, bytecode_offset: u32) -> Option<&str> { + pub fn get_original_function_name(&self, bytecode_offset: u32) -> Option<&BytesStr> { let token = self.sm.lookup_token(0, bytecode_offset)?; self.get_scope_for_token(token) } /// Resolves the name of the enclosing function for the given [`Token`]. - pub fn get_scope_for_token(&self, token: Token) -> Option<&str> { + pub fn get_scope_for_token(&self, token: Token) -> Option<&BytesStr> { let function_map = self .function_maps .get(token.get_src_id() as usize)? @@ -115,10 +117,7 @@ impl SourceMapHermes { &(token.get_src_line() + 1, token.get_src_col()), |o| (o.line, o.column), )?; - function_map - .names - .get(mapping.name_index as usize) - .map(|n| n.as_str()) + function_map.names.get(mapping.name_index as usize) } /// This rewrites the sourcemap according to the provided rewrite diff --git a/src/jsontypes.rs b/src/jsontypes.rs index 281ba6b..35f896a 100644 --- a/src/jsontypes.rs +++ b/src/jsontypes.rs @@ -1,3 +1,4 @@ +use bytes_str::BytesStr; use debugid::DebugId; use serde::de::IgnoredAny; use serde::{Deserialize, Serialize}; @@ -18,7 +19,7 @@ pub struct RawSection { #[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] pub struct FacebookScopeMapping { - pub names: Vec, + pub names: Vec, pub mappings: String, } diff --git a/src/lazy/mod.rs b/src/lazy/mod.rs index a61b962..ca0a55c 100644 --- a/src/lazy/mod.rs +++ b/src/lazy/mod.rs @@ -10,10 +10,10 @@ use crate::{ use std::{ borrow::Cow, collections::{BTreeSet, HashMap}, - sync::Arc, }; use bitvec::{order::Lsb0, vec::BitVec, view::BitView}; +use bytes_str::BytesStr; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::value::RawValue; @@ -132,7 +132,7 @@ where } } -type Str = Arc; +type Str = BytesStr; type StrValue<'a> = MaybeRawValue<'a, Str>; diff --git a/src/lib.rs b/src/lib.rs index 6c3686b..b835956 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,7 +26,7 @@ //! Usage: //! //! ```rust -//! use sourcemap::SourceMap; +//! use swc_sourcemap::SourceMap; //! let input: &[_] = b"{ //! \"version\":3, //! \"sources\":[\"coolstuff.js\"], diff --git a/src/ram_bundle.rs b/src/ram_bundle.rs index d37af65..8b071e0 100644 --- a/src/ram_bundle.rs +++ b/src/ram_bundle.rs @@ -1,4 +1,5 @@ //! RAM bundle operations +use bytes_str::BytesStr; use scroll::Pread; use std::borrow::Cow; use std::collections::BTreeMap; @@ -71,8 +72,8 @@ impl<'a> RamBundleModule<'a> { /// /// This operation fails if the source code is not valid UTF-8. pub fn source_view(&self) -> Result { - match std::str::from_utf8(self.data) { - Ok(s) => Ok(SourceView::new(s.into())), + match BytesStr::from_utf8_slice(self.data) { + Ok(s) => Ok(SourceView::new(s)), Err(e) => Err(Error::Utf8(e)), } } @@ -386,7 +387,7 @@ impl<'a> SplitRamBundleModuleIter<'a> { as u32; let filename = format!("{}.js", module.id); - let mut builder = SourceMapBuilder::new(Some(&filename)); + let mut builder = SourceMapBuilder::new(Some(filename.clone().into())); for token in token_iter { let dst_line = token.get_dst_line(); let dst_col = token.get_dst_col(); @@ -400,14 +401,14 @@ impl<'a> SplitRamBundleModuleIter<'a> { dst_col, token.get_src_line(), token.get_src_col(), - token.get_source(), - token.get_name(), + token.get_source().cloned(), + token.get_name().cloned(), false, ); if token.get_source().is_some() && !builder.has_source_contents(raw.src_id) { builder.set_source_contents( raw.src_id, - self.sm.get_source_contents(token.get_src_id()), + self.sm.get_source_contents(token.get_src_id()).cloned(), ); } } diff --git a/src/sourceview.rs b/src/sourceview.rs index e0e098d..1f9a6a5 100644 --- a/src/sourceview.rs +++ b/src/sourceview.rs @@ -3,9 +3,9 @@ use std::slice; use std::str; use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; -use std::sync::Arc; use std::sync::Mutex; +use bytes_str::BytesStr; use if_chain::if_chain; use crate::detector::{locate_sourcemap_reference_slice, SourceMapRef}; @@ -128,7 +128,7 @@ impl<'a> Iterator for Lines<'a> { /// This type is used to implement fairly efficient source mapping /// operations. pub struct SourceView { - pub(crate) source: Arc, + pub(crate) source: BytesStr, processed_until: AtomicUsize, lines: Mutex>, } @@ -159,7 +159,7 @@ impl PartialEq for SourceView { impl SourceView { /// Creates an optimized view of a given source. - pub fn new(source: Arc) -> SourceView { + pub fn new(source: BytesStr) -> SourceView { SourceView { source, processed_until: AtomicUsize::new(0), @@ -168,9 +168,9 @@ impl SourceView { } /// Creates an optimized view from a given source string - pub fn from_string(source: String) -> SourceView { + pub fn from_string(source: BytesStr) -> SourceView { SourceView { - source: source.into(), + source, processed_until: AtomicUsize::new(0), lines: Mutex::new(vec![]), } @@ -263,7 +263,7 @@ impl SourceView { } /// Returns the source. - pub fn source(&self) -> &str { + pub fn source(&self) -> &BytesStr { &self.source } @@ -287,7 +287,7 @@ impl SourceView { &self, token: Token<'map>, minified_name: &str, - ) -> Option<&'map str> { + ) -> Option<&'map BytesStr> { if !is_valid_javascript_identifier(minified_name) { return None; } diff --git a/src/types.rs b/src/types.rs index b56d2d6..f110090 100644 --- a/src/types.rs +++ b/src/types.rs @@ -4,7 +4,6 @@ use std::collections::BTreeSet; use std::fmt; use std::io::{Read, Write}; use std::path::Path; -use std::sync::Arc; use crate::builder::SourceMapBuilder; use crate::decoder::{decode, decode_slice}; @@ -14,6 +13,7 @@ use crate::hermes::SourceMapHermes; use crate::sourceview::SourceView; use crate::utils::{find_common_prefix, greatest_lower_bound}; +use bytes_str::BytesStr; use debugid::DebugId; /// Controls the `SourceMap::rewrite` behavior @@ -109,7 +109,7 @@ impl DecodedMap { col: u32, minified_name: Option<&str>, source_view: Option<&SourceView>, - ) -> Option<&str> { + ) -> Option<&BytesStr> { match *self { DecodedMap::Regular(ref sm) => { sm.get_original_function_name(line, col, minified_name?, source_view?) @@ -269,7 +269,7 @@ impl<'a> Token<'a> { } /// get the source if it exists as string - pub fn get_source(&self) -> Option<&'a str> { + pub fn get_source(&self) -> Option<&'a BytesStr> { if self.raw.src_id == !0 { None } else { @@ -283,7 +283,7 @@ impl<'a> Token<'a> { } /// get the name if it exists as string - pub fn get_name(&self) -> Option<&'a str> { + pub fn get_name(&self) -> Option<&'a BytesStr> { if self.raw.name_id == !0 { None } else { @@ -305,10 +305,10 @@ impl<'a> Token<'a> { /// `(source, src_line, src_col, name)` pub fn to_tuple(&self) -> (&'a str, u32, u32, Option<&'a str>) { ( - self.get_source().unwrap_or(""), + self.get_source().map(|v| &**v).unwrap_or(""), self.get_src_line(), self.get_src_col(), - self.get_name(), + self.get_name().map(|v| &**v), ) } @@ -366,9 +366,9 @@ pub struct SourceIter<'a> { } impl<'a> Iterator for SourceIter<'a> { - type Item = &'a str; + type Item = &'a BytesStr; - fn next(&mut self) -> Option<&'a str> { + fn next(&mut self) -> Option<&'a BytesStr> { self.i.get_source(self.next_idx).inspect(|_| { self.next_idx += 1; }) @@ -382,9 +382,9 @@ pub struct SourceContentsIter<'a> { } impl<'a> Iterator for SourceContentsIter<'a> { - type Item = Option<&'a str>; + type Item = Option<&'a BytesStr>; - fn next(&mut self) -> Option> { + fn next(&mut self) -> Option> { if self.next_idx >= self.i.get_source_count() { None } else { @@ -402,9 +402,9 @@ pub struct NameIter<'a> { } impl<'a> Iterator for NameIter<'a> { - type Item = &'a str; + type Item = &'a BytesStr; - fn next(&mut self) -> Option<&'a str> { + fn next(&mut self) -> Option<&'a BytesStr> { self.i.get_name(self.next_idx).inspect(|_| { self.next_idx += 1; }) @@ -422,7 +422,7 @@ impl fmt::Display for Token<'_> { write!( f, "{}:{}:{}{}", - self.get_source().unwrap_or(""), + self.get_source().map(|v| &**v).unwrap_or(""), self.get_src_line(), self.get_src_col(), self.get_name() @@ -469,7 +469,7 @@ impl<'a> Iterator for SourceMapSectionIter<'a> { /// Represents a sourcemap index in memory #[derive(Debug, Clone, PartialEq)] pub struct SourceMapIndex { - file: Option, + file: Option, sections: Vec, x_facebook_offsets: Option>>, x_metro_module_paths: Option>, @@ -483,12 +483,12 @@ pub struct SourceMapIndex { /// rejected with an error on reading. #[derive(Clone, Debug, PartialEq)] pub struct SourceMap { - pub(crate) file: Option>, + pub(crate) file: Option, pub(crate) tokens: Vec, - pub(crate) names: Vec>, - pub(crate) source_root: Option>, - pub(crate) sources: Vec>, - pub(crate) sources_prefixed: Option>>, + pub(crate) names: Vec, + pub(crate) source_root: Option, + pub(crate) sources: Vec, + pub(crate) sources_prefixed: Option>, pub(crate) sources_content: Vec>, pub(crate) ignore_list: BTreeSet, pub(crate) debug_id: Option, @@ -501,7 +501,7 @@ impl SourceMap { /// sourcemap is encountered an error is returned. /// /// ```rust - /// use sourcemap::SourceMap; + /// use swc_sourcemap::SourceMap; /// let input: &[_] = b"{ /// \"version\":3, /// \"sources\":[\"coolstuff.js\"], @@ -531,7 +531,7 @@ impl SourceMap { /// expanded. /// /// ```rust - /// # use sourcemap::SourceMap; + /// # use swc_sourcemap::SourceMap; /// # let input: &[_] = b"{ /// # \"version\":3, /// # \"sources\":[\"coolstuff.js\"], @@ -549,7 +549,7 @@ impl SourceMap { /// Encode a sourcemap into a data url. /// /// ```rust - /// # use sourcemap::SourceMap; + /// # use swc_sourcemap::SourceMap; /// # let input: &[_] = b"{ /// # \"version\":3, /// # \"sources\":[\"coolstuff.js\"], @@ -575,7 +575,7 @@ impl SourceMap { /// sourcemap is encountered an error is returned. /// /// ```rust - /// use sourcemap::SourceMap; + /// use swc_sourcemap::SourceMap; /// let input: &[_] = b"{ /// \"version\":3, /// \"sources\":[\"coolstuff.js\"], @@ -600,11 +600,11 @@ impl SourceMap { /// - `sources_content` optional source contents /// - `ignore_list` optional list of source indexes for devtools to ignore pub fn new( - file: Option>, + file: Option, mut tokens: Vec, - names: Vec>, - sources: Vec>, - sources_content: Option>>>, + names: Vec, + sources: Vec, + sources_content: Option>>, ) -> SourceMap { tokens.sort_unstable_by_key(|t| (t.dst_line, t.dst_col)); SourceMap { @@ -635,21 +635,21 @@ impl SourceMap { } /// Returns the embedded filename in case there is one. - pub fn get_file(&self) -> Option<&str> { - self.file.as_deref() + pub fn get_file(&self) -> Option<&BytesStr> { + self.file.as_ref() } /// Sets a new value for the file. - pub fn set_file>>(&mut self, value: Option) { + pub fn set_file>(&mut self, value: Option) { self.file = value.map(Into::into); } /// Returns the embedded source_root in case there is one. - pub fn get_source_root(&self) -> Option<&str> { - self.source_root.as_deref() + pub fn get_source_root(&self) -> Option<&BytesStr> { + self.source_root.as_ref() } - fn prefix_source(source_root: &str, source: &str) -> Arc { + fn prefix_source(source_root: &BytesStr, source: &BytesStr) -> BytesStr { let source_root = source_root.strip_suffix('/').unwrap_or(source_root); let is_valid = !source.is_empty() && (source.starts_with('/') @@ -657,17 +657,17 @@ impl SourceMap { || source.starts_with("https:")); if is_valid { - source.into() + source.clone() } else { format!("{source_root}/{source}").into() } } /// Sets a new value for the source_root. - pub fn set_source_root>>(&mut self, value: Option) { + pub fn set_source_root>(&mut self, value: Option) { self.source_root = value.map(Into::into); - match self.source_root.as_deref().filter(|rs| !rs.is_empty()) { + match self.source_root.as_ref().filter(|rs| !rs.is_empty()) { Some(source_root) => { let sources_prefixed = self .sources @@ -744,7 +744,7 @@ impl SourceMap { col: u32, minified_name: &str, sv: &SourceView, - ) -> Option<&str> { + ) -> Option<&BytesStr> { self.lookup_token(line, col) .and_then(|token| sv.get_original_function_name(token, minified_name)) } @@ -755,22 +755,22 @@ impl SourceMap { } /// Looks up a source for a specific index. - pub fn get_source(&self, idx: u32) -> Option<&str> { + pub fn get_source(&self, idx: u32) -> Option<&BytesStr> { let sources = self.sources_prefixed.as_deref().unwrap_or(&self.sources); - sources.get(idx as usize).map(|x| &x[..]) + sources.get(idx as usize) } /// Sets a new source value for an index. This cannot add new /// sources. /// /// This panics if a source is set that does not exist. - pub fn set_source(&mut self, idx: u32, value: &str) { - self.sources[idx as usize] = value.into(); + pub fn set_source(&mut self, idx: u32, value: BytesStr) { + self.sources[idx as usize] = value.clone(); if let Some(sources_prefixed) = self.sources_prefixed.as_mut() { // If sources_prefixed is `Some`, we must have a nonempty `source_root`. sources_prefixed[idx as usize] = - Self::prefix_source(self.source_root.as_deref().unwrap(), value); + Self::prefix_source(self.source_root.as_ref().unwrap(), &value); } } @@ -790,7 +790,7 @@ impl SourceMap { } /// Looks up the content for a source. - pub fn get_source_contents(&self, idx: u32) -> Option<&str> { + pub fn get_source_contents(&self, idx: u32) -> Option<&BytesStr> { self.sources_content .get(idx as usize) .and_then(Option::as_ref) @@ -798,11 +798,11 @@ impl SourceMap { } /// Sets source contents for a source. - pub fn set_source_contents(&mut self, idx: u32, value: Option<&str>) { + pub fn set_source_contents(&mut self, idx: u32, value: Option) { if self.sources_content.len() != self.sources.len() { self.sources_content.resize(self.sources.len(), None); } - self.sources_content[idx as usize] = value.map(|x| SourceView::from_string(x.to_string())); + self.sources_content[idx as usize] = value.map(SourceView::from_string); } /// Iterates over all source contents @@ -832,8 +832,8 @@ impl SourceMap { } /// Looks up a name for a specific index. - pub fn get_name(&self, idx: u32) -> Option<&str> { - self.names.get(idx as usize).map(|x| &x[..]) + pub fn get_name(&self, idx: u32) -> Option<&BytesStr> { + self.names.get(idx as usize) } /// Removes all names from the sourcemap. @@ -849,7 +849,7 @@ impl SourceMap { /// slightly compress sourcemaps if certain data is not wanted. /// /// ```rust - /// use sourcemap::{SourceMap, RewriteOptions}; + /// use swc_sourcemap::{SourceMap, RewriteOptions}; /// # let input: &[_] = b"{ /// # \"version\":3, /// # \"sources\":[\"coolstuff.js\"], @@ -871,7 +871,7 @@ impl SourceMap { self, options: &RewriteOptions<'_>, ) -> Result<(SourceMap, Vec)> { - let mut builder = SourceMapBuilder::new(self.get_file()); + let mut builder = SourceMapBuilder::new(self.get_file().cloned()); builder.set_debug_id(self.debug_id); for token in self.tokens() { @@ -880,8 +880,10 @@ impl SourceMap { && options.with_source_contents && !builder.has_source_contents(raw.src_id) { - builder - .set_source_contents(raw.src_id, self.get_source_contents(token.get_src_id())); + builder.set_source_contents( + raw.src_id, + self.get_source_contents(token.get_src_id()).cloned(), + ); } } @@ -1106,7 +1108,7 @@ impl SourceMapIndex { /// /// - `file`: an optional filename of the index /// - `sections`: a vector of source map index sections - pub fn new(file: Option, sections: Vec) -> SourceMapIndex { + pub fn new(file: Option, sections: Vec) -> SourceMapIndex { SourceMapIndex { file, sections, @@ -1124,7 +1126,7 @@ impl SourceMapIndex { /// - `x_facebook_offsets`: a vector of facebook offsets /// - `x_metro_module_paths`: a vector of metro module paths pub fn new_ram_bundle_compatible( - file: Option, + file: Option, sections: Vec, x_facebook_offsets: Option>>, x_metro_module_paths: Option>, @@ -1154,13 +1156,13 @@ impl SourceMapIndex { } /// Returns the embedded filename in case there is one. - pub fn get_file(&self) -> Option<&str> { - self.file.as_ref().map(|x| &x[..]) + pub fn get_file(&self) -> Option<&BytesStr> { + self.file.as_ref() } /// Sets a new value for the file. - pub fn set_file(&mut self, value: Option<&str>) { - self.file = value.map(str::to_owned); + pub fn set_file(&mut self, value: Option) { + self.file = value; } /// Returns the number of sections in this index @@ -1200,7 +1202,7 @@ impl SourceMapIndex { col: u32, minified_name: &str, sv: &SourceView, - ) -> Option<&str> { + ) -> Option<&BytesStr> { self.lookup_token(line, col) .and_then(|token| sv.get_original_function_name(token, minified_name)) } @@ -1224,7 +1226,7 @@ impl SourceMapIndex { /// Flattens an indexed sourcemap into a regular one. This requires /// that all referenced sourcemaps are attached. pub fn flatten(&self) -> Result { - let mut builder = SourceMapBuilder::new(self.get_file()); + let mut builder = SourceMapBuilder::new(self.get_file().cloned()); for section in self.sections() { let (off_line, off_col) = section.get_offset(); @@ -1249,12 +1251,12 @@ impl SourceMapIndex { map.sources().zip(map.source_contents()).enumerate() { debug_assert_eq!(original_id, src_id_map.len()); - let src_id = builder.add_source(source); + let src_id = builder.add_source(source.clone()); src_id_map.push(src_id); if let Some(contents) = contents { - builder.set_source_contents(src_id, Some(contents)); + builder.set_source_contents(src_id, Some(contents.clone())); } } @@ -1262,7 +1264,7 @@ impl SourceMapIndex { for (original_id, name) in map.names().enumerate() { debug_assert_eq!(original_id, name_id_map.len()); - let name_id = builder.add_name(name); + let name_id = builder.add_name(name.clone()); name_id_map.push(name_id); } @@ -1625,7 +1627,7 @@ mod tests { fn test_adjust_sections_offset_rows_basic() { // Create a sourcemap index with sections starting at (0, 0) and (10, 0) let mut smi = SourceMapIndex::new( - Some("test.js".to_string()), + Some("test.js".into()), vec![ SourceMapSection::new((0, 0), None, None), SourceMapSection::new((10, 0), None, None), @@ -1639,7 +1641,7 @@ mod tests { assert_eq!( smi, SourceMapIndex::new( - Some("test.js".to_string()), + Some("test.js".into()), vec![ SourceMapSection::new((1, 0), None, None), SourceMapSection::new((11, 0), None, None), @@ -1652,7 +1654,7 @@ mod tests { fn test_adjust_sections_offset_rows_zero() { // Create a sourcemap index with sections starting at (0, 0) and (10, 0) let mut smi = SourceMapIndex::new( - Some("test.js".to_string()), + Some("test.js".into()), vec![ SourceMapSection::new((0, 0), None, None), SourceMapSection::new((10, 0), None, None), @@ -1666,7 +1668,7 @@ mod tests { assert_eq!( smi, SourceMapIndex::new( - Some("test.js".to_string()), + Some("test.js".into()), vec![ SourceMapSection::new((0, 0), None, None), SourceMapSection::new((10, 0), None, None), @@ -1679,7 +1681,7 @@ mod tests { fn test_adjust_sections_offset_rows_multiple_sections() { // Create a sourcemap index with multiple sections let mut smi = SourceMapIndex::new( - Some("test.js".to_string()), + Some("test.js".into()), vec![ SourceMapSection::new((0, 0), None, None), SourceMapSection::new((10, 0), None, None), @@ -1695,7 +1697,7 @@ mod tests { assert_eq!( smi, SourceMapIndex::new( - Some("test.js".to_string()), + Some("test.js".into()), vec![ SourceMapSection::new((1, 0), None, None), SourceMapSection::new((11, 0), None, None), @@ -1710,7 +1712,7 @@ mod tests { fn test_adjust_sections_offset_rows_overflow() { // Create a sourcemap index with a section at u32::MAX let mut smi = SourceMapIndex::new( - Some("test.js".to_string()), + Some("test.js".into()), vec![ SourceMapSection::new((0, 0), None, None), SourceMapSection::new((u32::MAX, 0), None, None), @@ -1731,7 +1733,7 @@ mod tests { fn test_adjust_sections_offset_rows_partial_overflow() { // Create a sourcemap index with multiple sections, one at u32::MAX let mut smi = SourceMapIndex::new( - Some("test.js".to_string()), + Some("test.js".into()), vec![ SourceMapSection::new((0, 0), None, None), SourceMapSection::new((10, 0), None, None), @@ -1754,7 +1756,7 @@ mod tests { fn test_adjust_sections_offset_rows_large_amount() { // Create a sourcemap index with sections let mut smi = SourceMapIndex::new( - Some("test.js".to_string()), + Some("test.js".into()), vec![ SourceMapSection::new((0, 0), None, None), SourceMapSection::new((10, 0), None, None), @@ -1767,7 +1769,7 @@ mod tests { assert_eq!( smi, SourceMapIndex::new( - Some("test.js".to_string()), + Some("test.js".into()), vec![ SourceMapSection::new((1_000_000, 0), None, None), SourceMapSection::new((1_000_010, 0), None, None), @@ -1780,7 +1782,7 @@ mod tests { fn adjust_sections_offset_rows_large_amount_overflow() { // Create a sourcemap index with a section at a positive amount let mut smi = SourceMapIndex::new( - Some("test.js".to_string()), + Some("test.js".into()), vec![ SourceMapSection::new((0, 0), None, None), SourceMapSection::new((10, 0), None, None), @@ -1800,16 +1802,13 @@ mod tests { #[test] fn adjust_sections_offset_rows_no_sections() { // Create a sourcemap index with no sections - let mut smi = SourceMapIndex::new(Some("test.js".to_string()), vec![]); + let mut smi = SourceMapIndex::new(Some("test.js".into()), vec![]); // An adjustment by 1 should return true and no-op assert!(smi.adjust_sections_offset_rows(1)); // The sourcemap index should remain unchanged - assert_eq!( - smi, - SourceMapIndex::new(Some("test.js".to_string()), vec![]) - ); + assert_eq!(smi, SourceMapIndex::new(Some("test.js".into()), vec![])); } mod prop { diff --git a/src/utils.rs b/src/utils.rs index bd911c2..8635aa3 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -81,7 +81,7 @@ pub fn find_common_prefix<'a, I: Iterator>(iter: I) -> Option = vec![]; diff --git a/tests/test_decoder.rs b/tests/test_decoder.rs index 9f89802..da1127e 100644 --- a/tests/test_decoder.rs +++ b/tests/test_decoder.rs @@ -1,7 +1,7 @@ use std::io; use std::io::BufRead; -use sourcemap::{decode_data_url, DecodedMap, SourceMap, Token}; +use swc_sourcemap::{decode_data_url, DecodedMap, SourceMap, Token}; #[test] fn test_no_header() { diff --git a/tests/test_detector.rs b/tests/test_detector.rs index 69346d1..1fe59a8 100644 --- a/tests/test_detector.rs +++ b/tests/test_detector.rs @@ -1,4 +1,4 @@ -use sourcemap::{is_sourcemap_slice, locate_sourcemap_reference, SourceMapRef}; +use swc_sourcemap::{is_sourcemap_slice, locate_sourcemap_reference, SourceMapRef}; #[test] fn test_basic_locate() { diff --git a/tests/test_encoder.rs b/tests/test_encoder.rs index b67f087..26c594d 100644 --- a/tests/test_encoder.rs +++ b/tests/test_encoder.rs @@ -1,4 +1,4 @@ -use sourcemap::SourceMap; +use swc_sourcemap::SourceMap; #[test] fn test_basic_sourcemap() { diff --git a/tests/test_hermes.rs b/tests/test_hermes.rs index 37583d9..dc840b8 100644 --- a/tests/test_hermes.rs +++ b/tests/test_hermes.rs @@ -1,4 +1,4 @@ -use sourcemap::SourceMapHermes; +use swc_sourcemap::SourceMapHermes; #[test] fn test_react_native_hermes() { @@ -11,20 +11,29 @@ fn test_react_native_hermes() { sm.lookup_token(0, 11939).unwrap().to_tuple(), ("module.js", 1, 10, None) ); - assert_eq!(sm.get_original_function_name(11939), Some("foo")); + assert_eq!( + sm.get_original_function_name(11939).map(|v| &**v), + Some("foo") + ); // at anonymous (address at unknown:1:11857) assert_eq!( sm.lookup_token(0, 11857).unwrap().to_tuple(), ("input.js", 2, 0, None) ); - assert_eq!(sm.get_original_function_name(11857), Some("")); + assert_eq!( + sm.get_original_function_name(11857).map(|v| &**v), + Some("") + ); assert_eq!( sm.lookup_token(0, 11947).unwrap().to_tuple(), ("module.js", 1, 4, None) ); - assert_eq!(sm.get_original_function_name(11947), Some("foo")); + assert_eq!( + sm.get_original_function_name(11947).map(|v| &**v), + Some("foo") + ); } #[test] @@ -39,10 +48,13 @@ fn test_react_native_metro() { // at foo (output.js:1289:11) let token = sm.lookup_token(1288, 10).unwrap(); assert_eq!(token.to_tuple(), ("module.js", 1, 10, None)); - assert_eq!(sm.get_scope_for_token(token), Some("foo")); + assert_eq!(sm.get_scope_for_token(token).map(|v| &**v), Some("foo")); // at output.js:1280:19 let token = sm.lookup_token(1279, 18).unwrap(); assert_eq!(token.to_tuple(), ("input.js", 2, 0, None)); - assert_eq!(sm.get_scope_for_token(token), Some("")); + assert_eq!( + sm.get_scope_for_token(token).map(|v| &**v), + Some("") + ); } diff --git a/tests/test_index.rs b/tests/test_index.rs index b398357..45358d0 100644 --- a/tests/test_index.rs +++ b/tests/test_index.rs @@ -1,5 +1,5 @@ -use sourcemap::{DecodedMap, SourceMapIndex}; use std::collections::HashMap; +use swc_sourcemap::{DecodedMap, SourceMapIndex}; #[test] fn test_basic_indexed_sourcemap() { @@ -61,9 +61,9 @@ fn test_basic_indexed_sourcemap() { }; let contents = { let filename = map.get_source(0).unwrap(); - raw_files[filename] + raw_files[&**filename] }; - map.set_source_contents(0, Some(contents)); + map.set_source_contents(0, Some(contents.into())); } let flat_map = ism.flatten().unwrap(); @@ -72,7 +72,7 @@ fn test_basic_indexed_sourcemap() { println!("{}", String::from_utf8(out).unwrap()); for token in flat_map.tokens() { - let src = &files[token.get_source().unwrap()]; + let src = &files[&**token.get_source().unwrap()]; if let Some(name) = token.get_name() { let line = src[token.get_src_line() as usize]; let idx = token.get_src_col() as usize; @@ -82,7 +82,7 @@ fn test_basic_indexed_sourcemap() { } for (src_id, filename) in flat_map.sources().enumerate() { - let ref_contents = &files[filename]; + let ref_contents = &files[&**filename]; let contents: Vec<_> = flat_map .get_source_contents(src_id as u32) .unwrap_or_else(|| panic!("no source for {}", filename)) diff --git a/tests/test_namemap.rs b/tests/test_namemap.rs index 19c256e..4808155 100644 --- a/tests/test_namemap.rs +++ b/tests/test_namemap.rs @@ -1,4 +1,4 @@ -use sourcemap::{SourceMap, SourceView}; +use swc_sourcemap::{SourceMap, SourceView}; #[test] fn test_basic_name_mapping() { @@ -8,7 +8,7 @@ fn test_basic_name_mapping() { let sm = SourceMap::from_reader(input).unwrap(); let name = sm.get_original_function_name(0, 107, "e", &sv); - assert_eq!(name, Some("onFailure")); + assert_eq!(name.map(|v| &**v), Some("onFailure")); // a stacktrae let locs = &[ @@ -19,7 +19,7 @@ fn test_basic_name_mapping() { for &(line, col, minified_name, original_name) in locs { let name = sm.get_original_function_name(line, col, minified_name, &sv); - assert_eq!(name, Some(original_name)); + assert_eq!(name.map(|v| &**v), Some(original_name)); } } @@ -40,6 +40,6 @@ fn test_unicode_mapping() { for &(line, col, minified_name, original_name_match) in locs { let name = sm.get_original_function_name(line, col, minified_name, &sv); - assert_eq!(name, original_name_match); + assert_eq!(name.map(|v| &**v), original_name_match); } } diff --git a/tests/test_regular.rs b/tests/test_regular.rs index 93db0d4..73b1570 100644 --- a/tests/test_regular.rs +++ b/tests/test_regular.rs @@ -1,4 +1,4 @@ -use sourcemap::{SourceMap, SourceMapBuilder}; +use swc_sourcemap::{SourceMap, SourceMapBuilder}; #[test] fn test_basic_sourcemap() { @@ -39,7 +39,7 @@ fn test_basic_sourcemap() { #[test] fn test_basic_range() { let mut b = SourceMapBuilder::new(None); - let id = b.add_source("input.js"); + let id = b.add_source("input.js".into()); b.add_raw(1, 0, 2, 2, Some(id), None, true); let sm = b.into_sourcemap(); From 62102ed56d8c8a4c1d2544535b4a300921eded7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Mon, 9 Jun 2025 16:06:20 -0700 Subject: [PATCH 05/12] fix: Fix `sources` field (#2) --- Cargo.toml | 2 +- src/lazy/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 24a73f7..5c4b083 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "swc_sourcemap" -version = "9.3.0" +version = "9.3.1" authors = ["Sentry ", "swc-project "] keywords = ["javascript", "sourcemap", "sourcemaps"] description = "Forked from https://github.com/getsentry/rust-sourcemap" diff --git a/src/lazy/mod.rs b/src/lazy/mod.rs index ca0a55c..26b52f3 100644 --- a/src/lazy/mod.rs +++ b/src/lazy/mod.rs @@ -24,7 +24,7 @@ pub struct RawSourceMap<'a> { pub(crate) version: Option, #[serde(default, borrow, skip_serializing_if = "Option::is_none")] pub(crate) file: Option>, - #[serde(borrow, skip_serializing_if = "MaybeRawValue::is_empty")] + #[serde(borrow)] pub(crate) sources: MaybeRawValue<'a, Vec>>, #[serde(default, borrow, skip_serializing_if = "Option::is_none")] pub(crate) source_root: Option>, From 72e82b1e92f17a093bf4eb680dcc05c834079155 Mon Sep 17 00:00:00 2001 From: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com> Date: Wed, 11 Jun 2025 19:44:19 +0200 Subject: [PATCH 06/12] feat: Add `adjust_mappings_from_multiple` (#3) --- Cargo.toml | 2 +- src/lazy/mod.rs | 65 ++++- src/types.rs | 249 ++++++++++++++++++ .../adjust_mappings_from_multiple/bundle.js | 20 ++ .../bundle.js.map | 25 ++ .../merged.js.map | 1 + .../sourcemapped.js | 4 + .../sourcemapped.js.map | 1 + .../sourcemapped.ts | 6 + 9 files changed, 365 insertions(+), 8 deletions(-) create mode 100644 tests/fixtures/adjust_mappings_from_multiple/bundle.js create mode 100644 tests/fixtures/adjust_mappings_from_multiple/bundle.js.map create mode 100644 tests/fixtures/adjust_mappings_from_multiple/merged.js.map create mode 100644 tests/fixtures/adjust_mappings_from_multiple/sourcemapped.js create mode 100644 tests/fixtures/adjust_mappings_from_multiple/sourcemapped.js.map create mode 100644 tests/fixtures/adjust_mappings_from_multiple/sourcemapped.ts diff --git a/Cargo.toml b/Cargo.toml index 5c4b083..527f382 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "swc_sourcemap" -version = "9.3.1" +version = "9.3.2" authors = ["Sentry ", "swc-project "] keywords = ["javascript", "sourcemap", "sourcemaps"] description = "Forked from https://github.com/getsentry/rust-sourcemap" diff --git a/src/lazy/mod.rs b/src/lazy/mod.rs index 26b52f3..1b235c6 100644 --- a/src/lazy/mod.rs +++ b/src/lazy/mod.rs @@ -132,19 +132,41 @@ where } } +impl<'a, T> MaybeRawValue<'a, T> +where + T: Deserialize<'a>, + T: Default, +{ + pub fn as_data(&mut self) -> &mut T { + match self { + MaybeRawValue::RawValue(s) => { + *self = MaybeRawValue::Data( + serde_json::from_str(s.get()).expect("Failed to convert RawValue to Data"), + ); + if let MaybeRawValue::Data(data) = self { + data + } else { + unreachable!() + } + } + MaybeRawValue::Data(data) => data, + } + } +} + type Str = BytesStr; type StrValue<'a> = MaybeRawValue<'a, Str>; #[derive(Debug)] pub struct SourceMap<'a> { - file: Option>, - tokens: Vec, - names: MaybeRawValue<'a, Vec>>, - source_root: Option>, - sources: MaybeRawValue<'a, Vec>>, - sources_content: MaybeRawValue<'a, Vec>>>, - ignore_list: Option>>, + pub(crate) file: Option>, + pub(crate) tokens: Vec, + pub(crate) names: MaybeRawValue<'a, Vec>>, + pub(crate) source_root: Option>, + pub(crate) sources: MaybeRawValue<'a, Vec>>, + pub(crate) sources_content: MaybeRawValue<'a, Vec>>>, + pub(crate) ignore_list: Option>>, } #[derive(Debug)] @@ -444,6 +466,35 @@ impl<'a> SourceMap<'a> { ignore_list: self.ignore_list, } } + + pub fn file(&mut self) -> Option<&BytesStr> { + self.file.as_mut().map(|f| &*f.as_data()) + } + + pub fn sources(&mut self) -> impl Iterator + use<'_, 'a> { + self.sources.as_data().iter_mut().map(|d| &*d.as_data()) + } + + pub fn get_source(&mut self, src_id: u32) -> Option<&BytesStr> { + self.sources + .as_data() + .get_mut(src_id as usize) + .map(|d| &*d.as_data()) + } + + pub fn get_name(&mut self, src_id: u32) -> Option<&BytesStr> { + self.names + .as_data() + .get_mut(src_id as usize) + .map(|d| &*d.as_data()) + } + + pub fn get_source_contents(&mut self, src_id: u32) -> Option<&BytesStr> { + self.sources_content + .as_data() + .get_mut(src_id as usize) + .and_then(|d| d.as_mut().map(|d| &*d.as_data())) + } } impl<'a> SourceMapIndex<'a> { diff --git a/src/types.rs b/src/types.rs index f110090..0580a6d 100644 --- a/src/types.rs +++ b/src/types.rs @@ -15,6 +15,7 @@ use crate::utils::{find_common_prefix, greatest_lower_bound}; use bytes_str::BytesStr; use debugid::DebugId; +use rustc_hash::FxHashSet; /// Controls the `SourceMap::rewrite` behavior /// @@ -940,6 +941,14 @@ impl SourceMap { Cow::Borrowed(&adjustment.tokens), ); } + + /// Perform a similar operation as [`Self::adjust_mappings`], but by rewriting the last + /// sourcemap as opposed to the input source map: + /// + /// `transform.js.map.adjust_mappings_from_multiple([foo.js.map, bar.js.map])` + pub fn adjust_mappings_from_multiple(self, adjustments: Vec) -> Self { + adjust_mappings_from_multiple(self, adjustments) + } } pub(crate) fn adjust_mappings( @@ -1076,6 +1085,207 @@ pub(crate) fn adjust_mappings( new_tokens } +pub fn adjust_mappings_from_multiple( + mut this: SourceMap, + mut input_maps: Vec, +) -> SourceMap { + // Helper struct that makes it easier to compare tokens by the start and end + // of the range they cover. + #[derive(Debug, Clone, Copy)] + struct Range<'a> { + start: (u32, u32), + end: (u32, u32), + value: &'a RawToken, + map_idx: u32, + } + + /// Turns a list of tokens into a list of ranges, using the provided `key` function to determine the order of the tokens. + #[allow(clippy::ptr_arg)] + fn create_ranges( + tokens: &mut [(u32, RawToken)], + key: fn(&RawToken) -> (u32, u32), + ) -> Vec> { + tokens.sort_unstable_by_key(|(_, t)| key(t)); + + let mut token_iter = tokens.iter().peekable(); + let mut ranges = Vec::new(); + + while let Some((map_idx, t)) = token_iter.next() { + let start = key(t); + let next_start = token_iter + .peek() + .map_or((u32::MAX, u32::MAX), |(_, t)| key(t)); + // A token extends either to the start of the next token or the end of the line, whichever comes sooner + let end = std::cmp::min(next_start, (start.0, u32::MAX)); + ranges.push(Range { + start, + end, + value: t, + map_idx: *map_idx, + }); + } + + ranges + } + + // Turn `self.tokens` and `adjustment.tokens` into vectors of ranges so we have easy access to + // both start and end. + // We want to compare `self` and `adjustment` tokens by line/column numbers in the "original source" file. + // These line/column numbers are the `dst_line/col` for + // the `self` tokens and `src_line/col` for the `adjustment` tokens. + let mut input_tokens = input_maps + .iter_mut() + .enumerate() + .flat_map(|(i, map)| { + std::mem::take(&mut map.tokens) + .into_iter() + .map(move |t| ((i + 1) as u32, t)) + }) + .collect::>(); + let input_ranges = create_ranges(&mut input_tokens[..], |t| (t.dst_line, t.dst_col)); + let mut self_tokens = std::mem::take(&mut this.tokens) + .into_iter() + .map(|t| (0u32, t)) + .collect::>(); + let self_ranges = create_ranges(&mut self_tokens[..], |t| (t.src_line, t.src_col)); + + let mut input_ranges_iter = input_ranges.iter(); + let mut input_range = match input_ranges_iter.next() { + Some(r) => Some(r), + None => return this, + }; + + let covered_input_files = input_maps + .iter_mut() + .flat_map(|m| m.file().cloned()) + .collect::>(); + + let mut new_map = SourceMapBuilder::new(None); + let mut add_mapping = |input_maps: &mut Vec>, + map_idx: u32, + dst_line: u32, + dst_col: u32, + src_line: u32, + src_col: u32, + src_id: u32, + name_id: u32, + is_range: bool| { + let (src_id, name) = if map_idx == 0 { + let src = this.get_source(src_id).cloned(); + ( + src.map(|src| { + let src_id = new_map.add_source(src); + new_map.set_source_contents(src_id, this.get_source_contents(src_id).cloned()); + src_id + }), + this.get_name(name_id).cloned(), + ) + } else { + let this = &mut input_maps[(map_idx - 1) as usize]; + let src = this.get_source(src_id).cloned(); + ( + src.map(|src| { + let src_id = new_map.add_source(src); + new_map.set_source_contents(src_id, this.get_source_contents(src_id).cloned()); + src_id + }), + this.get_name(name_id).cloned(), + ) + }; + let name_id = name.map(|name| new_map.add_source(name)); + new_map.add_raw( + dst_line, dst_col, src_line, src_col, src_id, name_id, is_range, + ); + }; + + // Iterate over `self_ranges` (sorted by `src_line/col`). For each such range, consider + // all `self_ranges` which overlap with it. + for &self_range in &self_ranges { + // The `self_range` offsets lines and columns by a certain amount. All `input_ranges` + // it covers will get the same offset. + let (line_diff, col_diff) = ( + self_range.value.dst_line as i32 - self_range.value.src_line as i32, + self_range.value.dst_col as i32 - self_range.value.src_col as i32, + ); + + // Skip `input_ranges` that are entirely before the `_range`. + while input_range.is_some_and(|input_range| input_range.end <= self_range.start) { + input_range = input_ranges_iter.next(); + } + // At this point `self_range.end` > `input_range.start` + + if input_range.is_none_or(|input_range| { + self_range.start >= input_range.end + || this.get_source(self_range.value.src_id).is_none_or(|src| { + Some(src) != input_maps[(input_range.map_idx - 1) as usize].file() + }) + }) { + // No input range matches this range, keep the mapping though if this file isn't covered + // by any input sourcemap + if this + .get_source(self_range.value.src_id) + .is_none_or(|f| !covered_input_files.contains(f)) + { + add_mapping( + &mut input_maps, + 0, + self_range.value.dst_line, + self_range.value.dst_col, + self_range.value.src_line, + self_range.value.src_col, + self_range.value.src_id, + self_range.value.name_id, + self_range.value.is_range, + ); + } + } else { + let mut input_range_value = input_range.unwrap(); + // Iterate over `input_range` that fall at least partially within the `self_ranges`. + while input_range_value.start < self_range.end { + // If `input_range` started before `self_range`, cut off the token's start. + let (dst_line, dst_col) = std::cmp::max(input_range_value.start, self_range.start); + add_mapping( + &mut input_maps, + input_range_value.map_idx, + (dst_line as i32 + line_diff) as u32, + (dst_col as i32 + col_diff) as u32, + input_range_value.value.src_line, + input_range_value.value.src_col, + input_range_value.value.src_id, + input_range_value.value.name_id, + input_range_value.value.is_range, + ); + + if input_range_value.end >= self_range.end { + // There are surely no more `input_ranges` for this `self_range`. + // Break the loop without advancing the `input_range`. + break; + } else { + // Advance the `input_range`. + match input_ranges_iter.next() { + Some(r) => { + input_range_value = r; + input_range = Some(r); + } + None => { + input_range = None; + break; + } + } + } + } + } + } + + let mut new_map = new_map.into_sourcemap(); + + new_map + .tokens + .sort_unstable_by_key(|t| (t.dst_line, t.dst_col)); + + new_map +} + impl SourceMapIndex { /// Creates a sourcemap index from a reader over a JSON stream in UTF-8 /// format. Optionally a "garbage header" as defined by the @@ -1420,6 +1630,8 @@ impl SourceMapSection { mod tests { use std::collections::BTreeSet; + use crate::lazy::MaybeRawValue; + use super::{DecodedMap, RewriteOptions, SourceMap, SourceMapIndex, SourceMapSection}; use debugid::DebugId; @@ -1507,6 +1719,43 @@ mod tests { } } + #[test] + fn adjust_mappings_from_multiple() { + let original_map_file = std::fs::read_to_string( + "tests/fixtures/adjust_mappings_from_multiple/sourcemapped.js.map", + ) + .unwrap(); + + let bundled_map_file = + std::fs::read_to_string("tests/fixtures/adjust_mappings_from_multiple/bundle.js.map") + .unwrap(); + + let mut original_map = crate::lazy::decode(original_map_file.as_bytes()) + .unwrap() + .into_source_map() + .unwrap(); + original_map.file = Some(MaybeRawValue::Data("turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/source_maps/input-source-map-merged/input/sourcemapped.js".into())); + + let bundled_map = match crate::decode(bundled_map_file.as_bytes()).unwrap() { + DecodedMap::Regular(source_map) => source_map, + DecodedMap::Index(source_map_index) => source_map_index.flatten().unwrap(), + DecodedMap::Hermes(_) => unimplemented!(), + }; + // original_map.adjust_mappings(&bundled_map); + let bundled_map = bundled_map.adjust_mappings_from_multiple(vec![original_map]); + + let mut result = vec![]; + bundled_map.to_writer(&mut result).unwrap(); + let result = String::from_utf8(result).unwrap(); + // std::fs::write("tests/fixtures/adjust_mappings_from_multiple/merged.js.map", result).unwrap(); + + let bundled_map_file = + std::fs::read_to_string("tests/fixtures/adjust_mappings_from_multiple/merged.js.map") + .unwrap(); + + assert_eq!(bundled_map_file, result) + } + #[test] fn test_roundtrip() { let sm = br#"{ diff --git a/tests/fixtures/adjust_mappings_from_multiple/bundle.js b/tests/fixtures/adjust_mappings_from_multiple/bundle.js new file mode 100644 index 0000000..ccaa1f8 --- /dev/null +++ b/tests/fixtures/adjust_mappings_from_multiple/bundle.js @@ -0,0 +1,20 @@ +(globalThis.TURBOPACK = globalThis.TURBOPACK || []).push(["output/4c35f_tests_snapshot_source_maps_input-source-map-merged_input_index_e9baadb1.js", { + +"[project]/turbopack/crates/turbopack-tests/tests/snapshot/source_maps/input-source-map-merged/input/index.js [test] (ecmascript)": [((__turbopack_context__) => { +"use strict"; + +// MERGED MODULE: [project]/turbopack/crates/turbopack-tests/tests/snapshot/source_maps/input-source-map-merged/input/index.js [test] (ecmascript) +; +__turbopack_context__.s({}, "[project]/turbopack/crates/turbopack-tests/tests/snapshot/source_maps/input-source-map-merged/input/index.js [test] (ecmascript)"); +// MERGED MODULE: [project]/turbopack/crates/turbopack-tests/tests/snapshot/source_maps/input-source-map-merged/input/sourcemapped.js [test] (ecmascript) +; +function runExternalSourceMapped(fn) { + return fn(); +} //# sourceMappingURL=sourcemapped.js.map +; +runExternalSourceMapped(); +}), ["[project]/turbopack/crates/turbopack-tests/tests/snapshot/source_maps/input-source-map-merged/input/sourcemapped.js [test] (ecmascript)"]] +, +}]); + +//# sourceMappingURL=4c35f_tests_snapshot_source_maps_input-source-map-merged_input_index_e9baadb1.js.map \ No newline at end of file diff --git a/tests/fixtures/adjust_mappings_from_multiple/bundle.js.map b/tests/fixtures/adjust_mappings_from_multiple/bundle.js.map new file mode 100644 index 0000000..eeaae2e --- /dev/null +++ b/tests/fixtures/adjust_mappings_from_multiple/bundle.js.map @@ -0,0 +1,25 @@ +{ + "version": 3, + "sources": [], + "sections": [ + { + "offset": { + "line": 5, + "column": 0 + }, + "map": { + "version": 3, + "sources": [ + "turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/source_maps/input-source-map-merged/input/sourcemapped.js", + "turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/source_maps/input-source-map-merged/input/index.js" + ], + "sourcesContent": [ + "export function runExternalSourceMapped(fn) {\n return fn();\n}\n//# sourceMappingURL=sourcemapped.js.map", + "import { runExternalSourceMapped } from './sourcemapped.js'\n\nrunExternalSourceMapped()\n" + ], + "names": [], + "mappings": ";;;;;AAAO,SAAS,wBAAwB,EAAE;IACtC,OAAO;AACX,EACA,wCAAwC;;ACDxC" + } + } + ] +} diff --git a/tests/fixtures/adjust_mappings_from_multiple/merged.js.map b/tests/fixtures/adjust_mappings_from_multiple/merged.js.map new file mode 100644 index 0000000..cb30ac8 --- /dev/null +++ b/tests/fixtures/adjust_mappings_from_multiple/merged.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["sourcemapped.ts","turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/source_maps/input-source-map-merged/input/index.js"],"sourcesContent":["// Compile with pnpm tsc turbopack/crates/turbopack-tests/tests/snapshot/source_maps/input-source-map-merged/input/sourcemapped.ts --sourceMap --inlineSources --target esnext\n// tsc compile errors can be ignored\ntype Fn = () => T\nexport function runExternalSourceMapped(fn: Fn): T {\n return fn()\n}\n","import { runExternalSourceMapped } from './sourcemapped.js'\n\nrunExternalSourceMapped()\n"],"names":[],"mappings":";;;;;;;;;;AAGM,SAAU,uBAAuB,CAAI,EAAS;IAClD,OAAO,EAAE,EAAE,CAAA;;;ACFb"} \ No newline at end of file diff --git a/tests/fixtures/adjust_mappings_from_multiple/sourcemapped.js b/tests/fixtures/adjust_mappings_from_multiple/sourcemapped.js new file mode 100644 index 0000000..da8a4fd --- /dev/null +++ b/tests/fixtures/adjust_mappings_from_multiple/sourcemapped.js @@ -0,0 +1,4 @@ +export function runExternalSourceMapped(fn) { + return fn(); +} +//# sourceMappingURL=sourcemapped.js.map \ No newline at end of file diff --git a/tests/fixtures/adjust_mappings_from_multiple/sourcemapped.js.map b/tests/fixtures/adjust_mappings_from_multiple/sourcemapped.js.map new file mode 100644 index 0000000..3a41042 --- /dev/null +++ b/tests/fixtures/adjust_mappings_from_multiple/sourcemapped.js.map @@ -0,0 +1 @@ +{"version":3,"file":"sourcemapped.js","sourceRoot":"","sources":["sourcemapped.ts"],"names":[],"mappings":"AAGA,MAAM,UAAU,uBAAuB,CAAI,EAAS;IAClD,OAAO,EAAE,EAAE,CAAA;AACb,CAAC","sourcesContent":["// Compile with pnpm tsc turbopack/crates/turbopack-tests/tests/snapshot/source_maps/input-source-map-merged/input/sourcemapped.ts --sourceMap --inlineSources --target esnext\n// tsc compile errors can be ignored\ntype Fn = () => T\nexport function runExternalSourceMapped(fn: Fn): T {\n return fn()\n}\n"]} \ No newline at end of file diff --git a/tests/fixtures/adjust_mappings_from_multiple/sourcemapped.ts b/tests/fixtures/adjust_mappings_from_multiple/sourcemapped.ts new file mode 100644 index 0000000..409bc83 --- /dev/null +++ b/tests/fixtures/adjust_mappings_from_multiple/sourcemapped.ts @@ -0,0 +1,6 @@ +// Compile with pnpm tsc turbopack/crates/turbopack-tests/tests/snapshot/source_maps/input-source-map-merged/input/sourcemapped.ts --sourceMap --inlineSources --target esnext +// tsc compile errors can be ignored +type Fn = () => T +export function runExternalSourceMapped(fn: Fn): T { + return fn() +} From 6bc7ac9ea26a6b2efd504a4fce4cff2b6b7a2b7b Mon Sep 17 00:00:00 2001 From: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com> Date: Wed, 23 Jul 2025 14:50:09 +0200 Subject: [PATCH 07/12] chore: Fix 1.88.0 clippy lints (#5) cherry pick https://github.com/getsentry/rust-sourcemap/pull/130 Co-authored-by: Sebastian Zivota --- src/errors.rs | 2 +- src/types.rs | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index d833dc3..01342f9 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -125,7 +125,7 @@ impl fmt::Display for Error { Error::InvalidRamBundleEntry => write!(f, "invalid ram bundle module entry"), Error::NotARamBundle => write!(f, "not a ram bundle"), Error::InvalidRangeMappingIndex(err) => write!(f, "invalid range mapping index: {err}"), - Error::InvalidBase64(c) => write!(f, "invalid base64 character: {}", c), + Error::InvalidBase64(c) => write!(f, "invalid base64 character: {c}"), } } } diff --git a/src/types.rs b/src/types.rs index 0580a6d..e1277c0 100644 --- a/src/types.rs +++ b/src/types.rs @@ -564,10 +564,7 @@ impl SourceMap { let mut buf = vec![]; encode(self, &mut buf)?; let b64 = base64_simd::STANDARD.encode_to_string(&buf); - Ok(format!( - "data:application/json;charset=utf-8;base64,{}", - b64 - )) + Ok(format!("data:application/json;charset=utf-8;base64,{b64}")) } /// Creates a sourcemap from a reader over a JSON byte slice in UTF-8 From 34efd0c7c3df55500bb102ed0d842302c0895b56 Mon Sep 17 00:00:00 2001 From: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com> Date: Wed, 23 Jul 2025 14:50:55 +0200 Subject: [PATCH 08/12] fix: `add_source` that should have been `add_name` (#4) A stupid typo --- src/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types.rs b/src/types.rs index e1277c0..76a8e5b 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1189,7 +1189,7 @@ pub fn adjust_mappings_from_multiple( this.get_name(name_id).cloned(), ) }; - let name_id = name.map(|name| new_map.add_source(name)); + let name_id = name.map(|name| new_map.add_name(name)); new_map.add_raw( dst_line, dst_col, src_line, src_col, src_id, name_id, is_range, ); From 68cd09a0da7df6e5a096bae66ae8aff604d6dc2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Wed, 23 Jul 2025 21:54:45 +0900 Subject: [PATCH 09/12] chore: Publish v9.3.3 (#6) --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 527f382..2629ca1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "swc_sourcemap" -version = "9.3.2" +version = "9.3.3" authors = ["Sentry ", "swc-project "] keywords = ["javascript", "sourcemap", "sourcemaps"] description = "Forked from https://github.com/getsentry/rust-sourcemap" From 6dd85181a348318259ea01c2aff2079d817a3a16 Mon Sep 17 00:00:00 2001 From: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com> Date: Thu, 14 Aug 2025 12:40:04 +0200 Subject: [PATCH 10/12] fix: Fix 1.89.0 clippy lints (#132) (#8) cherry-pick https://github.com/getsentry/rust-sourcemap/pull/132 --------- Co-authored-by: Sebastian Zivota --- src/lazy/mod.rs | 2 +- src/ram_bundle.rs | 8 ++++---- src/sourceview.rs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lazy/mod.rs b/src/lazy/mod.rs index 1b235c6..2fecfa8 100644 --- a/src/lazy/mod.rs +++ b/src/lazy/mod.rs @@ -313,7 +313,7 @@ pub struct SourceMapIndex<'a> { sections: Vec>, } -pub fn decode(slice: &[u8]) -> Result { +pub fn decode(slice: &[u8]) -> Result> { let content = strip_junk_header(slice)?; let rsm: RawSourceMap = serde_json::from_slice(content)?; diff --git a/src/ram_bundle.rs b/src/ram_bundle.rs index 8b071e0..d7bd1c0 100644 --- a/src/ram_bundle.rs +++ b/src/ram_bundle.rs @@ -162,7 +162,7 @@ impl<'a> RamBundle<'a> { } /// Looks up a module by ID in the bundle - pub fn get_module(&self, id: usize) -> Result> { + pub fn get_module(&self, id: usize) -> Result>> { match self.repr { RamBundleImpl::Indexed(ref indexed) => indexed.get_module(id), RamBundleImpl::Unbundle(ref file) => file.get_module(id), @@ -185,7 +185,7 @@ impl<'a> RamBundle<'a> { } } /// Returns an iterator over all modules in the bundle - pub fn iter_modules(&self) -> RamBundleModuleIter { + pub fn iter_modules(&self) -> RamBundleModuleIter<'_> { RamBundleModuleIter { range: 0..self.module_count(), ram_bundle: self, @@ -268,7 +268,7 @@ impl UnbundleRamBundle { } /// Looks up a module by ID in the bundle - pub fn get_module(&self, id: usize) -> Result> { + pub fn get_module(&self, id: usize) -> Result>> { match self.modules.get(&id) { Some(data) => Ok(Some(RamBundleModule { id, data })), None => Ok(None), @@ -321,7 +321,7 @@ impl<'a> IndexedRamBundle<'a> { } /// Looks up a module by ID in the bundle - pub fn get_module(&self, id: usize) -> Result> { + pub fn get_module(&self, id: usize) -> Result>> { if id >= self.module_count { return Err(Error::InvalidRamBundleIndex); } diff --git a/src/sourceview.rs b/src/sourceview.rs index 1f9a6a5..064bacd 100644 --- a/src/sourceview.rs +++ b/src/sourceview.rs @@ -258,7 +258,7 @@ impl SourceView { } /// Returns an iterator over all lines. - pub fn lines(&self) -> Lines { + pub fn lines(&self) -> Lines<'_> { Lines { sv: self, idx: 0 } } From c168b3d92d28bf37be14d13d485b27ecf86dc5ac Mon Sep 17 00:00:00 2001 From: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com> Date: Thu, 14 Aug 2025 12:41:11 +0200 Subject: [PATCH 11/12] fix: Use correct id in `set_source_contents` (#7) Caused invalid source content: image --- src/types.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/types.rs b/src/types.rs index 76a8e5b..c2a8b04 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1171,9 +1171,10 @@ pub fn adjust_mappings_from_multiple( let src = this.get_source(src_id).cloned(); ( src.map(|src| { - let src_id = new_map.add_source(src); - new_map.set_source_contents(src_id, this.get_source_contents(src_id).cloned()); - src_id + let new_src_id = new_map.add_source(src); + new_map + .set_source_contents(new_src_id, this.get_source_contents(src_id).cloned()); + new_src_id }), this.get_name(name_id).cloned(), ) @@ -1182,9 +1183,10 @@ pub fn adjust_mappings_from_multiple( let src = this.get_source(src_id).cloned(); ( src.map(|src| { - let src_id = new_map.add_source(src); - new_map.set_source_contents(src_id, this.get_source_contents(src_id).cloned()); - src_id + let new_src_id = new_map.add_source(src); + new_map + .set_source_contents(new_src_id, this.get_source_contents(src_id).cloned()); + new_src_id }), this.get_name(name_id).cloned(), ) From 64fcb47c16547d40fb37bbe4d8790b520716c741 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Thu, 14 Aug 2025 19:44:44 +0900 Subject: [PATCH 12/12] chore: Publish v9.3.4 (#9) --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 2629ca1..bab15e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "swc_sourcemap" -version = "9.3.3" +version = "9.3.4" authors = ["Sentry ", "swc-project "] keywords = ["javascript", "sourcemap", "sourcemaps"] description = "Forked from https://github.com/getsentry/rust-sourcemap"