Skip to content

Commit

Permalink
fix: Correct sourceRoot logic (#76)
Browse files Browse the repository at this point in the history
  • Loading branch information
loewenheim authored Nov 2, 2023
1 parent c3ab912 commit 27402a7
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 38 deletions.
28 changes: 1 addition & 27 deletions src/decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,33 +187,7 @@ pub fn decode_regular(rsm: RawSourceMap) -> Result<SourceMap> {
}
}

let sources = match rsm.source_root {
Some(ref source_root) if !source_root.is_empty() => {
let source_root = if let Some(stripped) = source_root.strip_suffix('/') {
stripped
} else {
source_root
};

sources
.into_iter()
.map(|x| {
let x = x.unwrap_or_default();
let is_valid = !x.is_empty()
&& (x.starts_with('/')
|| x.starts_with("http:")
|| x.starts_with("https:"));

if is_valid {
x
} else {
format!("{source_root}/{x}")
}
})
.collect()
}
_ => sources.into_iter().map(Option::unwrap_or_default).collect(),
};
let sources = sources.into_iter().map(Option::unwrap_or_default).collect();

// apparently we can encounter some non string types in real world
// sourcemaps :(
Expand Down
2 changes: 1 addition & 1 deletion src/encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ impl Encodable for SourceMap {
RawSourceMap {
version: Some(3),
file: self.get_file().map(|x| Value::String(x.to_string())),
sources: Some(self.sources().map(|x| Some(x.to_string())).collect()),
sources: Some(self.sources.iter().map(|x| Some(x.to_string())).collect()),
source_root: self.get_source_root().map(|x| x.to_string()),
sources_content: if have_contents { Some(contents) } else { None },
sections: None,
Expand Down
76 changes: 67 additions & 9 deletions src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -457,14 +457,15 @@ pub struct SourceMapIndex {
/// rejected with an error on reading.
#[derive(Clone, Debug)]
pub struct SourceMap {
file: Option<String>,
tokens: Vec<RawToken>,
index: Vec<(u32, u32, u32)>,
names: Vec<String>,
source_root: Option<String>,
sources: Vec<String>,
sources_content: Vec<Option<SourceView<'static>>>,
debug_id: Option<DebugId>,
pub(crate) file: Option<String>,
pub(crate) tokens: Vec<RawToken>,
pub(crate) index: Vec<(u32, u32, u32)>,
pub(crate) names: Vec<String>,
pub(crate) source_root: Option<String>,
pub(crate) sources: Vec<String>,
pub(crate) sources_prefixed: Option<Vec<String>>,
pub(crate) sources_content: Vec<Option<SourceView<'static>>>,
pub(crate) debug_id: Option<DebugId>,
}

impl SourceMap {
Expand Down Expand Up @@ -568,6 +569,7 @@ impl SourceMap {
names,
source_root: None,
sources,
sources_prefixed: None,
sources_content: sources_content
.unwrap_or_default()
.into_iter()
Expand Down Expand Up @@ -602,9 +604,35 @@ impl SourceMap {
self.source_root.as_deref()
}

fn prefix_source(source_root: &str, source: &str) -> String {
let source_root = source_root.strip_suffix('/').unwrap_or(source_root);
let is_valid = !source.is_empty()
&& (source.starts_with('/')
|| source.starts_with("http:")
|| source.starts_with("https:"));

if is_valid {
source.to_string()
} else {
format!("{source_root}/{source}")
}
}

/// Sets a new value for the source_root.
pub fn set_source_root<T: Into<String>>(&mut self, value: Option<T>) {
self.source_root = value.map(Into::into);

match self.source_root.as_deref().filter(|rs| !rs.is_empty()) {
Some(source_root) => {
let sources_prefixed = self
.sources
.iter()
.map(|source| Self::prefix_source(source_root, source))
.collect();
self.sources_prefixed = Some(sources_prefixed)
}
None => self.sources_prefixed = None,
}
}

/// Looks up a token by its index.
Expand Down Expand Up @@ -659,7 +687,8 @@ impl SourceMap {

/// Looks up a source for a specific index.
pub fn get_source(&self, idx: u32) -> Option<&str> {
self.sources.get(idx as usize).map(|x| &x[..])
let sources = self.sources_prefixed.as_deref().unwrap_or(&self.sources);
sources.get(idx as usize).map(|x| &x[..])
}

/// Sets a new source value for an index. This cannot add new
Expand All @@ -668,6 +697,12 @@ impl SourceMap {
/// 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.to_string();

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);
}
}

/// Iterates over all sources
Expand Down Expand Up @@ -1313,6 +1348,29 @@ mod tests {
}
}

#[test]
fn test_roundtrip() {
let sm = br#"{
"version": 3,
"file": "foo.js",
"sources": [
"./bar.js",
"./baz.js"
],
"sourceRoot": "webpack:///",
"sourcesContent": [null, null],
"names": [],
"mappings": ""
}"#;

let sm = SourceMap::from_slice(sm).unwrap();
let mut out = Vec::new();
sm.to_writer(&mut out).unwrap();

let sm_new = SourceMap::from_slice(&out).unwrap();
assert_eq!(sm_new.sources, sm.sources);
}

mod prop {
//! This module exists to test the following property:
//!
Expand Down
2 changes: 1 addition & 1 deletion tests/test_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ fn test_builder_into_sourcemap() {

let sm = builder.into_sourcemap();
assert_eq!(sm.get_source_root(), Some("/foo/bar"));
assert_eq!(sm.get_source(0), Some("baz.js"));
assert_eq!(sm.get_source(0), Some("/foo/bar/baz.js"));
assert_eq!(sm.get_name(0), Some("x"));

let expected = br#"{"version":3,"sources":["baz.js"],"sourceRoot":"/foo/bar","names":["x"],"mappings":""}"#;
Expand Down
16 changes: 16 additions & 0 deletions tests/test_decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,22 @@ fn test_basic_sourcemap_with_absolute_uri_root() {
assert!(iter.next().is_none());
}

#[test]
fn test_basic_sourcemap_source_root_logic() {
let input: &[_] = br#"{
"version": 3,
"sources": ["coolstuff.js", "/evencoolerstuff.js", "https://awesome.js"],
"sourceRoot": "webpack:///",
"mappings": ""
}"#;
let sm = SourceMap::from_reader(input).unwrap();
let mut iter = sm.sources();
assert_eq!(iter.next().unwrap(), "webpack:///coolstuff.js");
assert_eq!(iter.next().unwrap(), "/evencoolerstuff.js");
assert_eq!(iter.next().unwrap(), "https://awesome.js");
assert!(iter.next().is_none());
}

#[test]
fn test_sourcemap_data_url() {
let url = "data:application/json;base64,\
Expand Down

0 comments on commit 27402a7

Please sign in to comment.