1mod 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
60const 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
71const OS_FLAG_HAS_NAME: u64 = 0x1;
75const OS_FLAG_HAS_KIND: u64 = 0x2;
76const OS_FLAG_IS_STACK_FRAME: u64 = 0x4;
77
78const 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#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
88pub struct Position {
89 pub line: u32,
90 pub column: u32,
91}
92
93#[derive(Debug, Clone, PartialEq, Eq)]
95pub struct OriginalScope {
96 pub start: Position,
97 pub end: Position,
98 pub name: Option<String>,
100 pub kind: Option<String>,
102 pub is_stack_frame: bool,
104 pub variables: Vec<String>,
106 pub children: Vec<OriginalScope>,
108}
109
110#[derive(Debug, Clone, PartialEq, Eq)]
112pub enum Binding {
113 Expression(String),
115 Unavailable,
117 SubRanges(Vec<SubRangeBinding>),
119}
120
121#[derive(Debug, Clone, PartialEq, Eq)]
123pub struct SubRangeBinding {
124 pub expression: Option<String>,
126 pub from: Position,
128}
129
130#[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#[derive(Debug, Clone, PartialEq, Eq)]
140pub struct GeneratedRange {
141 pub start: Position,
142 pub end: Position,
143 pub is_stack_frame: bool,
145 pub is_hidden: bool,
147 pub definition: Option<usize>,
149 pub call_site: Option<CallSite>,
151 pub bindings: Vec<Binding>,
153 pub children: Vec<GeneratedRange>,
155}
156
157#[derive(Debug, Clone, PartialEq, Eq)]
159pub struct ScopeInfo {
160 pub scopes: Vec<Option<OriginalScope>>,
163 pub ranges: Vec<GeneratedRange>,
165}
166
167impl ScopeInfo {
168 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#[derive(Debug)]
204pub enum ScopesError {
205 Vlq(DecodeError),
207 UnmatchedScopeEnd,
209 UnclosedScope,
211 UnmatchedRangeEnd,
213 UnclosedRange,
215 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
240fn 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
250fn 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
262fn 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#[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 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, 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 let scope0 = info.original_scope_for_definition(0).unwrap();
723 assert_eq!(scope0.kind.as_deref(), Some("global"));
724
725 let scope1 = info.original_scope_for_definition(1).unwrap();
727 assert_eq!(scope1.name.as_deref(), Some("foo"));
728
729 let scope2 = info.original_scope_for_definition(2).unwrap();
731 assert_eq!(scope2.name.as_deref(), Some("bar"));
732
733 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}