Skip to content

feat: Add debug_id field to SourceMapIndex #117

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 93 additions & 1 deletion src/decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,8 @@ fn decode_index(rsm: RawSourceMap) -> Result<SourceMapIndex> {
sections,
rsm.x_facebook_offsets,
rsm.x_metro_module_paths,
))
)
.with_debug_id(rsm._debug_id_new.or(rsm.debug_id)))
}

fn decode_common(rsm: RawSourceMap) -> Result<DecodedMap> {
Expand Down Expand Up @@ -387,4 +388,95 @@ mod tests {
assert_eq!(decode("g"), vec![5]);
assert_eq!(decode("Bg"), vec![0, 11]);
}

#[test]
fn test_decode_sourcemap_index_no_debug_id() {
let raw = RawSourceMap {
version: Some(3),
file: Some("test.js".into()),
sources: None,
source_root: None,
sources_content: None,
sections: Some(vec![]),
names: None,
range_mappings: None,
mappings: None,
ignore_list: None,
x_facebook_offsets: None,
x_metro_module_paths: None,
x_facebook_sources: None,
debug_id: None,
_debug_id_new: None,
};

let decoded = decode_common(raw).expect("should decoded");
assert_eq!(
decoded,
DecodedMap::Index(SourceMapIndex::new(Some("test.js".into()), vec![]))
);
}

#[test]
fn test_decode_sourcemap_index_debug_id() {
const DEBUG_ID: &str = "0123456789abcdef0123456789abcdef";

let raw = RawSourceMap {
version: Some(3),
file: Some("test.js".into()),
sources: None,
source_root: None,
sources_content: None,
sections: Some(vec![]),
names: None,
range_mappings: None,
mappings: None,
ignore_list: None,
x_facebook_offsets: None,
x_metro_module_paths: None,
x_facebook_sources: None,
debug_id: None,
_debug_id_new: Some(DEBUG_ID.parse().expect("valid debug id")),
};

let decoded = decode_common(raw).expect("should decode");
assert_eq!(
decoded,
DecodedMap::Index(
SourceMapIndex::new(Some("test.js".into()), vec![])
.with_debug_id(Some(DEBUG_ID.parse().expect("valid debug id")))
)
);
}

#[test]
fn test_decode_sourcemap_index_debug_id_from_legacy_key() {
const DEBUG_ID: &str = "0123456789abcdef0123456789abcdef";

let raw = RawSourceMap {
version: Some(3),
file: Some("test.js".into()),
sources: None,
source_root: None,
sources_content: None,
sections: Some(vec![]),
names: None,
range_mappings: None,
mappings: None,
ignore_list: None,
x_facebook_offsets: None,
x_metro_module_paths: None,
x_facebook_sources: None,
debug_id: Some(DEBUG_ID.parse().expect("valid debug id")),
_debug_id_new: None,
};

let decoded = decode_common(raw).expect("should decode");
assert_eq!(
decoded,
DecodedMap::Index(
SourceMapIndex::new(Some("test.js".into()), vec![])
.with_debug_id(Some(DEBUG_ID.parse().expect("valid debug id")))
)
);
}
}
60 changes: 59 additions & 1 deletion src/encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,8 @@ impl Encodable for SourceMapIndex {
x_metro_module_paths: None,
x_facebook_sources: None,
debug_id: None,
_debug_id_new: None,
// Put the debug ID on _debug_id_new to serialize it to the debugId field.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This behavior differs from the other cases—we don't write the _debug_id_new/debugId field anywhere, it's only used for reading. I would suggest doing the same here and switching to the new field in a separate PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean I should switch everything over to writing to the debugId field in the separate PR?

The reason I am reluctant to do that is because it would likely require a new major release of the crate (and then also in the CLI), since we would break anyone who relies on SourceMap objects being written to the debug_id field. On the other hand, since we are only just now adding the ability to write a debug ID field for SourceMapIndex, it is not a breaking change to write to debugId/_debug_id_new.

It would, however, in my opinion be beneficial to use the debugId field already here, since that is what we want to move towards, anyways

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, that makes sense.

_debug_id_new: self.debug_id(),
}
}
}
Expand Down Expand Up @@ -255,4 +256,61 @@ mod tests {
assert_eq!(encode(&[5]), "g");
assert_eq!(encode(&[0, 11]), "Bg");
}

#[test]
fn test_encode_sourcemap_index_no_debug_id() {
let smi = SourceMapIndex::new(Some("test.js".into()), vec![]);
let raw = smi.as_raw_sourcemap();

assert_eq!(
raw,
RawSourceMap {
version: Some(3),
file: Some("test.js".into()),
sources: None,
source_root: None,
sources_content: None,
sections: Some(vec![]),
names: None,
range_mappings: None,
mappings: None,
ignore_list: None,
x_facebook_offsets: None,
x_metro_module_paths: None,
x_facebook_sources: None,
debug_id: None,
_debug_id_new: None,
}
);
}

#[test]
fn test_encode_sourcemap_index_debug_id() {
const DEBUG_ID: &str = "0123456789abcdef0123456789abcdef";

let smi = SourceMapIndex::new(Some("test.js".into()), vec![])
.with_debug_id(Some(DEBUG_ID.parse().expect("valid debug id")));

let raw = smi.as_raw_sourcemap();
assert_eq!(
raw,
RawSourceMap {
version: Some(3),
file: Some("test.js".into()),
sources: None,
source_root: None,
sources_content: None,
sections: Some(vec![]),
names: None,
range_mappings: None,
mappings: None,
ignore_list: None,
x_facebook_offsets: None,
x_metro_module_paths: None,
x_facebook_sources: None,
debug_id: None,
_debug_id_new: Some(DEBUG_ID.parse().expect("valid debug id")),
}
);
}
}
6 changes: 3 additions & 3 deletions src/hermes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,22 @@ use std::ops::{Deref, DerefMut};
/// These are starting locations of scopes.
/// The `name_index` represents the index into the `HermesFunctionMap.names` vec,
/// which represents the function names/scopes.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
pub struct HermesScopeOffset {
line: u32,
column: u32,
name_index: u32,
}

