swc_sourcemap/
hermes.rs

1use bytes_str::BytesStr;
2
3use crate::decoder::{decode, decode_regular, decode_slice};
4use crate::encoder::{encode, Encodable};
5use crate::errors::{Error, Result};
6use crate::jsontypes::{FacebookScopeMapping, FacebookSources, RawSourceMap};
7use crate::types::{DecodedMap, RewriteOptions, SourceMap};
8use crate::utils::greatest_lower_bound;
9use crate::vlq::parse_vlq_segment_into;
10use crate::Token;
11use std::io::{Read, Write};
12use std::ops::{Deref, DerefMut};
13
14/// These are starting locations of scopes.
15/// The `name_index` represents the index into the `HermesFunctionMap.names` vec,
16/// which represents the function names/scopes.
17#[derive(Debug, Clone, PartialEq)]
18pub struct HermesScopeOffset {
19    line: u32,
20    column: u32,
21    name_index: u32,
22}
23
24#[derive(Debug, Clone, PartialEq)]
25pub struct HermesFunctionMap {
26    names: Vec<BytesStr>,
27    mappings: Vec<HermesScopeOffset>,
28}
29
30/// Represents a `react-native`-style SourceMap, which has additional scope
31/// information embedded.
32#[derive(Debug, Clone, PartialEq)]
33pub struct SourceMapHermes {
34    pub(crate) sm: SourceMap,
35    // There should be one `HermesFunctionMap` per each `sources` entry in the main SourceMap.
36    function_maps: Vec<Option<HermesFunctionMap>>,
37    // XXX: right now, I am too lazy to actually serialize the above `function_maps`
38    // back into json types, so just keep the original json. Might be a bit inefficient, but meh.
39    raw_facebook_sources: FacebookSources,
40}
41
42impl Deref for SourceMapHermes {
43    type Target = SourceMap;
44
45    fn deref(&self) -> &Self::Target {
46        &self.sm
47    }
48}
49
50impl DerefMut for SourceMapHermes {
51    fn deref_mut(&mut self) -> &mut Self::Target {
52        &mut self.sm
53    }
54}
55
56impl Encodable for SourceMapHermes {
57    fn as_raw_sourcemap(&self) -> RawSourceMap {
58        // TODO: need to serialize the `HermesFunctionMap` mappings
59        let mut rsm = self.sm.as_raw_sourcemap();
60        rsm.x_facebook_sources
61            .clone_from(&self.raw_facebook_sources);
62        rsm
63    }
64}
65
66impl SourceMapHermes {
67    /// Creates a sourcemap from a reader over a JSON stream in UTF-8
68    /// format.
69    ///
70    /// See [`SourceMap::from_reader`](struct.SourceMap.html#method.from_reader)
71    pub fn from_reader<R: Read>(rdr: R) -> Result<Self> {
72        match decode(rdr)? {
73            DecodedMap::Hermes(sm) => Ok(sm),
74            _ => Err(Error::IncompatibleSourceMap),
75        }
76    }
77
78    /// Creates a sourcemap from a reader over a JSON byte slice in UTF-8
79    /// format.
80    ///
81    /// See [`SourceMap::from_slice`](struct.SourceMap.html#method.from_slice)
82    pub fn from_slice(slice: &[u8]) -> Result<Self> {
83        match decode_slice(slice)? {
84            DecodedMap::Hermes(sm) => Ok(sm),
85            _ => Err(Error::IncompatibleSourceMap),
86        }
87    }
88
89    /// Writes a sourcemap into a writer.
90    ///
91    /// See [`SourceMap::to_writer`](struct.SourceMap.html#method.to_writer)
92    pub fn to_writer<W: Write>(&self, w: W) -> Result<()> {
93        encode(self, w)
94    }
95
96    /// Given a bytecode offset, this will find the enclosing scopes function
97    /// name.
98    pub fn get_original_function_name(&self, bytecode_offset: u32) -> Option<&BytesStr> {
99        let token = self.sm.lookup_token(0, bytecode_offset)?;
100
101        self.get_scope_for_token(token)
102    }
103
104    /// Resolves the name of the enclosing function for the given [`Token`].
105    pub fn get_scope_for_token(&self, token: Token) -> Option<&BytesStr> {
106        let function_map = self
107            .function_maps
108            .get(token.get_src_id() as usize)?
109            .as_ref()?;
110
111        // Find the closest mapping, just like here:
112        // https://github.com/facebook/metro/blob/63b523eb20e7bdf62018aeaf195bb5a3a1a67f36/packages/metro-symbolicate/src/SourceMetadataMapConsumer.js#L204-L231
113        // Mappings use 1-based index for lines, and 0-based index for cols, as seen here:
114        // https://github.com/facebook/metro/blob/f2d80cebe66d3c64742f67259f41da26e83a0d8d/packages/metro/src/Server/symbolicate.js#L58-L60
115        let (_mapping_idx, mapping) = greatest_lower_bound(
116            &function_map.mappings,
117            &(token.get_src_line() + 1, token.get_src_col()),
118            |o| (o.line, o.column),
119        )?;
120        function_map.names.get(mapping.name_index as usize)
121    }
122
123    /// This rewrites the sourcemap according to the provided rewrite
124    /// options.
125    ///
126    /// See [`SourceMap::rewrite`](struct.SourceMap.html#method.rewrite)
127    pub fn rewrite(self, options: &RewriteOptions<'_>) -> Result<Self> {
128        let Self {
129            sm,
130            mut function_maps,
131            mut raw_facebook_sources,
132        } = self;
133
134        let (sm, mapping) = sm.rewrite_with_mapping(options)?;
135
136        if function_maps.len() >= mapping.len() {
137            function_maps = mapping
138                .iter()
139                .map(|idx| function_maps[*idx as usize].take())
140                .collect();
141            raw_facebook_sources = raw_facebook_sources.map(|mut sources| {
142                mapping
143                    .into_iter()
144                    .map(|idx| sources[idx as usize].take())
145                    .collect()
146            });
147        }
148
149        Ok(Self {
150            sm,
151            function_maps,
152            raw_facebook_sources,
153        })
154    }
155}
156
157pub fn decode_hermes(mut rsm: RawSourceMap) -> Result<SourceMapHermes> {
158    let x_facebook_sources = rsm
159        .x_facebook_sources
160        .take()
161        .ok_or(Error::IncompatibleSourceMap)?;
162
163    // This is basically the logic from here:
164    // https://github.com/facebook/metro/blob/63b523eb20e7bdf62018aeaf195bb5a3a1a67f36/packages/metro-symbolicate/src/SourceMetadataMapConsumer.js#L182-L202
165
166    let mut nums = Vec::with_capacity(4);
167
168    let function_maps = x_facebook_sources
169        .iter()
170        .map(|v| {
171            let FacebookScopeMapping {
172                names,
173                mappings: raw_mappings,
174            } = v.as_ref()?.iter().next()?;
175
176            let mut mappings = vec![];
177            let mut line = 1;
178            let mut name_index = 0;
179
180            for line_mapping in raw_mappings.split(';') {
181                if line_mapping.is_empty() {
182                    continue;
183                }
184
185                let mut column = 0;
186
187                for mapping in line_mapping.split(',') {
188                    if mapping.is_empty() {
189                        continue;
190                    }
191
192                    nums.clear();
193                    parse_vlq_segment_into(mapping, &mut nums).ok()?;
194                    let mut nums = nums.iter().copied();
195
196                    column = (i64::from(column) + nums.next()?) as u32;
197                    name_index = (i64::from(name_index) + nums.next().unwrap_or(0)) as u32;
198                    line = (i64::from(line) + nums.next().unwrap_or(0)) as u32;
199                    mappings.push(HermesScopeOffset {
200                        column,
201                        line,
202                        name_index,
203                    });
204                }
205            }
206            Some(HermesFunctionMap {
207                names: names.clone(),
208                mappings,
209            })
210        })
211        .collect();
212
213    let sm = decode_regular(rsm)?;
214    Ok(SourceMapHermes {
215        sm,
216        function_maps,
217        raw_facebook_sources: Some(x_facebook_sources),
218    })
219}