1use std::collections::HashMap;
28
29use srcmap_codec::vlq_encode;
30use srcmap_scopes::ScopeInfo;
31
32#[derive(Debug, Clone)]
36pub struct Mapping {
37 pub generated_line: u32,
38 pub generated_column: u32,
39 pub source: Option<u32>,
40 pub original_line: u32,
41 pub original_column: u32,
42 pub name: Option<u32>,
43}
44
45#[derive(Debug)]
47pub struct SourceMapGenerator {
48 file: Option<String>,
49 source_root: Option<String>,
50 sources: Vec<String>,
51 sources_content: Vec<Option<String>>,
52 names: Vec<String>,
53 mappings: Vec<Mapping>,
54 ignore_list: Vec<u32>,
55 debug_id: Option<String>,
56 scopes: Option<ScopeInfo>,
57
58 source_map: HashMap<String, u32>,
60 name_map: HashMap<String, u32>,
61}
62
63impl SourceMapGenerator {
64 pub fn new(file: Option<String>) -> Self {
66 Self {
67 file,
68 source_root: None,
69 sources: Vec::new(),
70 sources_content: Vec::new(),
71 names: Vec::new(),
72 mappings: Vec::new(),
73 ignore_list: Vec::new(),
74 debug_id: None,
75 scopes: None,
76 source_map: HashMap::new(),
77 name_map: HashMap::new(),
78 }
79 }
80
81 pub fn set_source_root(&mut self, root: String) {
83 self.source_root = Some(root);
84 }
85
86 pub fn set_debug_id(&mut self, id: String) {
88 self.debug_id = Some(id);
89 }
90
91 pub fn set_scopes(&mut self, scopes: ScopeInfo) {
93 self.scopes = Some(scopes);
94 }
95
96 pub fn add_source(&mut self, source: &str) -> u32 {
98 if let Some(&idx) = self.source_map.get(source) {
99 return idx;
100 }
101 let idx = self.sources.len() as u32;
102 self.sources.push(source.to_string());
103 self.sources_content.push(None);
104 self.source_map.insert(source.to_string(), idx);
105 idx
106 }
107
108 pub fn set_source_content(&mut self, source_idx: u32, content: String) {
110 if (source_idx as usize) < self.sources_content.len() {
111 self.sources_content[source_idx as usize] = Some(content);
112 }
113 }
114
115 pub fn add_name(&mut self, name: &str) -> u32 {
117 if let Some(&idx) = self.name_map.get(name) {
118 return idx;
119 }
120 let idx = self.names.len() as u32;
121 self.names.push(name.to_string());
122 self.name_map.insert(name.to_string(), idx);
123 idx
124 }
125
126 pub fn add_to_ignore_list(&mut self, source_idx: u32) {
128 if !self.ignore_list.contains(&source_idx) {
129 self.ignore_list.push(source_idx);
130 }
131 }
132
133 pub fn add_generated_mapping(&mut self, generated_line: u32, generated_column: u32) {
135 self.mappings.push(Mapping {
136 generated_line,
137 generated_column,
138 source: None,
139 original_line: 0,
140 original_column: 0,
141 name: None,
142 });
143 }
144
145 pub fn add_mapping(
147 &mut self,
148 generated_line: u32,
149 generated_column: u32,
150 source: u32,
151 original_line: u32,
152 original_column: u32,
153 ) {
154 self.mappings.push(Mapping {
155 generated_line,
156 generated_column,
157 source: Some(source),
158 original_line,
159 original_column,
160 name: None,
161 });
162 }
163
164 pub fn add_named_mapping(
166 &mut self,
167 generated_line: u32,
168 generated_column: u32,
169 source: u32,
170 original_line: u32,
171 original_column: u32,
172 name: u32,
173 ) {
174 self.mappings.push(Mapping {
175 generated_line,
176 generated_column,
177 source: Some(source),
178 original_line,
179 original_column,
180 name: Some(name),
181 });
182 }
183
184 pub fn maybe_add_mapping(
190 &mut self,
191 generated_line: u32,
192 generated_column: u32,
193 source: u32,
194 original_line: u32,
195 original_column: u32,
196 ) -> bool {
197 if let Some(last) = self.mappings.last()
198 && last.generated_line == generated_line
199 && last.source == Some(source)
200 && last.original_line == original_line
201 && last.original_column == original_column
202 {
203 return false;
204 }
205 self.add_mapping(
206 generated_line,
207 generated_column,
208 source,
209 original_line,
210 original_column,
211 );
212 true
213 }
214
215 fn encode_mappings(&self) -> String {
217 if self.mappings.is_empty() {
218 return String::new();
219 }
220
221 let mut sorted: Vec<&Mapping> = self.mappings.iter().collect();
223 sorted.sort_unstable_by(|a, b| {
224 a.generated_line
225 .cmp(&b.generated_line)
226 .then(a.generated_column.cmp(&b.generated_column))
227 });
228
229 #[cfg(feature = "parallel")]
230 if sorted.len() >= 4096 {
231 return Self::encode_parallel_impl(&sorted);
232 }
233
234 Self::encode_sequential_impl(&sorted)
235 }
236
237 fn encode_sequential_impl(sorted: &[&Mapping]) -> String {
238 let mut out: Vec<u8> = Vec::with_capacity(sorted.len() * 6);
239
240 let mut prev_gen_col: i64 = 0;
241 let mut prev_source: i64 = 0;
242 let mut prev_orig_line: i64 = 0;
243 let mut prev_orig_col: i64 = 0;
244 let mut prev_name: i64 = 0;
245 let mut prev_gen_line: u32 = 0;
246 let mut first_in_line = true;
247
248 for m in sorted {
249 while prev_gen_line < m.generated_line {
250 out.push(b';');
251 prev_gen_line += 1;
252 prev_gen_col = 0;
253 first_in_line = true;
254 }
255
256 if !first_in_line {
257 out.push(b',');
258 }
259 first_in_line = false;
260
261 vlq_encode(&mut out, m.generated_column as i64 - prev_gen_col);
262 prev_gen_col = m.generated_column as i64;
263
264 if let Some(source) = m.source {
265 vlq_encode(&mut out, source as i64 - prev_source);
266 prev_source = source as i64;
267
268 vlq_encode(&mut out, m.original_line as i64 - prev_orig_line);
269 prev_orig_line = m.original_line as i64;
270
271 vlq_encode(&mut out, m.original_column as i64 - prev_orig_col);
272 prev_orig_col = m.original_column as i64;
273
274 if let Some(name) = m.name {
275 vlq_encode(&mut out, name as i64 - prev_name);
276 prev_name = name as i64;
277 }
278 }
279 }
280
281 unsafe { String::from_utf8_unchecked(out) }
283 }
284
285 #[cfg(feature = "parallel")]
286 fn encode_parallel_impl(sorted: &[&Mapping]) -> String {
287 use rayon::prelude::*;
288
289 let max_line = sorted.last().unwrap().generated_line as usize;
290
291 let mut line_ranges: Vec<(usize, usize)> = vec![(0, 0); max_line + 1];
293 let mut i = 0;
294 while i < sorted.len() {
295 let line = sorted[i].generated_line as usize;
296 let start = i;
297 while i < sorted.len() && sorted[i].generated_line as usize == line {
298 i += 1;
299 }
300 line_ranges[line] = (start, i);
301 }
302
303 let mut states: Vec<(i64, i64, i64, i64)> = Vec::with_capacity(max_line + 1);
305 let mut prev_source: i64 = 0;
306 let mut prev_orig_line: i64 = 0;
307 let mut prev_orig_col: i64 = 0;
308 let mut prev_name: i64 = 0;
309
310 for &(start, end) in &line_ranges {
311 states.push((prev_source, prev_orig_line, prev_orig_col, prev_name));
312 for m in &sorted[start..end] {
313 if let Some(source) = m.source {
314 prev_source = source as i64;
315 prev_orig_line = m.original_line as i64;
316 prev_orig_col = m.original_column as i64;
317 if let Some(name) = m.name {
318 prev_name = name as i64;
319 }
320 }
321 }
322 }
323
324 let encoded_lines: Vec<Vec<u8>> = line_ranges
326 .par_iter()
327 .zip(states.par_iter())
328 .map(|(&(start, end), &(s, ol, oc, n))| {
329 if start == end {
330 return Vec::new();
331 }
332 encode_mapping_slice(&sorted[start..end], s, ol, oc, n)
333 })
334 .collect();
335
336 let total_len = encoded_lines.iter().map(|l| l.len()).sum::<usize>() + max_line;
338 let mut out: Vec<u8> = Vec::with_capacity(total_len);
339 for (i, bytes) in encoded_lines.iter().enumerate() {
340 if i > 0 {
341 out.push(b';');
342 }
343 out.extend_from_slice(bytes);
344 }
345
346 unsafe { String::from_utf8_unchecked(out) }
348 }
349
350 pub fn to_json(&self) -> String {
352 let mappings = self.encode_mappings();
353
354 let (scopes_str, names_for_json) = if let Some(ref scopes_info) = self.scopes {
356 let mut names = self.names.clone();
357 let s = srcmap_scopes::encode_scopes(scopes_info, &mut names);
358 (Some(s), names)
359 } else {
360 (None, self.names.clone())
361 };
362
363 let mut json = String::with_capacity(256 + mappings.len());
364 json.push_str(r#"{"version":3"#);
365
366 if let Some(ref file) = self.file {
367 json.push_str(r#","file":"#);
368 json.push_str(&json_quote(file));
369 }
370
371 if let Some(ref root) = self.source_root {
372 json.push_str(r#","sourceRoot":"#);
373 json.push_str(&json_quote(root));
374 }
375
376 json.push_str(r#","sources":["#);
378 for (i, s) in self.sources.iter().enumerate() {
379 if i > 0 {
380 json.push(',');
381 }
382 json.push_str(&json_quote(s));
383 }
384 json.push(']');
385
386 if self.sources_content.iter().any(|c| c.is_some()) {
388 json.push_str(r#","sourcesContent":["#);
389
390 #[cfg(feature = "parallel")]
391 {
392 use rayon::prelude::*;
393
394 let total_content: usize = self
395 .sources_content
396 .iter()
397 .map(|c| c.as_ref().map_or(0, |s| s.len()))
398 .sum();
399
400 if self.sources_content.len() >= 8 && total_content >= 8192 {
401 let quoted: Vec<String> = self
402 .sources_content
403 .par_iter()
404 .map(|c| match c {
405 Some(content) => json_quote(content),
406 None => "null".to_string(),
407 })
408 .collect();
409 for (i, q) in quoted.iter().enumerate() {
410 if i > 0 {
411 json.push(',');
412 }
413 json.push_str(q);
414 }
415 } else {
416 for (i, c) in self.sources_content.iter().enumerate() {
417 if i > 0 {
418 json.push(',');
419 }
420 match c {
421 Some(content) => json.push_str(&json_quote(content)),
422 None => json.push_str("null"),
423 }
424 }
425 }
426 }
427
428 #[cfg(not(feature = "parallel"))]
429 for (i, c) in self.sources_content.iter().enumerate() {
430 if i > 0 {
431 json.push(',');
432 }
433 match c {
434 Some(content) => json.push_str(&json_quote(content)),
435 None => json.push_str("null"),
436 }
437 }
438
439 json.push(']');
440 }
441
442 json.push_str(r#","names":["#);
444 for (i, n) in names_for_json.iter().enumerate() {
445 if i > 0 {
446 json.push(',');
447 }
448 json.push_str(&json_quote(n));
449 }
450 json.push(']');
451
452 json.push_str(r#","mappings":"#);
454 json.push_str(&json_quote(&mappings));
455
456 if !self.ignore_list.is_empty() {
458 json.push_str(r#","ignoreList":["#);
459 for (i, &idx) in self.ignore_list.iter().enumerate() {
460 if i > 0 {
461 json.push(',');
462 }
463 json.push_str(&idx.to_string());
464 }
465 json.push(']');
466 }
467
468 if let Some(ref id) = self.debug_id {
470 json.push_str(r#","debugId":"#);
471 json.push_str(&json_quote(id));
472 }
473
474 if let Some(ref s) = scopes_str {
476 json.push_str(r#","scopes":"#);
477 json.push_str(&json_quote(s));
478 }
479
480 json.push('}');
481 json
482 }
483
484 pub fn mapping_count(&self) -> usize {
486 self.mappings.len()
487 }
488}
489
490#[cfg(feature = "parallel")]
495fn encode_mapping_slice(
496 mappings: &[&Mapping],
497 init_source: i64,
498 init_orig_line: i64,
499 init_orig_col: i64,
500 init_name: i64,
501) -> Vec<u8> {
502 let mut buf = Vec::with_capacity(mappings.len() * 6);
503 let mut prev_gen_col: i64 = 0;
504 let mut prev_source = init_source;
505 let mut prev_orig_line = init_orig_line;
506 let mut prev_orig_col = init_orig_col;
507 let mut prev_name = init_name;
508 let mut first = true;
509
510 for m in mappings {
511 if !first {
512 buf.push(b',');
513 }
514 first = false;
515
516 vlq_encode(&mut buf, m.generated_column as i64 - prev_gen_col);
517 prev_gen_col = m.generated_column as i64;
518
519 if let Some(source) = m.source {
520 vlq_encode(&mut buf, source as i64 - prev_source);
521 prev_source = source as i64;
522
523 vlq_encode(&mut buf, m.original_line as i64 - prev_orig_line);
524 prev_orig_line = m.original_line as i64;
525
526 vlq_encode(&mut buf, m.original_column as i64 - prev_orig_col);
527 prev_orig_col = m.original_column as i64;
528
529 if let Some(name) = m.name {
530 vlq_encode(&mut buf, name as i64 - prev_name);
531 prev_name = name as i64;
532 }
533 }
534 }
535
536 buf
537}
538
539fn json_quote(s: &str) -> String {
541 let mut out = String::with_capacity(s.len() + 2);
542 out.push('"');
543 for c in s.chars() {
544 match c {
545 '"' => out.push_str("\\\""),
546 '\\' => out.push_str("\\\\"),
547 '\n' => out.push_str("\\n"),
548 '\r' => out.push_str("\\r"),
549 '\t' => out.push_str("\\t"),
550 c if c < '\x20' => {
551 out.push_str(&format!("\\u{:04x}", c as u32));
552 }
553 c => out.push(c),
554 }
555 }
556 out.push('"');
557 out
558}
559
560#[cfg(test)]
563mod tests {
564 use super::*;
565
566 #[test]
567 fn empty_generator() {
568 let builder = SourceMapGenerator::new(None);
569 let json = builder.to_json();
570 assert!(json.contains(r#""version":3"#));
571 assert!(json.contains(r#""mappings":"""#));
572 }
573
574 #[test]
575 fn simple_mapping() {
576 let mut builder = SourceMapGenerator::new(Some("output.js".to_string()));
577 let src = builder.add_source("input.js");
578 builder.add_mapping(0, 0, src, 0, 0);
579
580 let json = builder.to_json();
581 assert!(json.contains(r#""file":"output.js""#));
582 assert!(json.contains(r#""sources":["input.js"]"#));
583
584 let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
586 let loc = sm.original_position_for(0, 0).unwrap();
587 assert_eq!(sm.source(loc.source), "input.js");
588 assert_eq!(loc.line, 0);
589 assert_eq!(loc.column, 0);
590 }
591
592 #[test]
593 fn mapping_with_name() {
594 let mut builder = SourceMapGenerator::new(None);
595 let src = builder.add_source("input.js");
596 let name = builder.add_name("myFunction");
597 builder.add_named_mapping(0, 0, src, 0, 0, name);
598
599 let json = builder.to_json();
600 assert!(json.contains(r#""names":["myFunction"]"#));
601
602 let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
603 let loc = sm.original_position_for(0, 0).unwrap();
604 assert_eq!(loc.name, Some(0));
605 assert_eq!(sm.name(0), "myFunction");
606 }
607
608 #[test]
609 fn multiple_lines() {
610 let mut builder = SourceMapGenerator::new(None);
611 let src = builder.add_source("input.js");
612 builder.add_mapping(0, 0, src, 0, 0);
613 builder.add_mapping(1, 4, src, 1, 2);
614 builder.add_mapping(2, 0, src, 2, 0);
615
616 let json = builder.to_json();
617 let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
618 assert_eq!(sm.line_count(), 3);
619
620 let loc = sm.original_position_for(1, 4).unwrap();
621 assert_eq!(loc.line, 1);
622 assert_eq!(loc.column, 2);
623 }
624
625 #[test]
626 fn multiple_sources() {
627 let mut builder = SourceMapGenerator::new(None);
628 let a = builder.add_source("a.js");
629 let b = builder.add_source("b.js");
630 builder.add_mapping(0, 0, a, 0, 0);
631 builder.add_mapping(1, 0, b, 0, 0);
632
633 let json = builder.to_json();
634 let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
635
636 let loc0 = sm.original_position_for(0, 0).unwrap();
637 let loc1 = sm.original_position_for(1, 0).unwrap();
638 assert_eq!(sm.source(loc0.source), "a.js");
639 assert_eq!(sm.source(loc1.source), "b.js");
640 }
641
642 #[test]
643 fn source_content() {
644 let mut builder = SourceMapGenerator::new(None);
645 let src = builder.add_source("input.js");
646 builder.set_source_content(src, "var x = 1;".to_string());
647 builder.add_mapping(0, 0, src, 0, 0);
648
649 let json = builder.to_json();
650 assert!(json.contains(r#""sourcesContent":["var x = 1;"]"#));
651
652 let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
653 assert_eq!(sm.sources_content[0], Some("var x = 1;".to_string()));
654 }
655
656 #[test]
657 fn source_root() {
658 let mut builder = SourceMapGenerator::new(None);
659 builder.set_source_root("src/".to_string());
660 let src = builder.add_source("input.js");
661 builder.add_mapping(0, 0, src, 0, 0);
662
663 let json = builder.to_json();
664 assert!(json.contains(r#""sourceRoot":"src/""#));
665 }
666
667 #[test]
668 fn ignore_list() {
669 let mut builder = SourceMapGenerator::new(None);
670 let _app = builder.add_source("app.js");
671 let lib = builder.add_source("node_modules/lib.js");
672 builder.add_to_ignore_list(lib);
673 builder.add_mapping(0, 0, lib, 0, 0);
674
675 let json = builder.to_json();
676 assert!(json.contains(r#""ignoreList":[1]"#));
677 }
678
679 #[test]
680 fn generated_only_mapping() {
681 let mut builder = SourceMapGenerator::new(None);
682 builder.add_generated_mapping(0, 0);
683
684 let json = builder.to_json();
685 let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
686 assert!(sm.original_position_for(0, 0).is_none());
688 }
689
690 #[test]
691 fn dedup_sources_and_names() {
692 let mut builder = SourceMapGenerator::new(None);
693 let s1 = builder.add_source("input.js");
694 let s2 = builder.add_source("input.js"); assert_eq!(s1, s2);
696
697 let n1 = builder.add_name("foo");
698 let n2 = builder.add_name("foo"); assert_eq!(n1, n2);
700
701 assert_eq!(builder.sources.len(), 1);
702 assert_eq!(builder.names.len(), 1);
703 }
704
705 #[test]
706 fn large_roundtrip() {
707 let mut builder = SourceMapGenerator::new(Some("bundle.js".to_string()));
708
709 for i in 0..5 {
710 builder.add_source(&format!("src/file{i}.js"));
711 }
712 for i in 0..10 {
713 builder.add_name(&format!("var{i}"));
714 }
715
716 for line in 0..100u32 {
718 for col in 0..10u32 {
719 let src = (line * 10 + col) % 5;
720 let name = if col % 3 == 0 { Some(col % 10) } else { None };
721
722 match name {
723 Some(n) => builder.add_named_mapping(line, col * 10, src, line, col * 5, n),
724 None => builder.add_mapping(line, col * 10, src, line, col * 5),
725 }
726 }
727 }
728
729 let json = builder.to_json();
730 let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
731
732 assert_eq!(sm.mapping_count(), 1000);
733 assert_eq!(sm.line_count(), 100);
734
735 let loc = sm.original_position_for(50, 30).unwrap();
737 assert_eq!(loc.line, 50);
738 assert_eq!(loc.column, 15);
739 }
740
741 #[test]
742 fn json_escaping() {
743 let mut builder = SourceMapGenerator::new(None);
744 let src = builder.add_source("path/with\"quotes.js");
745 builder.set_source_content(src, "line1\nline2\ttab".to_string());
746 builder.add_mapping(0, 0, src, 0, 0);
747
748 let json = builder.to_json();
749 let _: serde_json::Value = serde_json::from_str(&json).unwrap();
751 }
752
753 #[test]
754 fn maybe_add_mapping_skips_redundant() {
755 let mut builder = SourceMapGenerator::new(None);
756 let src = builder.add_source("input.js");
757
758 assert!(builder.maybe_add_mapping(0, 0, src, 10, 0));
760 assert!(!builder.maybe_add_mapping(0, 5, src, 10, 0));
762 assert!(builder.maybe_add_mapping(0, 10, src, 11, 0));
764 assert!(builder.maybe_add_mapping(1, 0, src, 11, 0));
766
767 assert_eq!(builder.mapping_count(), 3);
768
769 let json = builder.to_json();
770 let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
771 assert_eq!(sm.mapping_count(), 3);
772 }
773
774 #[test]
775 fn maybe_add_mapping_different_source() {
776 let mut builder = SourceMapGenerator::new(None);
777 let a = builder.add_source("a.js");
778 let b = builder.add_source("b.js");
779
780 assert!(builder.maybe_add_mapping(0, 0, a, 0, 0));
781 assert!(builder.maybe_add_mapping(0, 5, b, 0, 0));
783
784 assert_eq!(builder.mapping_count(), 2);
785 }
786
787 #[test]
788 fn empty_lines_between_mappings() {
789 let mut builder = SourceMapGenerator::new(None);
790 let src = builder.add_source("input.js");
791 builder.add_mapping(0, 0, src, 0, 0);
792 builder.add_mapping(5, 0, src, 5, 0);
794
795 let json = builder.to_json();
796 let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
797
798 assert!(sm.original_position_for(0, 0).is_some());
800 assert!(sm.original_position_for(2, 0).is_none());
802 assert!(sm.original_position_for(5, 0).is_some());
804 }
805
806 #[test]
807 fn debug_id() {
808 let mut builder = SourceMapGenerator::new(None);
809 builder.set_debug_id("85314830-023f-4cf1-a267-535f4e37bb17".to_string());
810 let src = builder.add_source("input.js");
811 builder.add_mapping(0, 0, src, 0, 0);
812
813 let json = builder.to_json();
814 assert!(json.contains(r#""debugId":"85314830-023f-4cf1-a267-535f4e37bb17""#));
815
816 let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
817 assert_eq!(
818 sm.debug_id.as_deref(),
819 Some("85314830-023f-4cf1-a267-535f4e37bb17")
820 );
821 }
822
823 #[test]
824 fn scopes_roundtrip() {
825 use srcmap_scopes::{
826 Binding, CallSite, GeneratedRange, OriginalScope, Position, ScopeInfo,
827 };
828
829 let mut builder = SourceMapGenerator::new(Some("bundle.js".to_string()));
830 let src = builder.add_source("input.js");
831 builder.set_source_content(
832 src,
833 "function hello(name) {\n return name;\n}\nhello('world');".to_string(),
834 );
835 let name_hello = builder.add_name("hello");
836 builder.add_named_mapping(0, 0, src, 0, 0, name_hello);
837 builder.add_mapping(1, 0, src, 1, 0);
838
839 builder.set_scopes(ScopeInfo {
841 scopes: vec![Some(OriginalScope {
842 start: Position { line: 0, column: 0 },
843 end: Position {
844 line: 3,
845 column: 14,
846 },
847 name: None,
848 kind: Some("global".to_string()),
849 is_stack_frame: false,
850 variables: vec!["hello".to_string()],
851 children: vec![OriginalScope {
852 start: Position { line: 0, column: 9 },
853 end: Position { line: 2, column: 1 },
854 name: Some("hello".to_string()),
855 kind: Some("function".to_string()),
856 is_stack_frame: true,
857 variables: vec!["name".to_string()],
858 children: vec![],
859 }],
860 })],
861 ranges: vec![GeneratedRange {
862 start: Position { line: 0, column: 0 },
863 end: Position {
864 line: 3,
865 column: 14,
866 },
867 is_stack_frame: false,
868 is_hidden: false,
869 definition: Some(0),
870 call_site: None,
871 bindings: vec![Binding::Expression("hello".to_string())],
872 children: vec![GeneratedRange {
873 start: Position { line: 0, column: 9 },
874 end: Position { line: 2, column: 1 },
875 is_stack_frame: true,
876 is_hidden: false,
877 definition: Some(1),
878 call_site: None,
879 bindings: vec![Binding::Expression("name".to_string())],
880 children: vec![],
881 }],
882 }],
883 });
884
885 let json = builder.to_json();
886
887 assert!(json.contains(r#""scopes":"#));
889
890 let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
892 assert!(sm.scopes.is_some());
893
894 let scopes_info = sm.scopes.unwrap();
895
896 assert_eq!(scopes_info.scopes.len(), 1);
898 let root_scope = scopes_info.scopes[0].as_ref().unwrap();
899 assert_eq!(root_scope.kind.as_deref(), Some("global"));
900 assert_eq!(root_scope.variables, vec!["hello"]);
901 assert_eq!(root_scope.children.len(), 1);
902
903 let fn_scope = &root_scope.children[0];
904 assert_eq!(fn_scope.name.as_deref(), Some("hello"));
905 assert_eq!(fn_scope.kind.as_deref(), Some("function"));
906 assert!(fn_scope.is_stack_frame);
907 assert_eq!(fn_scope.variables, vec!["name"]);
908
909 assert_eq!(scopes_info.ranges.len(), 1);
911 let outer = &scopes_info.ranges[0];
912 assert_eq!(outer.definition, Some(0));
913 assert_eq!(
914 outer.bindings,
915 vec![Binding::Expression("hello".to_string())]
916 );
917 assert_eq!(outer.children.len(), 1);
918
919 let inner = &outer.children[0];
920 assert_eq!(inner.definition, Some(1));
921 assert!(inner.is_stack_frame);
922 assert_eq!(
923 inner.bindings,
924 vec![Binding::Expression("name".to_string())]
925 );
926 }
927
928 #[test]
929 fn scopes_with_inlining_roundtrip() {
930 use srcmap_scopes::{
931 Binding, CallSite, GeneratedRange, OriginalScope, Position, ScopeInfo,
932 };
933
934 let mut builder = SourceMapGenerator::new(None);
935 let src = builder.add_source("input.js");
936 builder.add_mapping(0, 0, src, 0, 0);
937
938 builder.set_scopes(ScopeInfo {
939 scopes: vec![Some(OriginalScope {
940 start: Position { line: 0, column: 0 },
941 end: Position {
942 line: 10,
943 column: 0,
944 },
945 name: None,
946 kind: None,
947 is_stack_frame: false,
948 variables: vec!["x".to_string()],
949 children: vec![OriginalScope {
950 start: Position { line: 1, column: 0 },
951 end: Position { line: 4, column: 1 },
952 name: Some("greet".to_string()),
953 kind: Some("function".to_string()),
954 is_stack_frame: true,
955 variables: vec!["msg".to_string()],
956 children: vec![],
957 }],
958 })],
959 ranges: vec![GeneratedRange {
960 start: Position { line: 0, column: 0 },
961 end: Position {
962 line: 10,
963 column: 0,
964 },
965 is_stack_frame: false,
966 is_hidden: false,
967 definition: Some(0),
968 call_site: None,
969 bindings: vec![Binding::Expression("_x".to_string())],
970 children: vec![GeneratedRange {
971 start: Position { line: 6, column: 0 },
972 end: Position { line: 8, column: 0 },
973 is_stack_frame: true,
974 is_hidden: false,
975 definition: Some(1),
976 call_site: Some(CallSite {
977 source_index: 0,
978 line: 8,
979 column: 0,
980 }),
981 bindings: vec![Binding::Expression("\"Hello\"".to_string())],
982 children: vec![],
983 }],
984 }],
985 });
986
987 let json = builder.to_json();
988 let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
989 let info = sm.scopes.unwrap();
990
991 let inlined = &info.ranges[0].children[0];
993 assert_eq!(
994 inlined.call_site,
995 Some(CallSite {
996 source_index: 0,
997 line: 8,
998 column: 0,
999 })
1000 );
1001 assert_eq!(
1002 inlined.bindings,
1003 vec![Binding::Expression("\"Hello\"".to_string())]
1004 );
1005 }
1006
1007 #[cfg(feature = "parallel")]
1008 mod parallel_tests {
1009 use super::*;
1010
1011 fn build_large_generator(lines: u32, cols_per_line: u32) -> SourceMapGenerator {
1012 let mut builder = SourceMapGenerator::new(Some("bundle.js".to_string()));
1013 for i in 0..10 {
1014 let src = builder.add_source(&format!("src/file{i}.js"));
1015 builder.set_source_content(
1016 src,
1017 format!("// source file {i}\n{}", "x = 1;\n".repeat(100)),
1018 );
1019 }
1020 for i in 0..20 {
1021 builder.add_name(&format!("var{i}"));
1022 }
1023
1024 for line in 0..lines {
1025 for col in 0..cols_per_line {
1026 let src = (line * cols_per_line + col) % 10;
1027 let name = if col % 3 == 0 {
1028 Some((col % 20) as u32)
1029 } else {
1030 None
1031 };
1032 match name {
1033 Some(n) => builder.add_named_mapping(line, col * 10, src, line, col * 5, n),
1034 None => builder.add_mapping(line, col * 10, src, line, col * 5),
1035 }
1036 }
1037 }
1038 builder
1039 }
1040
1041 #[test]
1042 fn parallel_large_roundtrip() {
1043 let builder = build_large_generator(500, 20);
1044 let json = builder.to_json();
1045 let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
1046 assert_eq!(sm.mapping_count(), 10000);
1047 assert_eq!(sm.line_count(), 500);
1048
1049 let loc = sm.original_position_for(250, 50).unwrap();
1051 assert_eq!(loc.line, 250);
1052 assert_eq!(loc.column, 25);
1053 }
1054
1055 #[test]
1056 fn parallel_matches_sequential() {
1057 let builder = build_large_generator(500, 20);
1058
1059 let mut sorted: Vec<&Mapping> = builder.mappings.iter().collect();
1061 sorted.sort_unstable_by(|a, b| {
1062 a.generated_line
1063 .cmp(&b.generated_line)
1064 .then(a.generated_column.cmp(&b.generated_column))
1065 });
1066
1067 let sequential = SourceMapGenerator::encode_sequential_impl(&sorted);
1068 let parallel = SourceMapGenerator::encode_parallel_impl(&sorted);
1069 assert_eq!(sequential, parallel);
1070 }
1071
1072 #[test]
1073 fn parallel_with_sparse_lines() {
1074 let mut builder = SourceMapGenerator::new(None);
1075 let src = builder.add_source("input.js");
1076
1077 for i in 0..50 {
1079 let line = i * 100;
1080 for col in 0..100u32 {
1081 builder.add_mapping(line, col * 10, src, line, col * 5);
1082 }
1083 }
1084
1085 let json = builder.to_json();
1086 let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
1087 assert_eq!(sm.mapping_count(), 5000);
1088
1089 assert!(sm.original_position_for(50, 0).is_none());
1091 let loc = sm.original_position_for(200, 50).unwrap();
1093 assert_eq!(loc.line, 200);
1094 assert_eq!(loc.column, 25);
1095 }
1096 }
1097}