diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 84d42f44..5921c67e 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 00000000..c8e0c614 --- /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 ad5fdac2..bab15e2c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,11 @@ [package] -name = "sourcemap" -version = "9.3.0" -authors = ["Sentry "] +name = "swc_sourcemap" +version = "9.3.4" +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" @@ -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 } @@ -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 a0f7e6c7..f057ee21 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 0acf8693..34e6a962 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 84965ae3..845ea004 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 87b1ce2e..448982d8 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 9aafdbec..93f8f945 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 32d0bdcc..5f3d8b74 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 132f657d..4d82a1b3 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); @@ -295,7 +295,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/encoder.rs b/src/encoder.rs index c4e5c8cf..f6f95299 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/hermes.rs b/src/hermes.rs index e43eedc7..2e0dd645 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 9185bf5a..4692ff1c 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, Deserializer, Serialize}; @@ -19,7 +20,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 new file mode 100644 index 00000000..2fecfa8b --- /dev/null +++ b/src/lazy/mod.rs @@ -0,0 +1,683 @@ +//! 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}, +}; + +use bitvec::{order::Lsb0, vec::BitVec, view::BitView}; +use bytes_str::BytesStr; +use serde::{Deserialize, Deserializer, Serialize}; +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, skip_serializing_if = "Option::is_none")] + pub(crate) file: Option>, + #[serde(borrow)] + pub(crate) sources: MaybeRawValue<'a, Vec>>, + #[serde(default, borrow, skip_serializing_if = "Option::is_none")] + pub(crate) source_root: Option>, + #[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)] + 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, skip_serializing_if = "Option::is_none")] + 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 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, + 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()) + } +} + +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> { + 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)] +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, + } + } + + 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> { + 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 7aeccd7f..b835956d 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\"], @@ -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/ram_bundle.rs b/src/ram_bundle.rs index 23353714..d7bd1c08 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 699faf52..064bacd0 100644 --- a/src/sourceview.rs +++ b/src/sourceview.rs @@ -1,8 +1,11 @@ use std::fmt; +use std::slice; use std::str; -use std::sync::Arc; +use std::sync::atomic::AtomicUsize; +use std::sync::atomic::Ordering; use std::sync::Mutex; +use bytes_str::BytesStr; use if_chain::if_chain; use crate::detector::{locate_sourcemap_reference_slice, SourceMapRef}; @@ -125,15 +128,17 @@ impl<'a> Iterator for Lines<'a> { /// This type is used to implement fairly efficient source mapping /// operations. pub struct SourceView { - source: Arc, - line_end_offsets: Mutex>, + pub(crate) source: BytesStr, + processed_until: AtomicUsize, + lines: Mutex>, } impl Clone for SourceView { fn clone(&self) -> SourceView { SourceView { source: self.source.clone(), - line_end_offsets: Mutex::new(vec![]), + processed_until: AtomicUsize::new(0), + lines: Mutex::new(vec![]), } } } @@ -154,77 +159,62 @@ 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, - line_end_offsets: Mutex::new(vec![]), + processed_until: AtomicUsize::new(0), + lines: Mutex::new(vec![]), } } /// 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(), - line_end_offsets: Mutex::new(vec![]), + source, + processed_until: AtomicUsize::new(0), + lines: Mutex::new(vec![]), } } /// Returns a requested minified line. pub fn get_line(&self, idx: u32) -> Option<&str> { let idx = idx as usize; - - let get_from_line_ends = |line_ends: &[LineEndOffset]| { - let end = line_ends.get(idx)?.to_end_index(); - let start = if idx == 0 { - 0 - } else { - line_ends[idx - 1].to_start_index() - }; - Some(&self.source[start..end]) - }; - - let mut line_ends = self - .line_end_offsets - .lock() - .unwrap_or_else(|e| e.into_inner()); - - if let Some(line) = get_from_line_ends(&line_ends) { - return Some(line); + { + let lines = self.lines.lock().unwrap(); + if idx < lines.len() { + return Some(lines[idx]); + } } - // check whether we've processed the entire string - the end of the - // last-processed line would be the same as the end of the string - if line_ends - .last() - .is_some_and(|i| i.to_end_index() == self.source.len()) - { + // fetched everything + if self.processed_until.load(Ordering::Relaxed) > self.source.len() { return None; } - let mut rest_offset = line_ends.last().map_or(0, |i| i.to_start_index()); - let mut rest = &self.source[rest_offset..]; + let mut lines = self.lines.lock().unwrap(); let mut done = false; while !done { - let line_term = if let Some(idx) = rest.find(['\n', '\r']) { - rest_offset += idx; - rest = &rest[idx..]; - if rest.starts_with("\r\n") { - LineTerminator::CrLf - } else { - LineTerminator::LfOrCr + let rest = &self.source.as_bytes()[self.processed_until.load(Ordering::Relaxed)..]; + + let rv = if let Some(mut idx) = rest.iter().position(|&x| x == b'\n' || x == b'\r') { + let rv = &rest[..idx]; + if rest[idx] == b'\r' && rest.get(idx + 1) == Some(&b'\n') { + idx += 1; } + self.processed_until.fetch_add(idx + 1, Ordering::Relaxed); + rv } else { - rest_offset += rest.len(); - rest = &rest[rest.len()..]; + self.processed_until + .fetch_add(rest.len() + 1, Ordering::Relaxed); done = true; - LineTerminator::Eof + rest }; - line_ends.push(LineEndOffset::new(rest_offset, line_term)); - rest_offset += line_term as usize; - rest = &rest[line_term as usize..]; - if let Some(line) = get_from_line_ends(&line_ends) { + lines.push(unsafe { + str::from_utf8_unchecked(slice::from_raw_parts(rv.as_ptr(), rv.len())) + }); + if let Some(&line) = lines.get(idx) { return Some(line); } } @@ -273,7 +263,7 @@ impl SourceView { } /// Returns the source. - pub fn source(&self) -> &str { + pub fn source(&self) -> &BytesStr { &self.source } @@ -297,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; } @@ -321,7 +311,7 @@ impl SourceView { /// Returns the number of lines. pub fn line_count(&self) -> usize { self.get_line(!0); - self.line_end_offsets.lock().unwrap().len() + self.lines.lock().unwrap().len() } /// Returns the source map reference in the source view. @@ -330,37 +320,6 @@ impl SourceView { } } -/// A wrapper around an index that stores a [`LineTerminator`] in its 2 lowest bits. -// We use `u64` instead of `usize` in order to not lose data when bit-packing -// on 32-bit targets. -#[derive(Clone, Copy)] -struct LineEndOffset(u64); - -#[derive(Clone, Copy)] -enum LineTerminator { - Eof = 0, - LfOrCr = 1, - CrLf = 2, -} - -impl LineEndOffset { - fn new(index: usize, line_end: LineTerminator) -> Self { - let shifted = (index as u64) << 2; - - Self(shifted | line_end as u64) - } - - /// Return the index of the end of this line. - fn to_end_index(self) -> usize { - (self.0 >> 2) as usize - } - - /// Return the index of the start of the next line. - fn to_start_index(self) -> usize { - self.to_end_index() + (self.0 & 0b11) as usize - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/types.rs b/src/types.rs index 908f9f75..0b1de268 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,7 +13,9 @@ use crate::hermes::SourceMapHermes; use crate::sourceview::SourceView; 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 /// @@ -109,7 +110,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 +270,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 +284,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 +306,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 +367,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 +383,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 +403,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 +423,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 +470,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 +484,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 +502,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 +532,7 @@ impl SourceMap { /// expanded. /// /// ```rust - /// # use sourcemap::SourceMap; + /// # use swc_sourcemap::SourceMap; /// # let input: &[_] = b"{ /// # \"version\":3, /// # \"sources\":[\"coolstuff.js\"], @@ -549,7 +550,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\"], @@ -572,7 +573,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\"], @@ -597,11 +598,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 { @@ -632,21 +633,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('/') @@ -654,17 +655,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 @@ -741,7 +742,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)) } @@ -752,22 +753,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); } } @@ -787,7 +788,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) @@ -795,11 +796,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 @@ -829,8 +830,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. @@ -846,7 +847,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\"], @@ -868,7 +869,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() { @@ -877,8 +878,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(), + ); } } @@ -930,137 +933,356 @@ 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), + ); + } + + /// 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( + 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 + } + + 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 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)); + + let mut original_ranges_iter = original_ranges.iter(); + + let mut original_range = match original_ranges_iter.next() { + Some(r) => r, + None => return self_tokens, + }; - ranges + // 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, + } } - // 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, - }; + // At this point `original_range.end` > `adjustment_range.start` - // 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, - ); + // 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; - // Skip `original_ranges` that are entirely before the `adjustment_range`. - while original_range.end <= adjustment_range.start { + 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, } } + } + } - // At this point `original_range.end` > `adjustment_range.start` + new_tokens.sort_unstable_by_key(|t| (t.dst_line, t.dst_col)); - // 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 - }; + 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 + } - token.dst_line = (token.dst_line as i32 + line_diff) as u32; - token.dst_col = (token.dst_col as i32 + col_diff) as u32; + // 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 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(), + ) + } else { + let this = &mut input_maps[(map_idx - 1) as usize]; + let src = this.get_source(src_id).cloned(); + ( + src.map(|src| { + 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(), + ) + }; + 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, + ); + }; + + // 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` - self.tokens.push(token); + 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 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`. + 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 `original_range`. - match original_ranges_iter.next() { - Some(r) => original_range = r, - None => break 'outer, + // Advance the `input_range`. + match input_ranges_iter.next() { + Some(r) => { + input_range_value = r; + input_range = Some(r); + } + None => { + input_range = None; + break; + } } } } } - - self.tokens - .sort_unstable_by_key(|t| (t.dst_line, t.dst_col)); } + + 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 { @@ -1095,7 +1317,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, @@ -1113,7 +1335,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>, @@ -1143,13 +1365,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 @@ -1189,7 +1411,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)) } @@ -1213,7 +1435,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(); @@ -1238,12 +1460,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())); } } @@ -1251,7 +1473,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); } @@ -1407,6 +1629,8 @@ impl SourceMapSection { mod tests { use std::collections::BTreeSet; + use crate::lazy::MaybeRawValue; + use super::{DecodedMap, RewriteOptions, SourceMap, SourceMapIndex, SourceMapSection}; use debugid::DebugId; @@ -1494,6 +1718,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#"{ @@ -1614,7 +1875,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), @@ -1628,7 +1889,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), @@ -1641,7 +1902,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), @@ -1655,7 +1916,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), @@ -1668,7 +1929,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), @@ -1684,7 +1945,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), @@ -1699,7 +1960,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), @@ -1720,7 +1981,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), @@ -1743,7 +2004,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), @@ -1756,7 +2017,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), @@ -1769,7 +2030,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), @@ -1789,16 +2050,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 bd911c2a..8635aa33 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -81,7 +81,7 @@ pub fn find_common_prefix<'a, I: Iterator>(iter: I) -> Option { +"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 00000000..eeaae2e5 --- /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 00000000..cb30ac8a --- /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 00000000..da8a4fd3 --- /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 00000000..3a41042b --- /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 00000000..409bc830 --- /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() +} diff --git a/tests/test_builder.rs b/tests/test_builder.rs index c3d38751..77b9d33c 100644 --- a/tests/test_builder.rs +++ b/tests/test_builder.rs @@ -1,17 +1,17 @@ -use sourcemap::SourceMapBuilder; +use swc_sourcemap::SourceMapBuilder; #[test] fn test_builder_into_sourcemap() { let mut builder = SourceMapBuilder::new(None); builder.set_source_root(Some("/foo/bar")); - builder.add_source("baz.js"); - builder.add_name("x"); + builder.add_source("baz.js".into()); + builder.add_name("x".into()); builder.add_to_ignore_list(0); let sm = builder.into_sourcemap(); - assert_eq!(sm.get_source_root(), Some("/foo/bar")); - assert_eq!(sm.get_source(0), Some("/foo/bar/baz.js")); - assert_eq!(sm.get_name(0), Some("x")); + assert_eq!(sm.get_source_root().map(|v| &**v), Some("/foo/bar")); + assert_eq!(sm.get_source(0).map(|v| &**v), Some("/foo/bar/baz.js")); + assert_eq!(sm.get_name(0).map(|v| &**v), Some("x")); let expected = br#"{"version":3,"sources":["baz.js"],"sourceRoot":"/foo/bar","names":["x"],"mappings":"","ignoreList":[0]}"#; let mut output: Vec = vec![]; diff --git a/tests/test_decoder.rs b/tests/test_decoder.rs index 9f898021..da1127e4 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 69346d11..1fe59a87 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 840ddb87..dc6600f5 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 37583d9e..dc840b8e 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 0540bb0e..a485adcc 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 19c256e8..4808155d 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 93db0d46..73b15700 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();