From d3c1b2884cf8201968afe7289dc40ca37f4ac149 Mon Sep 17 00:00:00 2001 From: Will Binns-Smith Date: Wed, 16 Oct 2024 14:11:12 -0700 Subject: [PATCH 1/3] Add support for ignoreList property MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sourcemaps can now specify an `ignoreList`, a list of indexes into sources for which those sources should be shown as ignored in dev tools, for example as collapsed stack frames in program stacks. This adds the list to the SourceMap structure but only includes it in serialized output when it’s non-zero. Open to feedback. Added a test to exercise this through flattening indexed source maps into a regular map with updated source positions. --- src/builder.rs | 16 ++++++++++++++- src/decoder.rs | 9 ++++++++- src/encoder.rs | 6 ++++++ src/jsontypes.rs | 2 ++ src/types.rs | 19 ++++++++++++++++++ tests/test_index.rs | 47 +++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 97 insertions(+), 2 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 8bb0cc66..ecdaa599 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -1,5 +1,6 @@ #![cfg_attr(not(any(unix, windows, target_os = "redox")), allow(unused_imports))] +use std::collections::BTreeSet; use std::env; use std::fs; use std::io::Read; @@ -28,6 +29,7 @@ pub struct SourceMapBuilder { sources: Vec>, source_contents: Vec>>, sources_mapping: Vec, + ignore_list: BTreeSet, debug_id: Option, } @@ -61,6 +63,7 @@ impl SourceMapBuilder { sources: vec![], source_contents: vec![], sources_mapping: vec![], + ignore_list: BTreeSet::default(), debug_id: None, } } @@ -116,6 +119,10 @@ impl SourceMapBuilder { self.sources.get(src_id as usize).map(|x| &x[..]) } + pub fn add_to_ignore_list(&mut self, src_id: u32) { + self.ignore_list.insert(src_id); + } + /// Sets the source contents for an already existing source. pub fn set_source_contents(&mut self, src_id: u32, contents: Option<&str>) { assert!(src_id != !0, "Cannot set sources for tombstone source id"); @@ -301,7 +308,14 @@ impl SourceMapBuilder { None }; - let mut sm = SourceMap::new(self.file, self.tokens, self.names, self.sources, contents); + let mut sm = SourceMap::new( + self.file, + self.tokens, + self.names, + self.sources, + contents, + Some(self.ignore_list.iter().cloned().collect()), + ); sm.set_source_root(self.source_root); sm.set_debug_id(self.debug_id); diff --git a/src/decoder.rs b/src/decoder.rs index 0ee7c75c..5df34c43 100644 --- a/src/decoder.rs +++ b/src/decoder.rs @@ -251,7 +251,14 @@ pub fn decode_regular(rsm: RawSourceMap) -> Result { .sources_content .map(|x| x.into_iter().map(|v| v.map(Into::into)).collect::>()); - let mut sm = SourceMap::new(file, tokens, names, sources, source_content); + let mut sm = SourceMap::new( + file, + tokens, + names, + sources, + source_content, + rsm.ignore_list, + ); sm.set_source_root(rsm.source_root); sm.set_debug_id(rsm.debug_id); diff --git a/src/encoder.rs b/src/encoder.rs index 8225dc08..6b62d4bc 100644 --- a/src/encoder.rs +++ b/src/encoder.rs @@ -170,6 +170,11 @@ impl Encodable for SourceMap { names: Some(self.names().map(|x| Value::String(x.to_string())).collect()), range_mappings: serialize_range_mappings(self), mappings: Some(serialize_mappings(self)), + ignore_list: if self.ignore_list.is_empty() { + None + } else { + Some(self.ignore_list.iter().cloned().collect()) + }, x_facebook_offsets: None, x_metro_module_paths: None, x_facebook_sources: None, @@ -203,6 +208,7 @@ impl Encodable for SourceMapIndex { names: None, range_mappings: None, mappings: None, + ignore_list: None, x_facebook_offsets: None, x_metro_module_paths: None, x_facebook_sources: None, diff --git a/src/jsontypes.rs b/src/jsontypes.rs index 59d893b9..f83c192a 100644 --- a/src/jsontypes.rs +++ b/src/jsontypes.rs @@ -46,6 +46,8 @@ pub struct RawSourceMap { pub range_mappings: Option, #[serde(skip_serializing_if = "Option::is_none")] pub mappings: Option, + #[serde(rename = "ignoreList", skip_serializing_if = "Option::is_none")] + pub ignore_list: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub x_facebook_offsets: Option>>, #[serde(skip_serializing_if = "Option::is_none")] diff --git a/src/types.rs b/src/types.rs index a2afdc36..5281163c 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,5 +1,6 @@ use std::borrow::Cow; use std::cmp::Ordering; +use std::collections::BTreeSet; use std::fmt; use std::io::{Read, Write}; use std::path::Path; @@ -464,6 +465,7 @@ pub struct SourceMap { pub(crate) sources: Vec>, pub(crate) sources_prefixed: Option>>, pub(crate) sources_content: Vec>, + pub(crate) ignore_list: BTreeSet, pub(crate) debug_id: Option, } @@ -571,12 +573,14 @@ impl SourceMap { /// - `names`: a vector of names /// - `sources` a vector of source filenames /// - `sources_content` optional source contents + /// - `ignore_list` optional list of source indexes for devtools to ignore pub fn new( file: Option>, mut tokens: Vec, names: Vec>, sources: Vec>, sources_content: Option>>>, + ignore_list: Option>, ) -> SourceMap { tokens.sort_unstable_by_key(|t| (t.dst_line, t.dst_col)); SourceMap { @@ -591,6 +595,10 @@ impl SourceMap { .into_iter() .map(|opt| opt.map(SourceView::new)) .collect(), + ignore_list: match ignore_list { + Some(ignore_list) => ignore_list.into_iter().collect(), + None => BTreeSet::default(), + }, debug_id: None, } } @@ -651,6 +659,14 @@ impl SourceMap { } } + pub fn add_to_ignore_list(&mut self, src_id: u32) { + self.ignore_list.insert(src_id); + } + + pub fn ignore_list(&self) -> impl Iterator { + self.ignore_list.iter() + } + /// Looks up a token by its index. pub fn get_token(&self, idx: usize) -> Option> { self.tokens.get(idx).map(|raw| Token { @@ -1201,6 +1217,9 @@ impl SourceMapIndex { map.get_source_contents(token.get_src_id()), ); } + if map.ignore_list.contains(&token.get_src_id()) { + builder.add_to_ignore_list(raw.src_id); + } } } diff --git a/tests/test_index.rs b/tests/test_index.rs index e373b7bf..d5a84693 100644 --- a/tests/test_index.rs +++ b/tests/test_index.rs @@ -158,3 +158,50 @@ fn test_indexed_sourcemap_for_react_native() { let ism = SourceMapIndex::from_reader(input).unwrap(); assert!(ism.is_for_ram_bundle()); } + +#[test] +fn test_flatten_indexed_sourcemap_with_ignore_list() { + let input: &[_] = br#"{ + "version": 3, + "file": "bla", + "sections": [ + { + "offset": { + "line": 0, + "column": 0 + }, + "map": { + "version":3, + "sources":["file1.js"], + "names":["add","a","b"], + "mappings":"AAAA,QAASA,KAAIC,EAAGC,GACf,YACA,OAAOD,GAAIC", + "file":"file1.min.js" + } + }, + { + "offset": { + "line": 1, + "column": 0 + }, + "map": { + "version":3, + "sources":["file2.js"], + "names":["multiply","a","b","divide","add","c","e","Raven","captureException"], + "mappings":"AAAA,QAASA,UAASC,EAAGC,GACpB,YACA,OAAOD,GAAIC,EAEZ,QAASC,QAAOF,EAAGC,GAClB,YACA,KACC,MAAOF,UAASI,IAAIH,EAAGC,GAAID,EAAGC,GAAKG,EAClC,MAAOC,GACRC,MAAMC,iBAAiBF", + "file":"file2.min.js", + "ignoreList": [0] + } + } + ] + }"#; + + let ism = SourceMapIndex::from_reader(input).unwrap(); + assert_eq!( + ism.flatten() + .unwrap() + .ignore_list() + .cloned() + .collect::>(), + vec![1] + ); +} From 8ea1cca2db87bbc06a7d9c2c96201fa0aaa29661 Mon Sep 17 00:00:00 2001 From: Will Binns-Smith Date: Wed, 23 Oct 2024 14:31:04 -0700 Subject: [PATCH 2/3] Update src/builder.rs Co-authored-by: Sebastian Zivota --- src/builder.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/builder.rs b/src/builder.rs index ecdaa599..c9399702 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -314,7 +314,7 @@ impl SourceMapBuilder { self.names, self.sources, contents, - Some(self.ignore_list.iter().cloned().collect()), + Some(self.ignore_list.into_iter().collect()), ); sm.set_source_root(self.source_root); sm.set_debug_id(self.debug_id); From fb686c783bbb9491e2eabb82bd578e63b1c41790 Mon Sep 17 00:00:00 2001 From: Will Binns-Smith Date: Wed, 23 Oct 2024 14:58:01 -0700 Subject: [PATCH 3/3] fixup! Add support for ignoreList property --- src/builder.rs | 12 ++++-------- src/decoder.rs | 14 ++++++-------- src/types.rs | 6 +----- tests/test_builder.rs | 3 ++- tests/test_index.rs | 2 +- 5 files changed, 14 insertions(+), 23 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index c9399702..32d0bdcc 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -308,16 +308,12 @@ impl SourceMapBuilder { None }; - let mut sm = SourceMap::new( - self.file, - self.tokens, - self.names, - self.sources, - contents, - Some(self.ignore_list.into_iter().collect()), - ); + let mut sm = SourceMap::new(self.file, self.tokens, self.names, self.sources, contents); sm.set_source_root(self.source_root); sm.set_debug_id(self.debug_id); + for ignored_src_id in self.ignore_list { + sm.add_to_ignore_list(ignored_src_id); + } sm } diff --git a/src/decoder.rs b/src/decoder.rs index 5df34c43..464505ed 100644 --- a/src/decoder.rs +++ b/src/decoder.rs @@ -251,16 +251,14 @@ pub fn decode_regular(rsm: RawSourceMap) -> Result { .sources_content .map(|x| x.into_iter().map(|v| v.map(Into::into)).collect::>()); - let mut sm = SourceMap::new( - file, - tokens, - names, - sources, - source_content, - rsm.ignore_list, - ); + let mut sm = SourceMap::new(file, tokens, names, sources, source_content); sm.set_source_root(rsm.source_root); sm.set_debug_id(rsm.debug_id); + if let Some(ignore_list) = rsm.ignore_list { + for idx in ignore_list { + sm.add_to_ignore_list(idx); + } + } Ok(sm) } diff --git a/src/types.rs b/src/types.rs index 5281163c..d28ac632 100644 --- a/src/types.rs +++ b/src/types.rs @@ -580,7 +580,6 @@ impl SourceMap { names: Vec>, sources: Vec>, sources_content: Option>>>, - ignore_list: Option>, ) -> SourceMap { tokens.sort_unstable_by_key(|t| (t.dst_line, t.dst_col)); SourceMap { @@ -595,10 +594,7 @@ impl SourceMap { .into_iter() .map(|opt| opt.map(SourceView::new)) .collect(), - ignore_list: match ignore_list { - Some(ignore_list) => ignore_list.into_iter().collect(), - None => BTreeSet::default(), - }, + ignore_list: BTreeSet::default(), debug_id: None, } } diff --git a/tests/test_builder.rs b/tests/test_builder.rs index 8d34a307..c3d38751 100644 --- a/tests/test_builder.rs +++ b/tests/test_builder.rs @@ -6,13 +6,14 @@ fn test_builder_into_sourcemap() { builder.set_source_root(Some("/foo/bar")); builder.add_source("baz.js"); builder.add_name("x"); + 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")); - let expected = br#"{"version":3,"sources":["baz.js"],"sourceRoot":"/foo/bar","names":["x"],"mappings":""}"#; + let expected = br#"{"version":3,"sources":["baz.js"],"sourceRoot":"/foo/bar","names":["x"],"mappings":"","ignoreList":[0]}"#; let mut output: Vec = vec![]; sm.to_writer(&mut output).unwrap(); assert_eq!(output, expected); diff --git a/tests/test_index.rs b/tests/test_index.rs index d5a84693..b3983576 100644 --- a/tests/test_index.rs +++ b/tests/test_index.rs @@ -200,7 +200,7 @@ fn test_flatten_indexed_sourcemap_with_ignore_list() { ism.flatten() .unwrap() .ignore_list() - .cloned() + .copied() .collect::>(), vec![1] );