Skip to content

Commit c95386a

Browse files
authored
feat: Add support for range mappings proposal (#77)
1 parent f80bd6c commit c95386a

File tree

12 files changed

+290
-12
lines changed

12 files changed

+290
-12
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ scroll = { version = "0.10.1", features = ["derive"], optional = true }
3333
data-encoding = "2.3.3"
3434
debugid = {version = "0.8.0", features = ["serde"] }
3535
base64-simd = { version = "0.7" }
36+
bitvec = "1.0.1"
3637
rustc-hash = "1.1.0"
3738

3839
[build-dependencies]

src/builder.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ impl SourceMapBuilder {
182182
}
183183

184184
/// Adds a new mapping to the builder.
185+
#[allow(clippy::too_many_arguments)]
185186
pub fn add(
186187
&mut self,
187188
dst_line: u32,
@@ -190,8 +191,11 @@ impl SourceMapBuilder {
190191
src_col: u32,
191192
source: Option<&str>,
192193
name: Option<&str>,
194+
is_range: bool,
193195
) -> RawToken {
194-
self.add_with_id(dst_line, dst_col, src_line, src_col, source, !0, name)
196+
self.add_with_id(
197+
dst_line, dst_col, src_line, src_col, source, !0, name, is_range,
198+
)
195199
}
196200

197201
#[allow(clippy::too_many_arguments)]
@@ -204,6 +208,7 @@ impl SourceMapBuilder {
204208
source: Option<&str>,
205209
source_id: u32,
206210
name: Option<&str>,
211+
is_range: bool,
207212
) -> RawToken {
208213
let src_id = match source {
209214
Some(source) => self.add_source_with_id(source, source_id),
@@ -220,12 +225,14 @@ impl SourceMapBuilder {
220225
src_col,
221226
src_id,
222227
name_id,
228+
is_range,
223229
};
224230
self.tokens.push(raw);
225231
raw
226232
}
227233

228234
/// Adds a new mapping to the builder.
235+
#[allow(clippy::too_many_arguments)]
229236
pub fn add_raw(
230237
&mut self,
231238
dst_line: u32,
@@ -234,6 +241,7 @@ impl SourceMapBuilder {
234241
src_col: u32,
235242
source: Option<u32>,
236243
name: Option<u32>,
244+
is_range: bool,
237245
) -> RawToken {
238246
let src_id = source.unwrap_or(!0);
239247
let name_id = name.unwrap_or(!0);
@@ -244,6 +252,7 @@ impl SourceMapBuilder {
244252
src_col,
245253
src_id,
246254
name_id,
255+
is_range,
247256
};
248257
self.tokens.push(raw);
249258
raw
@@ -261,6 +270,7 @@ impl SourceMapBuilder {
261270
token.get_source(),
262271
token.get_src_id(),
263272
name,
273+
token.is_range(),
264274
)
265275
}
266276

src/decoder.rs

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
use std::io;
22
use std::io::{BufReader, Read};
33

4+
use bitvec::field::BitField;
5+
use bitvec::order::Lsb0;
6+
use bitvec::vec::BitVec;
47
use serde_json::Value;
58

69
use crate::errors::{Error, Result};
@@ -120,6 +123,29 @@ pub fn strip_junk_header(slice: &[u8]) -> io::Result<&[u8]> {
120123
Ok(&slice[slice.len()..])
121124
}
122125

126+
/// Decodes range mappping bitfield string into index
127+
fn decode_rmi(rmi_str: &str, val: &mut BitVec<u8, Lsb0>) -> Result<()> {
128+
val.clear();
129+
val.resize(rmi_str.len() * 6, false);
130+
131+
for (idx, &byte) in rmi_str.as_bytes().iter().enumerate() {
132+
let byte = match byte {
133+
b'A'..=b'Z' => byte - b'A',
134+
b'a'..=b'z' => byte - b'a' + 26,
135+
b'0'..=b'9' => byte - b'0' + 52,
136+
b'+' => 62,
137+
b'/' => 63,
138+
_ => {
139+
fail!(Error::InvalidBase64(byte as char));
140+
}
141+
};
142+
143+
val[6 * idx..6 * (idx + 1)].store_le::<u8>(byte);
144+
}
145+
146+
Ok(())
147+
}
148+
123149
pub fn decode_regular(rsm: RawSourceMap) -> Result<SourceMap> {
124150
let mut dst_col;
125151
let mut src_id = 0;
@@ -129,20 +155,28 @@ pub fn decode_regular(rsm: RawSourceMap) -> Result<SourceMap> {
129155

130156
let names = rsm.names.unwrap_or_default();
131157
let sources = rsm.sources.unwrap_or_default();
158+
let range_mappings = rsm.range_mappings.unwrap_or_default();
132159
let mappings = rsm.mappings.unwrap_or_default();
133160
let allocation_size = mappings.matches(&[',', ';'][..]).count() + 10;
134161
let mut tokens = Vec::with_capacity(allocation_size);
135162

136163
let mut nums = Vec::with_capacity(6);
164+
let mut rmi = BitVec::new();
137165

138-
for (dst_line, line) in mappings.split(';').enumerate() {
166+
for (dst_line, (line, rmi_str)) in mappings
167+
.split(';')
168+
.zip(range_mappings.split(';').chain(std::iter::repeat("")))
169+
.enumerate()
170+
{
139171
if line.is_empty() {
140172
continue;
141173
}
142174

143175
dst_col = 0;
144176

145-
for segment in line.split(',') {
177+
decode_rmi(rmi_str, &mut rmi)?;
178+
179+
for (line_index, segment) in line.split(',').enumerate() {
146180
if segment.is_empty() {
147181
continue;
148182
}
@@ -176,13 +210,16 @@ pub fn decode_regular(rsm: RawSourceMap) -> Result<SourceMap> {
176210
}
177211
}
178212

213+
let is_range = rmi.get(line_index).map(|v| *v).unwrap_or_default();
214+
179215
tokens.push(RawToken {
180216
dst_line: dst_line as u32,
181217
dst_col,
182218
src_line,
183219
src_col,
184220
src_id: src,
185221
name_id: name,
222+
is_range,
186223
});
187224
}
188225
}
@@ -319,3 +356,24 @@ fn test_bad_newline() {
319356
}
320357
}
321358
}
359+
360+
#[test]
361+
fn test_decode_rmi() {
362+
fn decode(rmi_str: &str) -> Vec<usize> {
363+
let mut out = bitvec::bitvec![u8, Lsb0; 0; 0];
364+
decode_rmi(rmi_str, &mut out).expect("failed to decode");
365+
366+
let mut res = vec![];
367+
for (idx, bit) in out.iter().enumerate() {
368+
if *bit {
369+
res.push(idx);
370+
}
371+
}
372+
res
373+
}
374+
375+
// This is 0-based index of the bits
376+
assert_eq!(decode("AAB"), vec![12]);
377+
assert_eq!(decode("g"), vec![5]);
378+
assert_eq!(decode("Bg"), vec![0, 11]);
379+
}

src/encoder.rs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
use std::io::Write;
22

3+
use bitvec::field::BitField;
4+
use bitvec::order::Lsb0;
5+
use bitvec::view::BitView;
36
use serde_json::Value;
47

58
use crate::errors::Result;
@@ -21,6 +24,78 @@ fn encode_vlq_diff(out: &mut String, a: u32, b: u32) {
2124
encode_vlq(out, i64::from(a) - i64::from(b))
2225
}
2326

27+
fn encode_rmi(out: &mut Vec<u8>, data: &mut Vec<u8>) {
28+
fn encode_byte(b: u8) -> u8 {
29+
match b {
30+
0..=25 => b + b'A',
31+
26..=51 => b + b'a' - 26,
32+
52..=61 => b + b'0' - 52,
33+
62 => b'+',
34+
63 => b'/',
35+
_ => panic!("invalid byte"),
36+
}
37+
}
38+
39+
let bits = data.view_bits_mut::<Lsb0>();
40+
41+
// trim zero at the end
42+
let mut last = 0;
43+
for (idx, bit) in bits.iter().enumerate() {
44+
if *bit {
45+
last = idx;
46+
}
47+
}
48+
let bits = &mut bits[..last + 1];
49+
50+
for byte in bits.chunks(6) {
51+
let byte = byte.load::<u8>();
52+
53+
let encoded = encode_byte(byte);
54+
55+
out.push(encoded);
56+
}
57+
}
58+
59+
fn serialize_range_mappings(sm: &SourceMap) -> Option<String> {
60+
let mut buf = Vec::new();
61+
let mut prev_line = 0;
62+
let mut had_rmi = false;
63+
64+
let mut idx_of_first_in_line = 0;
65+
66+
let mut rmi_data = Vec::<u8>::new();
67+
68+
for (idx, token) in sm.tokens().enumerate() {
69+
if token.is_range() {
70+
had_rmi = true;
71+
72+
let num = idx - idx_of_first_in_line;
73+
74+
rmi_data.resize(rmi_data.len() + 2, 0);
75+
76+
let rmi_bits = rmi_data.view_bits_mut::<Lsb0>();
77+
rmi_bits.set(num, true);
78+
}
79+
80+
while token.get_dst_line() != prev_line {
81+
if had_rmi {
82+
encode_rmi(&mut buf, &mut rmi_data);
83+
rmi_data.clear();
84+
}
85+
86+
buf.push(b';');
87+
prev_line += 1;
88+
had_rmi = false;
89+
idx_of_first_in_line = idx;
90+
}
91+
}
92+
if had_rmi {
93+
encode_rmi(&mut buf, &mut rmi_data);
94+
}
95+
96+
Some(String::from_utf8(buf).expect("invalid utf8"))
97+
}
98+
2499
fn serialize_mappings(sm: &SourceMap) -> String {
25100
let mut rv = String::new();
26101
// dst == minified == generated
@@ -89,6 +164,7 @@ impl Encodable for SourceMap {
89164
sources_content: if have_contents { Some(contents) } else { None },
90165
sections: None,
91166
names: Some(self.names().map(|x| Value::String(x.to_string())).collect()),
167+
range_mappings: serialize_range_mappings(self),
92168
mappings: Some(serialize_mappings(self)),
93169
x_facebook_offsets: None,
94170
x_metro_module_paths: None,
@@ -121,6 +197,7 @@ impl Encodable for SourceMapIndex {
121197
.collect(),
122198
),
123199
names: None,
200+
range_mappings: None,
124201
mappings: None,
125202
x_facebook_offsets: None,
126203
x_metro_module_paths: None,
@@ -139,3 +216,26 @@ impl Encodable for DecodedMap {
139216
}
140217
}
141218
}
219+
220+
#[test]
221+
fn test_encode_rmi() {
222+
fn encode(indices: &[usize]) -> String {
223+
let mut out = vec![];
224+
225+
// Fill with zeros while testing
226+
let mut data = vec![0; 256];
227+
228+
let bits = data.view_bits_mut::<Lsb0>();
229+
for &i in indices {
230+
bits.set(i, true);
231+
}
232+
233+
encode_rmi(&mut out, &mut data);
234+
String::from_utf8(out).unwrap()
235+
}
236+
237+
// This is 0-based index
238+
assert_eq!(encode(&[12]), "AAB");
239+
assert_eq!(encode(&[5]), "g");
240+
assert_eq!(encode(&[0, 11]), "Bg");
241+
}

src/errors.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ pub enum Error {
4545
InvalidRamBundleEntry,
4646
/// Tried to operate on a non RAM bundle file
4747
NotARamBundle,
48+
/// Range mapping index is invalid
49+
InvalidRangeMappingIndex(data_encoding::DecodeError),
50+
51+
InvalidBase64(char),
4852
}
4953

5054
impl From<io::Error> for Error {
@@ -78,6 +82,12 @@ impl From<serde_json::Error> for Error {
7882
}
7983
}
8084

85+
impl From<data_encoding::DecodeError> for Error {
86+
fn from(err: data_encoding::DecodeError) -> Error {
87+
Error::InvalidRangeMappingIndex(err)
88+
}
89+
}
90+
8191
impl error::Error for Error {
8292
fn cause(&self) -> Option<&dyn error::Error> {
8393
match *self {
@@ -114,6 +124,8 @@ impl fmt::Display for Error {
114124
Error::InvalidRamBundleIndex => write!(f, "invalid module index in ram bundle"),
115125
Error::InvalidRamBundleEntry => write!(f, "invalid ram bundle module entry"),
116126
Error::NotARamBundle => write!(f, "not a ram bundle"),
127+
Error::InvalidRangeMappingIndex(err) => write!(f, "invalid range mapping index: {err}"),
128+
Error::InvalidBase64(c) => write!(f, "invalid base64 character: {}", c),
117129
}
118130
}
119131
}

src/jsontypes.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ pub struct RawSourceMap {
4242
pub sections: Option<Vec<RawSection>>,
4343
#[serde(skip_serializing_if = "Option::is_none")]
4444
pub names: Option<Vec<Value>>,
45+
#[serde(rename = "rangeMappings", skip_serializing_if = "Option::is_none")]
46+
pub range_mappings: Option<String>,
4547
#[serde(skip_serializing_if = "Option::is_none")]
4648
pub mappings: Option<String>,
4749
#[serde(skip_serializing_if = "Option::is_none")]

src/ram_bundle.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,7 @@ impl<'a> SplitRamBundleModuleIter<'a> {
402402
token.get_src_col(),
403403
token.get_source(),
404404
token.get_name(),
405+
false,
405406
);
406407
if token.get_source().is_some() && !builder.has_source_contents(raw.src_id) {
407408
builder.set_source_contents(

0 commit comments

Comments
 (0)