From 676bdd6c6713c54838cf04a5cee9a9c13dbf0068 Mon Sep 17 00:00:00 2001 From: Daniel Szoke Date: Wed, 23 Apr 2025 15:52:02 +0200 Subject: [PATCH] feat: Add `debug_id` field to `SourceMapIndex` Also, add support for encoding and decoding the new `debug_id` field, and add tests. In order to be able to more thoroughly test encoding/decoding, this code adds `PartialEq` and `Debug` implementations to some structs which were lacking it previously. Closes #113 --- src/decoder.rs | 94 ++++++++++++++++++++++++++++++++++++++++++++++- src/encoder.rs | 60 +++++++++++++++++++++++++++++- src/hermes.rs | 6 +-- src/jsontypes.rs | 8 ++-- src/sourceview.rs | 6 +++ src/types.rs | 43 +++++++++++++++++++--- 6 files changed, 203 insertions(+), 14 deletions(-) diff --git a/src/decoder.rs b/src/decoder.rs index 77df8bd..f51c8da 100644 --- a/src/decoder.rs +++ b/src/decoder.rs @@ -292,7 +292,8 @@ fn decode_index(rsm: RawSourceMap) -> Result { 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 { @@ -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"))) + ) + ); + } } diff --git a/src/encoder.rs b/src/encoder.rs index b2bc0ff..abc3286 100644 --- a/src/encoder.rs +++ b/src/encoder.rs @@ -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. + _debug_id_new: self.debug_id(), } } } @@ -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")), + } + ); + } } diff --git a/src/hermes.rs b/src/hermes.rs index 737dedc..e43eedc 100644 --- a/src/hermes.rs +++ b/src/hermes.rs @@ -12,14 +12,14 @@ 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, mappings: Vec, @@ -27,7 +27,7 @@ pub struct HermesFunctionMap { /// 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. diff --git a/src/jsontypes.rs b/src/jsontypes.rs index 6f1f676..281ba6b 100644 --- a/src/jsontypes.rs +++ b/src/jsontypes.rs @@ -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, pub map: Option>, } -#[derive(Serialize, Deserialize, Clone, Debug)] +#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] pub struct FacebookScopeMapping { pub names: Vec, pub mappings: String, @@ -28,7 +28,7 @@ pub struct FacebookScopeMapping { // See the decoder in `hermes.rs` for details. pub type FacebookSources = Option>>>; -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, PartialEq, Debug)] pub struct RawSourceMap { pub version: Option, #[serde(skip_serializing_if = "Option::is_none")] diff --git a/src/sourceview.rs b/src/sourceview.rs index 20fcc1f..e0ff52e 100644 --- a/src/sourceview.rs +++ b/src/sourceview.rs @@ -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) -> SourceView { diff --git a/src/types.rs b/src/types.rs index ae357af..a6e276b 100644 --- a/src/types.rs +++ b/src/types.rs @@ -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), @@ -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, @@ -443,12 +443,13 @@ impl<'a> Iterator for SourceMapSectionIter<'a> { } /// Represents a sourcemap index in memory -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct SourceMapIndex { file: Option, sections: Vec, x_facebook_offsets: Option>>, x_metro_module_paths: Option>, + debug_id: Option, } /// Represents a sourcemap in memory @@ -456,7 +457,7 @@ pub struct SourceMapIndex { /// 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>, pub(crate) tokens: Vec, @@ -1078,6 +1079,7 @@ impl SourceMapIndex { sections, x_facebook_offsets: None, x_metro_module_paths: None, + debug_id: None, } } @@ -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 { + self.debug_id + } + + /// Adds the given debug id to the sourcemap index. + pub(crate) fn with_debug_id(mut self, debug_id: Option) -> 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[..]) @@ -1306,7 +1320,7 @@ impl SourceMapSection { #[cfg(test)] mod tests { - use super::{RewriteOptions, SourceMap}; + use super::{RewriteOptions, SourceMap, SourceMapIndex}; use debugid::DebugId; #[test] @@ -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: //!