#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
pub struct HermesFunctionMap {
names: Vec<String>,
mappings: Vec<HermesScopeOffset>,
}

/// Represents a `react-native`-style SourceMap, which has additional scope
/// information embedded.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
pub struct SourceMapHermes {
pub(crate) sm: SourceMap,
// There should be one `HermesFunctionMap` per each `sources` entry in the main SourceMap.
Expand Down
8 changes: 4 additions & 4 deletions src/jsontypes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,20 @@ use serde::de::IgnoredAny;
use serde::{Deserialize, Serialize};
use serde_json::Value;

#[derive(Serialize, Deserialize)]
#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub struct RawSectionOffset {
pub line: u32,
pub column: u32,
}

#[derive(Serialize, Deserialize)]
#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub struct RawSection {
pub offset: RawSectionOffset,
pub url: Option<String>,
pub map: Option<Box<RawSourceMap>>,
}

#[derive(Serialize, Deserialize, Clone, Debug)]
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
pub struct FacebookScopeMapping {
pub names: Vec<String>,
pub mappings: String,
Expand All @@ -28,7 +28,7 @@ pub struct FacebookScopeMapping {
// See the decoder in `hermes.rs` for details.
pub type FacebookSources = Option<Vec<Option<Vec<FacebookScopeMapping>>>>;

#[derive(Serialize, Deserialize)]
#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub struct RawSourceMap {
pub version: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
Expand Down
6 changes: 6 additions & 0 deletions src/sourceview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,12 @@ impl fmt::Debug for SourceView {
}
}

impl PartialEq for SourceView {
fn eq(&self, other: &Self) -> bool {
self.source == other.source
}
}

impl SourceView {
/// Creates an optimized view of a given source.
pub fn new(source: Arc<str>) -> SourceView {
Expand Down
43 changes: 38 additions & 5 deletions src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ impl<'a> Default for RewriteOptions<'a> {
/// Usually the two things are too distinct to provide a common
/// interface however for token lookup and writing back into a writer
/// general methods are provided.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
pub enum DecodedMap {
/// Indicates a regular sourcemap
Regular(SourceMap),
Expand Down Expand Up @@ -419,7 +419,7 @@ impl fmt::Display for Token<'_> {
}

/// Represents a section in a sourcemap index
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
pub struct SourceMapSection {
offset: (u32, u32),
url: Option<String>,
Expand All @@ -443,20 +443,21 @@ impl<'a> Iterator for SourceMapSectionIter<'a> {
}

/// Represents a sourcemap index in memory
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
pub struct SourceMapIndex {
file: Option<String>,
sections: Vec<SourceMapSection>,
x_facebook_offsets: Option<Vec<Option<u32>>>,
x_metro_module_paths: Option<Vec<String>>,
debug_id: Option<DebugId>,
}

/// Represents a sourcemap in memory
///
/// This is always represents a regular "non-indexed" sourcemap. Particularly
/// in case the `from_reader` method is used an index sourcemap will be
/// rejected with an error on reading.
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub struct SourceMap {
pub(crate) file: Option<Arc<str>>,
pub(crate) tokens: Vec<RawToken>,
Expand Down Expand Up @@ -1078,6 +1079,7 @@ impl SourceMapIndex {
sections,
x_facebook_offsets: None,
x_metro_module_paths: None,
debug_id: None,
}
}

Expand All @@ -1099,9 +1101,21 @@ impl SourceMapIndex {
sections,
x_facebook_offsets,
x_metro_module_paths,
debug_id: None,
}
}

/// Returns the debug ID.
pub(crate) fn debug_id(&self) -> Option<DebugId> {
self.debug_id
}

/// Adds the given debug id to the sourcemap index.
pub(crate) fn with_debug_id(mut self, debug_id: Option<DebugId>) -> Self {
self.debug_id = debug_id;
self
}

/// Returns the embedded filename in case there is one.
pub fn get_file(&self) -> Option<&str> {
self.file.as_ref().map(|x| &x[..])
Expand Down Expand Up @@ -1306,7 +1320,7 @@ impl SourceMapSection {

#[cfg(test)]
mod tests {
use super::{RewriteOptions, SourceMap};
use super::{RewriteOptions, SourceMap, SourceMapIndex};
use debugid::DebugId;

#[test]
Expand Down Expand Up @@ -1416,6 +1430,25 @@ mod tests {
assert_eq!(sm_new.sources, sm.sources);
}

#[test]
fn test_sourcemap_index_default_debug_id() {
let sm = SourceMapIndex::new(None, vec![]);
assert!(sm.debug_id().is_none());
}

#[test]
fn test_sourcemap_index_debug_id() {
const DEBUG_ID: &str = "0123456789abcdef0123456789abcdef";

let sm = SourceMapIndex::new(None, vec![])
.with_debug_id(Some(DEBUG_ID.parse().expect("valid debug id")));

assert_eq!(
sm.debug_id(),
Some(DEBUG_ID.parse().expect("valid debug id"))
);
}

mod prop {
//! This module exists to test the following property:
//!
Expand Down