Skip to main content

srcmap_scopes/
lib.rs

1//! Scopes and variables decoder/encoder for source maps (ECMA-426).
2//!
3//! Implements the "Scopes" proposal for source maps, enabling debuggers to
4//! reconstruct original scope trees, variable bindings, and inlined function
5//! call sites from generated code.
6//!
7//! # Examples
8//!
9//! ```
10//! use srcmap_scopes::{
11//!     decode_scopes, encode_scopes, Binding, CallSite, GeneratedRange,
12//!     OriginalScope, Position, ScopeInfo,
13//! };
14//!
15//! // Build scope info
16//! let info = ScopeInfo {
17//!     scopes: vec![Some(OriginalScope {
18//!         start: Position { line: 0, column: 0 },
19//!         end: Position { line: 5, column: 0 },
20//!         name: None,
21//!         kind: Some("global".to_string()),
22//!         is_stack_frame: false,
23//!         variables: vec!["x".to_string()],
24//!         children: vec![],
25//!     })],
26//!     ranges: vec![GeneratedRange {
27//!         start: Position { line: 0, column: 0 },
28//!         end: Position { line: 5, column: 0 },
29//!         is_stack_frame: false,
30//!         is_hidden: false,
31//!         definition: Some(0),
32//!         call_site: None,
33//!         bindings: vec![Binding::Expression("_x".to_string())],
34//!         children: vec![],
35//!     }],
36//! };
37//!
38//! // Encode
39//! let mut names = vec!["global".to_string(), "x".to_string(), "_x".to_string()];
40//! let encoded = encode_scopes(&info, &mut names);
41//! assert!(!encoded.is_empty());
42//!
43//! // Decode
44//! let decoded = decode_scopes(&encoded, &names, 1).unwrap();
45//! assert_eq!(decoded.scopes.len(), 1);
46//! assert!(decoded.scopes[0].is_some());
47//! ```
48
49mod decode;
50mod encode;
51
52use std::collections::HashMap;
53use std::fmt;
54
55pub use decode::decode_scopes;
56pub use encode::encode_scopes;
57
58use srcmap_codec::DecodeError;
59
60// ── Tag constants ────────────────────────────────────────────────
61
62const TAG_ORIGINAL_SCOPE_START: u64 = 0x1;
63const TAG_ORIGINAL_SCOPE_END: u64 = 0x2;
64const TAG_ORIGINAL_SCOPE_VARIABLES: u64 = 0x3;
65const TAG_GENERATED_RANGE_START: u64 = 0x4;
66const TAG_GENERATED_RANGE_END: u64 = 0x5;
67const TAG_GENERATED_RANGE_BINDINGS: u64 = 0x6;
68const TAG_GENERATED_RANGE_SUB_RANGE_BINDINGS: u64 = 0x7;
69const TAG_GENERATED_RANGE_CALL_SITE: u64 = 0x8;
70
71// ── Flag constants ───────────────────────────────────────────────
72
73/// Flags for original scope start (B tag).
74const OS_FLAG_HAS_NAME: u64 = 0x1;
75const OS_FLAG_HAS_KIND: u64 = 0x2;
76const OS_FLAG_IS_STACK_FRAME: u64 = 0x4;
77
78/// Flags for generated range start (E tag).
79const GR_FLAG_HAS_LINE: u64 = 0x1;
80const GR_FLAG_HAS_DEFINITION: u64 = 0x2;
81const GR_FLAG_IS_STACK_FRAME: u64 = 0x4;
82const GR_FLAG_IS_HIDDEN: u64 = 0x8;
83
84// ── Public types ─────────────────────────────────────────────────
85
86/// A 0-based position in source code.
87#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
88pub struct Position {
89    pub line: u32,
90    pub column: u32,
91}
92
93/// An original scope from authored source code.
94#[derive(Debug, Clone, PartialEq, Eq)]
95pub struct OriginalScope {
96    pub start: Position,
97    pub end: Position,
98    /// Scope name (e.g., function name). Stored in the `names` array.
99    pub name: Option<String>,
100    /// Scope kind (e.g., "global", "function", "block"). Stored in `names`.
101    pub kind: Option<String>,
102    /// Whether this scope is a stack frame (function boundary).
103    pub is_stack_frame: bool,
104    /// Variables declared in this scope.
105    pub variables: Vec<String>,
106    /// Child scopes nested within this one.
107    pub children: Vec<OriginalScope>,
108}
109
110/// A binding expression for a variable in a generated range.
111#[derive(Debug, Clone, PartialEq, Eq)]
112pub enum Binding {
113    /// Variable is available via this JS expression.
114    Expression(String),
115    /// Variable is not available in this range.
116    Unavailable,
117    /// Variable has different bindings in different sub-ranges.
118    SubRanges(Vec<SubRangeBinding>),
119}
120
121/// A sub-range binding within a generated range.
122#[derive(Debug, Clone, PartialEq, Eq)]
123pub struct SubRangeBinding {
124    /// The JS expression evaluating to the variable's value. `None` = unavailable.
125    pub expression: Option<String>,
126    /// Start position of this sub-range within the generated range.
127    pub from: Position,
128}
129
130/// A call site in original source code (for inlined functions).
131#[derive(Debug, Clone, Copy, PartialEq, Eq)]
132pub struct CallSite {
133    pub source_index: u32,
134    pub line: u32,
135    pub column: u32,
136}
137
138/// A generated range in the output code.
139#[derive(Debug, Clone, PartialEq, Eq)]
140pub struct GeneratedRange {
141    pub start: Position,
142    pub end: Position,
143    /// Whether this range is a stack frame (function boundary).
144    pub is_stack_frame: bool,
145    /// Whether this stack frame should be hidden from traces.
146    pub is_hidden: bool,
147    /// Index into the pre-order list of all original scope starts.
148    pub definition: Option<usize>,
149    /// Call site if this range represents an inlined function body.
150    pub call_site: Option<CallSite>,
151    /// Variable bindings (one per variable in the referenced original scope).
152    pub bindings: Vec<Binding>,
153    /// Child ranges nested within this one.
154    pub children: Vec<GeneratedRange>,
155}
156
157/// Decoded scope information from a source map.
158#[derive(Debug, Clone, PartialEq, Eq)]
159pub struct ScopeInfo {
160    /// Original scope trees, one per source file (aligned with `sources`).
161    /// `None` means no scope info for that source file.
162    pub scopes: Vec<Option<OriginalScope>>,
163    /// Top-level generated ranges for the output code.
164    pub ranges: Vec<GeneratedRange>,
165}
166
167impl ScopeInfo {
168    /// Get the original scope referenced by a generated range's `definition` index.
169    ///
170    /// The definition index references scopes in pre-order traversal order
171    /// across all source files.
172    pub fn original_scope_for_definition(&self, definition: usize) -> Option<&OriginalScope> {
173        let mut count = 0;
174        for scope in self.scopes.iter().flatten() {
175            if let Some(result) = find_nth_scope(scope, definition, &mut count) {
176                return Some(result);
177            }
178        }
179        None
180    }
181}
182
183fn find_nth_scope<'a>(
184    scope: &'a OriginalScope,
185    target: usize,
186    count: &mut usize,
187) -> Option<&'a OriginalScope> {
188    if *count == target {
189        return Some(scope);
190    }
191    *count += 1;
192    for child in &scope.children {
193        if let Some(result) = find_nth_scope(child, target, count) {
194            return Some(result);
195        }
196    }
197    None
198}
199
200// ── Errors ───────────────────────────────────────────────────────
201
202/// Errors during scopes decoding.
203#[derive(Debug)]
204pub enum ScopesError {
205    /// VLQ decoding failed.
206    Vlq(DecodeError),
207    /// Scope end without matching scope start.
208    UnmatchedScopeEnd,
209    /// Scope was opened but never closed.
210    UnclosedScope,
211    /// Range end without matching range start.
212    UnmatchedRangeEnd,
213    /// Range was opened but never closed.
214    UnclosedRange,
215    /// Name index out of bounds.
216    InvalidNameIndex(i64),
217}
218
219impl fmt::Display for ScopesError {
220    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
221        match self {
222            Self::Vlq(e) => write!(f, "VLQ decode error: {e}"),
223            Self::UnmatchedScopeEnd => write!(f, "scope end without matching start"),
224            Self::UnclosedScope => write!(f, "scope opened but never closed"),
225            Self::UnmatchedRangeEnd => write!(f, "range end without matching start"),
226            Self::UnclosedRange => write!(f, "range opened but never closed"),
227            Self::InvalidNameIndex(idx) => write!(f, "invalid name index: {idx}"),
228        }
229    }
230}
231
232impl std::error::Error for ScopesError {}
233
234impl From<DecodeError> for ScopesError {
235    fn from(e: DecodeError) -> Self {
236        Self::Vlq(e)
237    }
238}
239
240// ── Internal helpers ─────────────────────────────────────────────
241
242/// Resolve a name from the names array by absolute index.
243fn resolve_name(names: &[String], index: i64) -> Result<String, ScopesError> {
244    if index < 0 || index as usize >= names.len() {
245        return Err(ScopesError::InvalidNameIndex(index));
246    }
247    Ok(names[index as usize].clone())
248}
249
250/// Resolve a 1-based binding name (0 = unavailable).
251fn resolve_binding(names: &[String], index: u64) -> Result<Option<String>, ScopesError> {
252    if index == 0 {
253        return Ok(None);
254    }
255    let actual = (index - 1) as usize;
256    if actual >= names.len() {
257        return Err(ScopesError::InvalidNameIndex(index as i64));
258    }
259    Ok(Some(names[actual].clone()))
260}
261
262/// Look up or insert a name, returning its 0-based index.
263fn resolve_or_add_name(
264    name: &str,
265    names: &mut Vec<String>,
266    name_map: &mut HashMap<String, u32>,
267) -> u32 {
268    if let Some(&idx) = name_map.get(name) {
269        return idx;
270    }
271    let idx = names.len() as u32;
272    names.push(name.to_string());
273    name_map.insert(name.to_string(), idx);
274    idx
275}
276
277// ── Tests ────────────────────────────────────────────────────────
278
279#[cfg(test)]
280mod tests {
281    use super::*;
282
283    #[test]
284    fn empty_scopes() {
285        let info = decode_scopes("", &[], 0).unwrap();
286        assert!(info.scopes.is_empty());
287        assert!(info.ranges.is_empty());
288    }
289
290    #[test]
291    fn empty_scopes_with_sources() {
292        // Two empty items (commas) for two source files with no scopes
293        let info = decode_scopes(",", &[], 2).unwrap();
294        assert_eq!(info.scopes.len(), 2);
295        assert!(info.scopes[0].is_none());
296        assert!(info.scopes[1].is_none());
297    }
298
299    #[test]
300    fn single_global_scope_roundtrip() {
301        let info = ScopeInfo {
302            scopes: vec![Some(OriginalScope {
303                start: Position { line: 0, column: 0 },
304                end: Position {
305                    line: 10,
306                    column: 0,
307                },
308                name: None,
309                kind: Some("global".to_string()),
310                is_stack_frame: false,
311                variables: vec!["x".to_string(), "y".to_string()],
312                children: vec![],
313            })],
314            ranges: vec![],
315        };
316
317        let mut names = vec![];
318        let encoded = encode_scopes(&info, &mut names);
319        let decoded = decode_scopes(&encoded, &names, 1).unwrap();
320
321        assert_eq!(decoded.scopes.len(), 1);
322        let scope = decoded.scopes[0].as_ref().unwrap();
323        assert_eq!(scope.start, Position { line: 0, column: 0 });
324        assert_eq!(
325            scope.end,
326            Position {
327                line: 10,
328                column: 0
329            }
330        );
331        assert_eq!(scope.kind.as_deref(), Some("global"));
332        assert_eq!(scope.name, None);
333        assert!(!scope.is_stack_frame);
334        assert_eq!(scope.variables, vec!["x", "y"]);
335    }
336
337    #[test]
338    fn nested_scopes_roundtrip() {
339        let info = ScopeInfo {
340            scopes: vec![Some(OriginalScope {
341                start: Position { line: 0, column: 0 },
342                end: Position {
343                    line: 10,
344                    column: 1,
345                },
346                name: None,
347                kind: Some("global".to_string()),
348                is_stack_frame: false,
349                variables: vec!["z".to_string()],
350                children: vec![OriginalScope {
351                    start: Position { line: 1, column: 0 },
352                    end: Position { line: 5, column: 1 },
353                    name: Some("hello".to_string()),
354                    kind: Some("function".to_string()),
355                    is_stack_frame: true,
356                    variables: vec!["msg".to_string(), "result".to_string()],
357                    children: vec![],
358                }],
359            })],
360            ranges: vec![],
361        };
362
363        let mut names = vec![];
364        let encoded = encode_scopes(&info, &mut names);
365        let decoded = decode_scopes(&encoded, &names, 1).unwrap();
366
367        let scope = decoded.scopes[0].as_ref().unwrap();
368        assert_eq!(scope.children.len(), 1);
369        let child = &scope.children[0];
370        assert_eq!(child.start, Position { line: 1, column: 0 });
371        assert_eq!(child.end, Position { line: 5, column: 1 });
372        assert_eq!(child.name.as_deref(), Some("hello"));
373        assert_eq!(child.kind.as_deref(), Some("function"));
374        assert!(child.is_stack_frame);
375        assert_eq!(child.variables, vec!["msg", "result"]);
376    }
377
378    #[test]
379    fn multiple_sources_with_gaps() {
380        let info = ScopeInfo {
381            scopes: vec![
382                Some(OriginalScope {
383                    start: Position { line: 0, column: 0 },
384                    end: Position { line: 5, column: 0 },
385                    name: None,
386                    kind: None,
387                    is_stack_frame: false,
388                    variables: vec![],
389                    children: vec![],
390                }),
391                None, // second source has no scopes
392                Some(OriginalScope {
393                    start: Position { line: 0, column: 0 },
394                    end: Position { line: 3, column: 0 },
395                    name: None,
396                    kind: None,
397                    is_stack_frame: false,
398                    variables: vec![],
399                    children: vec![],
400                }),
401            ],
402            ranges: vec![],
403        };
404
405        let mut names = vec![];
406        let encoded = encode_scopes(&info, &mut names);
407        let decoded = decode_scopes(&encoded, &names, 3).unwrap();
408
409        assert_eq!(decoded.scopes.len(), 3);
410        assert!(decoded.scopes[0].is_some());
411        assert!(decoded.scopes[1].is_none());
412        assert!(decoded.scopes[2].is_some());
413    }
414
415    #[test]
416    fn generated_ranges_roundtrip() {
417        let info = ScopeInfo {
418            scopes: vec![Some(OriginalScope {
419                start: Position { line: 0, column: 0 },
420                end: Position {
421                    line: 10,
422                    column: 0,
423                },
424                name: None,
425                kind: Some("global".to_string()),
426                is_stack_frame: false,
427                variables: vec!["x".to_string()],
428                children: vec![],
429            })],
430            ranges: vec![GeneratedRange {
431                start: Position { line: 0, column: 0 },
432                end: Position {
433                    line: 10,
434                    column: 0,
435                },
436                is_stack_frame: false,
437                is_hidden: false,
438                definition: Some(0),
439                call_site: None,
440                bindings: vec![Binding::Expression("_x".to_string())],
441                children: vec![],
442            }],
443        };
444
445        let mut names = vec![];
446        let encoded = encode_scopes(&info, &mut names);
447        let decoded = decode_scopes(&encoded, &names, 1).unwrap();
448
449        assert_eq!(decoded.ranges.len(), 1);
450        let range = &decoded.ranges[0];
451        assert_eq!(range.start, Position { line: 0, column: 0 });
452        assert_eq!(
453            range.end,
454            Position {
455                line: 10,
456                column: 0
457            }
458        );
459        assert_eq!(range.definition, Some(0));
460        assert_eq!(range.bindings, vec![Binding::Expression("_x".to_string())]);
461    }
462
463    #[test]
464    fn nested_ranges_with_inlining() {
465        let info = ScopeInfo {
466            scopes: vec![Some(OriginalScope {
467                start: Position { line: 0, column: 0 },
468                end: Position {
469                    line: 10,
470                    column: 0,
471                },
472                name: None,
473                kind: Some("global".to_string()),
474                is_stack_frame: false,
475                variables: vec!["x".to_string()],
476                children: vec![OriginalScope {
477                    start: Position { line: 1, column: 0 },
478                    end: Position { line: 5, column: 1 },
479                    name: Some("fn1".to_string()),
480                    kind: Some("function".to_string()),
481                    is_stack_frame: true,
482                    variables: vec!["a".to_string()],
483                    children: vec![],
484                }],
485            })],
486            ranges: vec![GeneratedRange {
487                start: Position { line: 0, column: 0 },
488                end: Position {
489                    line: 10,
490                    column: 0,
491                },
492                is_stack_frame: false,
493                is_hidden: false,
494                definition: Some(0),
495                call_site: None,
496                bindings: vec![Binding::Expression("_x".to_string())],
497                children: vec![GeneratedRange {
498                    start: Position { line: 6, column: 0 },
499                    end: Position {
500                        line: 8,
501                        column: 20,
502                    },
503                    is_stack_frame: true,
504                    is_hidden: false,
505                    definition: Some(1),
506                    call_site: Some(CallSite {
507                        source_index: 0,
508                        line: 7,
509                        column: 0,
510                    }),
511                    bindings: vec![Binding::Expression("\"hello\"".to_string())],
512                    children: vec![],
513                }],
514            }],
515        };
516
517        let mut names = vec![];
518        let encoded = encode_scopes(&info, &mut names);
519        let decoded = decode_scopes(&encoded, &names, 1).unwrap();
520
521        assert_eq!(decoded.ranges.len(), 1);
522        let outer = &decoded.ranges[0];
523        assert_eq!(outer.children.len(), 1);
524        let inner = &outer.children[0];
525        assert!(inner.is_stack_frame);
526        assert_eq!(inner.definition, Some(1));
527        assert_eq!(
528            inner.call_site,
529            Some(CallSite {
530                source_index: 0,
531                line: 7,
532                column: 0,
533            })
534        );
535        assert_eq!(
536            inner.bindings,
537            vec![Binding::Expression("\"hello\"".to_string())]
538        );
539    }
540
541    #[test]
542    fn unavailable_bindings() {
543        let info = ScopeInfo {
544            scopes: vec![Some(OriginalScope {
545                start: Position { line: 0, column: 0 },
546                end: Position { line: 5, column: 0 },
547                name: None,
548                kind: None,
549                is_stack_frame: false,
550                variables: vec!["a".to_string(), "b".to_string()],
551                children: vec![],
552            })],
553            ranges: vec![GeneratedRange {
554                start: Position { line: 0, column: 0 },
555                end: Position { line: 5, column: 0 },
556                is_stack_frame: false,
557                is_hidden: false,
558                definition: Some(0),
559                call_site: None,
560                bindings: vec![Binding::Expression("_a".to_string()), Binding::Unavailable],
561                children: vec![],
562            }],
563        };
564
565        let mut names = vec![];
566        let encoded = encode_scopes(&info, &mut names);
567        let decoded = decode_scopes(&encoded, &names, 1).unwrap();
568
569        assert_eq!(
570            decoded.ranges[0].bindings,
571            vec![Binding::Expression("_a".to_string()), Binding::Unavailable,]
572        );
573    }
574
575    #[test]
576    fn sub_range_bindings_roundtrip() {
577        let info = ScopeInfo {
578            scopes: vec![Some(OriginalScope {
579                start: Position { line: 0, column: 0 },
580                end: Position {
581                    line: 20,
582                    column: 0,
583                },
584                name: None,
585                kind: None,
586                is_stack_frame: false,
587                variables: vec!["x".to_string(), "y".to_string()],
588                children: vec![],
589            })],
590            ranges: vec![GeneratedRange {
591                start: Position { line: 0, column: 0 },
592                end: Position {
593                    line: 20,
594                    column: 0,
595                },
596                is_stack_frame: false,
597                is_hidden: false,
598                definition: Some(0),
599                call_site: None,
600                bindings: vec![
601                    Binding::SubRanges(vec![
602                        SubRangeBinding {
603                            expression: Some("a".to_string()),
604                            from: Position { line: 0, column: 0 },
605                        },
606                        SubRangeBinding {
607                            expression: Some("b".to_string()),
608                            from: Position { line: 5, column: 0 },
609                        },
610                        SubRangeBinding {
611                            expression: None,
612                            from: Position {
613                                line: 10,
614                                column: 0,
615                            },
616                        },
617                    ]),
618                    Binding::Expression("_y".to_string()),
619                ],
620                children: vec![],
621            }],
622        };
623
624        let mut names = vec![];
625        let encoded = encode_scopes(&info, &mut names);
626        let decoded = decode_scopes(&encoded, &names, 1).unwrap();
627
628        let bindings = &decoded.ranges[0].bindings;
629        assert_eq!(bindings.len(), 2);
630
631        match &bindings[0] {
632            Binding::SubRanges(subs) => {
633                assert_eq!(subs.len(), 3);
634                assert_eq!(subs[0].expression.as_deref(), Some("a"));
635                assert_eq!(subs[0].from, Position { line: 0, column: 0 });
636                assert_eq!(subs[1].expression.as_deref(), Some("b"));
637                assert_eq!(subs[1].from, Position { line: 5, column: 0 });
638                assert_eq!(subs[2].expression, None);
639                assert_eq!(
640                    subs[2].from,
641                    Position {
642                        line: 10,
643                        column: 0,
644                    }
645                );
646            }
647            other => panic!("expected SubRanges, got {other:?}"),
648        }
649        assert_eq!(bindings[1], Binding::Expression("_y".to_string()));
650    }
651
652    #[test]
653    fn hidden_range() {
654        let info = ScopeInfo {
655            scopes: vec![Some(OriginalScope {
656                start: Position { line: 0, column: 0 },
657                end: Position { line: 5, column: 0 },
658                name: None,
659                kind: None,
660                is_stack_frame: false,
661                variables: vec![],
662                children: vec![],
663            })],
664            ranges: vec![GeneratedRange {
665                start: Position { line: 0, column: 0 },
666                end: Position { line: 5, column: 0 },
667                is_stack_frame: true,
668                is_hidden: true,
669                definition: Some(0),
670                call_site: None,
671                bindings: vec![],
672                children: vec![],
673            }],
674        };
675
676        let mut names = vec![];
677        let encoded = encode_scopes(&info, &mut names);
678        let decoded = decode_scopes(&encoded, &names, 1).unwrap();
679
680        assert!(decoded.ranges[0].is_stack_frame);
681        assert!(decoded.ranges[0].is_hidden);
682    }
683
684    #[test]
685    fn definition_resolution() {
686        let info = ScopeInfo {
687            scopes: vec![Some(OriginalScope {
688                start: Position { line: 0, column: 0 },
689                end: Position {
690                    line: 10,
691                    column: 0,
692                },
693                name: None,
694                kind: Some("global".to_string()),
695                is_stack_frame: false,
696                variables: vec![],
697                children: vec![
698                    OriginalScope {
699                        start: Position { line: 1, column: 0 },
700                        end: Position { line: 4, column: 1 },
701                        name: Some("foo".to_string()),
702                        kind: Some("function".to_string()),
703                        is_stack_frame: true,
704                        variables: vec![],
705                        children: vec![],
706                    },
707                    OriginalScope {
708                        start: Position { line: 5, column: 0 },
709                        end: Position { line: 9, column: 1 },
710                        name: Some("bar".to_string()),
711                        kind: Some("function".to_string()),
712                        is_stack_frame: true,
713                        variables: vec![],
714                        children: vec![],
715                    },
716                ],
717            })],
718            ranges: vec![],
719        };
720
721        // Definition 0 = global scope
722        let scope0 = info.original_scope_for_definition(0).unwrap();
723        assert_eq!(scope0.kind.as_deref(), Some("global"));
724
725        // Definition 1 = foo
726        let scope1 = info.original_scope_for_definition(1).unwrap();
727        assert_eq!(scope1.name.as_deref(), Some("foo"));
728
729        // Definition 2 = bar
730        let scope2 = info.original_scope_for_definition(2).unwrap();
731        assert_eq!(scope2.name.as_deref(), Some("bar"));
732
733        // Definition 3 = out of bounds
734        assert!(info.original_scope_for_definition(3).is_none());
735    }
736
737    #[test]
738    fn scopes_only_no_ranges() {
739        let info = ScopeInfo {
740            scopes: vec![Some(OriginalScope {
741                start: Position { line: 0, column: 0 },
742                end: Position { line: 5, column: 0 },
743                name: None,
744                kind: None,
745                is_stack_frame: false,
746                variables: vec![],
747                children: vec![],
748            })],
749            ranges: vec![],
750        };
751
752        let mut names = vec![];
753        let encoded = encode_scopes(&info, &mut names);
754        let decoded = decode_scopes(&encoded, &names, 1).unwrap();
755
756        assert_eq!(decoded.scopes.len(), 1);
757        assert!(decoded.scopes[0].is_some());
758        assert!(decoded.ranges.is_empty());
759    }
760
761    #[test]
762    fn ranges_only_no_scopes() {
763        let info = ScopeInfo {
764            scopes: vec![None],
765            ranges: vec![GeneratedRange {
766                start: Position { line: 0, column: 0 },
767                end: Position { line: 5, column: 0 },
768                is_stack_frame: false,
769                is_hidden: false,
770                definition: None,
771                call_site: None,
772                bindings: vec![],
773                children: vec![],
774            }],
775        };
776
777        let mut names = vec![];
778        let encoded = encode_scopes(&info, &mut names);
779        let decoded = decode_scopes(&encoded, &names, 1).unwrap();
780
781        assert_eq!(decoded.scopes.len(), 1);
782        assert!(decoded.scopes[0].is_none());
783        assert_eq!(decoded.ranges.len(), 1);
784    }
785
786    #[test]
787    fn range_no_definition() {
788        let info = ScopeInfo {
789            scopes: vec![],
790            ranges: vec![GeneratedRange {
791                start: Position { line: 0, column: 0 },
792                end: Position { line: 5, column: 0 },
793                is_stack_frame: false,
794                is_hidden: false,
795                definition: None,
796                call_site: None,
797                bindings: vec![],
798                children: vec![],
799            }],
800        };
801
802        let mut names = vec![];
803        let encoded = encode_scopes(&info, &mut names);
804        let decoded = decode_scopes(&encoded, &names, 0).unwrap();
805
806        assert_eq!(decoded.ranges.len(), 1);
807        assert_eq!(decoded.ranges[0].definition, None);
808    }
809}