Skip to main content

swc_sourcemap/lazy/
mod.rs

1//! This is _lazy_ because we skip deserializing all the fields that we don't
2//! need. (unlike the original crate)
3
4use std::{
5    borrow::Cow,
6    collections::{BTreeSet, HashMap},
7};
8
9use bitvec::{order::Lsb0, vec::BitVec, view::BitView};
10use bytes_str::BytesStr;
11use serde::{Deserialize, Deserializer, Serialize};
12use serde_json::value::RawValue;
13
14use crate::{
15    decoder::{decode_rmi, strip_junk_header},
16    encoder::{encode_rmi, encode_vlq_diff},
17    types::adjust_mappings,
18    vlq::parse_vlq_segment_into,
19    Error, RawToken, Result,
20};
21
22#[derive(Serialize, Deserialize, Debug)]
23#[serde(rename_all = "camelCase")]
24pub struct RawSourceMap<'a> {
25    #[serde(default, skip_serializing_if = "Option::is_none")]
26    pub(crate) version: Option<u32>,
27    #[serde(default, borrow, skip_serializing_if = "Option::is_none")]
28    pub(crate) file: Option<MaybeRawValue<'a, Str>>,
29    #[serde(borrow)]
30    pub(crate) sources: MaybeRawValue<'a, Vec<StrValue<'a>>>,
31    #[serde(default, borrow, skip_serializing_if = "Option::is_none")]
32    pub(crate) source_root: Option<StrValue<'a>>,
33    #[serde(default, borrow, skip_serializing_if = "MaybeRawValue::is_empty")]
34    pub(crate) sources_content: MaybeRawValue<'a, Vec<Option<StrValue<'a>>>>,
35    #[serde(default, skip_serializing_if = "Option::is_none")]
36    pub(crate) sections: Option<Vec<RawSection<'a>>>,
37    #[serde(default, borrow)]
38    pub(crate) names: MaybeRawValue<'a, Vec<StrValue<'a>>>,
39    #[serde(default, borrow, skip_serializing_if = "Option::is_none")]
40    pub(crate) scopes: Option<StrValue<'a>>,
41    #[serde(default, skip_serializing_if = "Option::is_none")]
42    pub(crate) range_mappings: Option<String>,
43    #[serde(default, skip_serializing_if = "Option::is_none")]
44    pub(crate) mappings: Option<String>,
45    #[serde(default, borrow, skip_serializing_if = "Option::is_none")]
46    pub(crate) ignore_list: Option<MaybeRawValue<'a, BTreeSet<u32>>>,
47}
48
49#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, Copy)]
50pub struct RawSectionOffset {
51    pub line: u32,
52    pub column: u32,
53}
54
55#[derive(Serialize, Deserialize, Debug)]
56pub struct RawSection<'a> {
57    pub offset: RawSectionOffset,
58    #[serde(borrow)]
59    pub url: Option<&'a RawValue>,
60    #[serde(borrow)]
61    pub map: Option<&'a RawValue>,
62}
63
64#[derive(Debug)]
65pub enum DecodedMap<'a> {
66    Regular(SourceMap<'a>),
67    Index(SourceMapIndex<'a>),
68}
69
70impl<'a> DecodedMap<'a> {
71    pub fn into_source_map(self) -> Result<SourceMap<'a>> {
72        match self {
73            DecodedMap::Regular(source_map) => Ok(source_map),
74            DecodedMap::Index(source_map_index) => source_map_index.flatten(),
75        }
76    }
77}
78
79#[derive(Debug, Clone, Copy, Serialize)]
80#[serde(untagged)]
81pub(crate) enum MaybeRawValue<'a, T> {
82    RawValue(#[serde(borrow)] &'a RawValue),
83    Data(T),
84}
85
86impl<T> MaybeRawValue<'_, Vec<T>> {
87    pub fn is_empty(&self) -> bool {
88        match self {
89            MaybeRawValue::Data(vec) => vec.is_empty(),
90            MaybeRawValue::RawValue(_) => false,
91        }
92    }
93}
94
95impl<'a, 'de, T> Deserialize<'de> for MaybeRawValue<'a, T>
96where
97    'de: 'a,
98    T: Deserialize<'de>,
99{
100    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
101    where
102        D: Deserializer<'de>,
103    {
104        let raw: &'de RawValue = Deserialize::deserialize(deserializer)?;
105        Ok(MaybeRawValue::RawValue(raw))
106    }
107}
108
109impl<'a, T> MaybeRawValue<'a, T>
110where
111    T: Deserialize<'a>,
112{
113    pub fn into_data(self) -> T {
114        match self {
115            MaybeRawValue::RawValue(s) => {
116                serde_json::from_str(s.get()).expect("Failed to convert RawValue to Data")
117            }
118            MaybeRawValue::Data(data) => data,
119        }
120    }
121
122    fn assert_raw_value(self) -> &'a RawValue {
123        match self {
124            MaybeRawValue::RawValue(s) => s,
125            MaybeRawValue::Data(_) => unreachable!("Expected RawValue, got Data"),
126        }
127    }
128}
129
130impl<T> Default for MaybeRawValue<'_, T>
131where
132    T: Default,
133{
134    fn default() -> Self {
135        MaybeRawValue::Data(T::default())
136    }
137}
138
139impl<'a, T> MaybeRawValue<'a, T>
140where
141    T: Deserialize<'a>,
142    T: Default,
143{
144    pub fn as_data(&mut self) -> &mut T {
145        match self {
146            MaybeRawValue::RawValue(s) => {
147                *self = MaybeRawValue::Data(
148                    serde_json::from_str(s.get()).expect("Failed to convert RawValue to Data"),
149                );
150                if let MaybeRawValue::Data(data) = self {
151                    data
152                } else {
153                    unreachable!()
154                }
155            }
156            MaybeRawValue::Data(data) => data,
157        }
158    }
159}
160
161type Str = BytesStr;
162
163type StrValue<'a> = MaybeRawValue<'a, Str>;
164
165#[derive(Debug)]
166pub struct SourceMap<'a> {
167    pub(crate) file: Option<StrValue<'a>>,
168    pub(crate) tokens: Vec<RawToken>,
169    pub(crate) names: MaybeRawValue<'a, Vec<StrValue<'a>>>,
170    pub(crate) scopes: Option<StrValue<'a>>,
171    pub(crate) source_root: Option<StrValue<'a>>,
172    pub(crate) sources: MaybeRawValue<'a, Vec<StrValue<'a>>>,
173    pub(crate) sources_content: MaybeRawValue<'a, Vec<Option<StrValue<'a>>>>,
174    pub(crate) ignore_list: Option<MaybeRawValue<'a, BTreeSet<u32>>>,
175}
176
177#[derive(Debug)]
178pub(crate) struct SourceMapBuilder<'a> {
179    file: Option<StrValue<'a>>,
180    name_map: HashMap<&'a str, u32>,
181    names: Vec<StrValue<'a>>,
182    tokens: Vec<RawToken>,
183    source_map: HashMap<&'a str, u32>,
184    sources: Vec<StrValue<'a>>,
185    source_contents: Vec<Option<StrValue<'a>>>,
186    source_root: Option<StrValue<'a>>,
187    ignore_list: Option<BTreeSet<u32>>,
188}
189
190impl<'a> SourceMapBuilder<'a> {
191    pub fn new(file: Option<StrValue<'a>>) -> Self {
192        SourceMapBuilder {
193            file,
194            name_map: HashMap::new(),
195            names: Vec::new(),
196            tokens: Vec::new(),
197            source_map: HashMap::new(),
198            sources: Vec::new(),
199            source_contents: Vec::new(),
200            source_root: None,
201            ignore_list: None,
202        }
203    }
204
205    pub fn add_source(&mut self, src_raw: &'a RawValue) -> u32 {
206        let src_str = src_raw.get(); // RawValue provides get() -> &str
207        let count = self.sources.len() as u32;
208        let id = *self.source_map.entry(src_str).or_insert(count);
209        if id == count {
210            // New source
211            self.sources.push(MaybeRawValue::RawValue(src_raw));
212            // Ensure source_contents has a corresponding entry, defaulting to None.
213            // This logic ensures source_contents is always same length as sources if new
214            // one added.
215            self.source_contents.resize(self.sources.len(), None);
216        }
217        id
218    }
219
220    pub fn add_name(&mut self, name_raw: &'a RawValue) -> u32 {
221        let name_str = name_raw.get();
222        let count = self.names.len() as u32;
223        let id = *self.name_map.entry(name_str).or_insert(count);
224        if id == count {
225            // New name
226            self.names.push(MaybeRawValue::RawValue(name_raw));
227        }
228        id
229    }
230
231    pub fn set_source_contents(&mut self, src_id: u32, contents: Option<&'a RawValue>) {
232        // Ensure source_contents is large enough. src_id is 0-indexed.
233        if (src_id as usize) >= self.source_contents.len() {
234            self.source_contents.resize(src_id as usize + 1, None);
235        }
236        self.source_contents[src_id as usize] = contents.map(MaybeRawValue::RawValue);
237    }
238
239    pub fn add_to_ignore_list(&mut self, src_id: u32) {
240        self.ignore_list
241            .get_or_insert_with(BTreeSet::new)
242            .insert(src_id);
243    }
244
245    pub fn into_sourcemap(self) -> SourceMap<'a> {
246        SourceMap {
247            file: self.file,
248            tokens: self.tokens,
249            names: MaybeRawValue::Data(self.names),
250            scopes: None,
251            source_root: self.source_root,
252            sources: MaybeRawValue::Data(self.sources),
253            sources_content: MaybeRawValue::Data(self.source_contents),
254            ignore_list: self.ignore_list.map(MaybeRawValue::Data),
255        }
256    }
257
258    /// Adds a new mapping to the builder.
259    #[allow(clippy::too_many_arguments)]
260    pub fn add_raw(
261        &mut self,
262        dst_line: u32,
263        dst_col: u32,
264        src_line: u32,
265        src_col: u32,
266        source: Option<u32>,
267        name: Option<u32>,
268        is_range: bool,
269    ) -> RawToken {
270        let src_id = source.unwrap_or(!0);
271        let name_id = name.unwrap_or(!0);
272        let raw = RawToken {
273            dst_line,
274            dst_col,
275            src_line,
276            src_col,
277            src_id,
278            name_id,
279            is_range,
280        };
281        self.tokens.push(raw);
282        raw
283    }
284}
285
286#[derive(Debug)]
287pub(crate) struct SourceMapSection<'a> {
288    offset: (u32, u32),
289    url: Option<MaybeRawValue<'a, String>>,
290    map: Option<Box<MaybeRawValue<'a, RawSourceMap<'a>>>>,
291}
292
293impl<'a> SourceMapSection<'a> {
294    /// Create a new sourcemap index section
295    ///
296    /// - `offset`: offset as line and column
297    /// - `url`: optional URL of where the sourcemap is located
298    /// - `map`: an optional already resolved internal sourcemap
299    pub fn new(
300        offset: (u32, u32),
301        url: Option<MaybeRawValue<'a, String>>,
302        map: Option<MaybeRawValue<'a, RawSourceMap<'a>>>,
303    ) -> SourceMapSection<'a> {
304        SourceMapSection {
305            offset,
306            url,
307            map: map.map(Box::new),
308        }
309    }
310
311    /// Returns the offset as tuple
312    pub fn get_offset(&self) -> (u32, u32) {
313        self.offset
314    }
315}
316
317#[derive(Debug)]
318pub struct SourceMapIndex<'a> {
319    file: Option<MaybeRawValue<'a, Str>>,
320    sections: Vec<SourceMapSection<'a>>,
321}
322
323pub fn decode(slice: &[u8]) -> Result<DecodedMap<'_>> {
324    let content = strip_junk_header(slice)?;
325    let rsm: RawSourceMap = serde_json::from_slice(content)?;
326
327    decode_common(rsm)
328}
329
330fn decode_common(rsm: RawSourceMap) -> Result<DecodedMap> {
331    if rsm.sections.is_some() {
332        decode_index(rsm).map(DecodedMap::Index)
333    } else {
334        decode_regular(rsm).map(DecodedMap::Regular)
335    }
336}
337
338fn decode_index(rsm: RawSourceMap) -> Result<SourceMapIndex> {
339    let mut sections = vec![];
340
341    for raw_section in rsm.sections.unwrap_or_default() {
342        sections.push(SourceMapSection::new(
343            (raw_section.offset.line, raw_section.offset.column),
344            raw_section.url.map(MaybeRawValue::RawValue),
345            raw_section.map.map(MaybeRawValue::RawValue),
346        ));
347    }
348
349    sections.sort_by_key(SourceMapSection::get_offset);
350
351    // file sometimes is not a string for unexplicable reasons
352    let file = rsm.file;
353
354    Ok(SourceMapIndex { file, sections })
355}
356
357pub fn decode_regular(rsm: RawSourceMap) -> Result<SourceMap> {
358    let mut dst_col;
359
360    // Source IDs, lines, columns, and names are "running" values.
361    // Each token (except the first) contains the delta from the previous value.
362    let mut running_src_id = 0;
363    let mut running_src_line = 0;
364    let mut running_src_col = 0;
365    let mut running_name_id = 0;
366
367    let range_mappings = rsm.range_mappings.unwrap_or_default();
368    let mappings = rsm.mappings.unwrap_or_default();
369    let allocation_size = mappings.matches(&[',', ';'][..]).count() + 10;
370    let mut tokens = Vec::with_capacity(allocation_size);
371
372    let mut nums = Vec::with_capacity(6);
373    let mut rmi = BitVec::new();
374
375    for (dst_line, (line, rmi_str)) in mappings
376        .split(';')
377        .zip(range_mappings.split(';').chain(std::iter::repeat("")))
378        .enumerate()
379    {
380        if line.is_empty() {
381            continue;
382        }
383
384        dst_col = 0;
385
386        decode_rmi(rmi_str, &mut rmi)?;
387
388        for (line_index, segment) in line.split(',').enumerate() {
389            if segment.is_empty() {
390                continue;
391            }
392
393            nums.clear();
394            parse_vlq_segment_into(segment, &mut nums)?;
395            match nums.len() {
396                1 | 4 | 5 => {}
397                _ => return Err(Error::BadSegmentSize(nums.len() as u32)),
398            }
399
400            dst_col = (i64::from(dst_col) + nums[0]) as u32;
401
402            // The source file , source line, source column, and name
403            // may not be present in the current token. We use `u32::MAX`
404            // as the placeholder for missing values.
405            let mut current_src_id = !0;
406            let mut current_src_line = !0;
407            let mut current_src_col = !0;
408            let mut current_name_id = !0;
409
410            if nums.len() > 1 {
411                running_src_id = (i64::from(running_src_id) + nums[1]) as u32;
412
413                running_src_line = (i64::from(running_src_line) + nums[2]) as u32;
414                running_src_col = (i64::from(running_src_col) + nums[3]) as u32;
415
416                current_src_id = running_src_id;
417                current_src_line = running_src_line;
418                current_src_col = running_src_col;
419
420                if nums.len() > 4 {
421                    running_name_id = (i64::from(running_name_id) + nums[4]) as u32;
422                    current_name_id = running_name_id;
423                }
424            }
425
426            let is_range = rmi.get(line_index).map(|v| *v).unwrap_or_default();
427
428            tokens.push(RawToken {
429                dst_line: dst_line as u32,
430                dst_col,
431                src_line: current_src_line,
432                src_col: current_src_col,
433                src_id: current_src_id,
434                name_id: current_name_id,
435                is_range,
436            });
437        }
438    }
439
440    let sm = SourceMap {
441        file: rsm.file,
442        tokens,
443        names: rsm.names,
444        scopes: rsm.scopes,
445        source_root: rsm.source_root,
446        sources: rsm.sources,
447        sources_content: rsm.sources_content,
448        ignore_list: rsm.ignore_list,
449    };
450
451    Ok(sm)
452}
453
454impl<'a> SourceMap<'a> {
455    /// Refer to [crate::SourceMap::adjust_mappings] for more details.
456    pub fn adjust_mappings(&mut self, adjustment: crate::SourceMap) {
457        self.tokens = adjust_mappings(
458            std::mem::take(&mut self.tokens),
459            Cow::Owned(adjustment.tokens),
460        );
461        self.scopes = None;
462    }
463
464    pub fn into_raw_sourcemap(self) -> RawSourceMap<'a> {
465        RawSourceMap {
466            version: Some(3),
467            range_mappings: serialize_range_mappings(&self),
468            mappings: Some(serialize_mappings(&self)),
469            file: self.file,
470            sources: self.sources,
471            source_root: self.source_root,
472            sources_content: self.sources_content,
473            sections: None,
474            names: self.names,
475            scopes: self.scopes,
476            ignore_list: self.ignore_list,
477        }
478    }
479
480    pub fn file(&mut self) -> Option<&BytesStr> {
481        self.file.as_mut().map(|f| &*f.as_data())
482    }
483
484    pub fn sources(&mut self) -> impl Iterator<Item = &'_ BytesStr> + use<'_, 'a> {
485        self.sources.as_data().iter_mut().map(|d| &*d.as_data())
486    }
487
488    pub fn get_source(&mut self, src_id: u32) -> Option<&BytesStr> {
489        self.sources
490            .as_data()
491            .get_mut(src_id as usize)
492            .map(|d| &*d.as_data())
493    }
494
495    pub fn get_name(&mut self, src_id: u32) -> Option<&BytesStr> {
496        self.names
497            .as_data()
498            .get_mut(src_id as usize)
499            .map(|d| &*d.as_data())
500    }
501
502    pub fn get_source_contents(&mut self, src_id: u32) -> Option<&BytesStr> {
503        self.sources_content
504            .as_data()
505            .get_mut(src_id as usize)
506            .and_then(|d| d.as_mut().map(|d| &*d.as_data()))
507    }
508}
509
510impl<'a> SourceMapIndex<'a> {
511    pub fn flatten(self) -> Result<SourceMap<'a>> {
512        let mut builder = SourceMapBuilder::new(self.file);
513
514        for section in self.sections {
515            let (off_line, off_col) = section.get_offset();
516
517            let map = match section.map {
518                Some(map) => match decode_common(map.into_data())? {
519                    DecodedMap::Regular(sm) => sm,
520                    DecodedMap::Index(idx) => idx.flatten()?,
521                },
522                None => {
523                    return Err(Error::CannotFlatten(format!(
524                        "Section has an unresolved sourcemap: {}",
525                        section
526                            .url
527                            .map(|v| v.into_data())
528                            .as_deref()
529                            .unwrap_or("<unknown url>")
530                    )));
531                }
532            };
533
534            let sources = map.sources.into_data();
535            let source_contents = map.sources_content.into_data();
536            let ignore_list = map.ignore_list.unwrap_or_default().into_data();
537
538            let mut src_id_map = Vec::<u32>::with_capacity(sources.len());
539
540            for (original_id, (source, contents)) in
541                sources.into_iter().zip(source_contents).enumerate()
542            {
543                debug_assert_eq!(original_id, src_id_map.len());
544                let src_id = builder.add_source(source.assert_raw_value());
545
546                src_id_map.push(src_id);
547
548                if let Some(contents) = contents {
549                    builder.set_source_contents(src_id, Some(contents.assert_raw_value()));
550                }
551            }
552
553            let names = map.names.into_data();
554            let mut name_id_map = Vec::<u32>::with_capacity(names.len());
555
556            for (original_id, name) in names.into_iter().enumerate() {
557                debug_assert_eq!(original_id, name_id_map.len());
558                let name_id = builder.add_name(name.assert_raw_value());
559                name_id_map.push(name_id);
560            }
561
562            for token in map.tokens {
563                let dst_col = if token.dst_line == 0 {
564                    token.dst_col + off_col
565                } else {
566                    token.dst_col
567                };
568
569                // Use u32 -> u32 map instead of using the hash map in SourceMapBuilder for
570                // better performance
571                let original_src_id = token.src_id;
572                let src_id = if original_src_id == !0 {
573                    None
574                } else {
575                    src_id_map.get(original_src_id as usize).copied()
576                };
577
578                let original_name_id = token.name_id;
579                let name_id = if original_name_id == !0 {
580                    None
581                } else {
582                    name_id_map.get(original_name_id as usize).copied()
583                };
584
585                let raw = builder.add_raw(
586                    token.dst_line + off_line,
587                    dst_col,
588                    token.src_line,
589                    token.src_col,
590                    src_id,
591                    name_id,
592                    token.is_range,
593                );
594
595                if ignore_list.contains(&token.src_id) {
596                    builder.add_to_ignore_list(raw.src_id);
597                }
598            }
599        }
600
601        Ok(builder.into_sourcemap())
602    }
603}
604
605fn serialize_range_mappings(sm: &SourceMap) -> Option<String> {
606    let mut buf = Vec::new();
607    let mut prev_line = 0;
608    let mut had_rmi = false;
609    let mut empty = true;
610
611    let mut idx_of_first_in_line = 0;
612
613    let mut rmi_data = Vec::<u8>::new();
614
615    for (idx, token) in sm.tokens.iter().enumerate() {
616        if token.is_range {
617            had_rmi = true;
618            empty = false;
619
620            let num = idx - idx_of_first_in_line;
621
622            rmi_data.resize(rmi_data.len() + 2, 0);
623
624            let rmi_bits = rmi_data.view_bits_mut::<Lsb0>();
625            rmi_bits.set(num, true);
626        }
627
628        while token.dst_line != prev_line {
629            if had_rmi {
630                encode_rmi(&mut buf, &rmi_data);
631                rmi_data.clear();
632            }
633
634            buf.push(b';');
635            prev_line += 1;
636            had_rmi = false;
637            idx_of_first_in_line = idx;
638        }
639    }
640    if empty {
641        return None;
642    }
643
644    if had_rmi {
645        encode_rmi(&mut buf, &rmi_data);
646    }
647
648    Some(String::from_utf8(buf).expect("invalid utf8"))
649}
650
651fn serialize_mappings(sm: &SourceMap) -> String {
652    let mut rv = String::new();
653    // dst == minified == generated
654    let mut prev_dst_line = 0;
655    let mut prev_dst_col = 0;
656    let mut prev_src_line = 0;
657    let mut prev_src_col = 0;
658    let mut prev_name_id = 0;
659    let mut prev_src_id = 0;
660
661    for (idx, token) in sm.tokens.iter().enumerate() {
662        if token.dst_line != prev_dst_line {
663            prev_dst_col = 0;
664            while token.dst_line != prev_dst_line {
665                rv.push(';');
666                prev_dst_line += 1;
667            }
668        } else if idx > 0 {
669            if Some(&token) == sm.tokens.get(idx - 1).as_ref() {
670                continue;
671            }
672            rv.push(',');
673        }
674
675        encode_vlq_diff(&mut rv, token.dst_col, prev_dst_col);
676        prev_dst_col = token.dst_col;
677
678        if token.src_id != !0 {
679            encode_vlq_diff(&mut rv, token.src_id, prev_src_id);
680            prev_src_id = token.src_id;
681            encode_vlq_diff(&mut rv, token.src_line, prev_src_line);
682            prev_src_line = token.src_line;
683            encode_vlq_diff(&mut rv, token.src_col, prev_src_col);
684            prev_src_col = token.src_col;
685            if token.name_id != !0 {
686                encode_vlq_diff(&mut rv, token.name_id, prev_name_id);
687                prev_name_id = token.name_id;
688            }
689        }
690    }
691
692    rv
693}
694
695#[cfg(test)]
696mod tests {
697    use super::*;
698
699    #[test]
700    fn test_scopes_roundtrip() {
701        let map = decode(
702            br#"{
703                "version": 3,
704                "sources": ["coolstuff.js"],
705                "names": [],
706                "mappings": "AAAA",
707                "scopes": "B,A,A,C,A,A"
708            }"#,
709        )
710        .unwrap()
711        .into_source_map()
712        .unwrap();
713
714        let raw = map.into_raw_sourcemap();
715        let scopes = raw.scopes.map(|v| v.into_data().to_string());
716        assert_eq!(scopes.as_deref(), Some("B,A,A,C,A,A"));
717    }
718
719    #[test]
720    fn test_adjust_mappings_drops_scopes() {
721        let mut map = decode(
722            br#"{
723                "version": 3,
724                "sources": ["coolstuff.js"],
725                "names": [],
726                "mappings": "AAAA",
727                "scopes": "B,A,A,C,A,A"
728            }"#,
729        )
730        .unwrap()
731        .into_source_map()
732        .unwrap();
733
734        let adjustment = crate::SourceMap::from_slice(
735            br#"{
736                "version": 3,
737                "sources": ["coolstuff.js"],
738                "names": [],
739                "mappings": "AAAA"
740            }"#,
741        )
742        .unwrap();
743
744        map.adjust_mappings(adjustment);
745
746        let raw = map.into_raw_sourcemap();
747        assert!(raw.scopes.is_none());
748    }
749}