1use std::collections::HashMap;
28
29use srcmap_codec::vlq_encode;
30
31#[derive(Debug, Clone)]
35pub struct Mapping {
36 pub generated_line: u32,
37 pub generated_column: u32,
38 pub source: Option<u32>,
39 pub original_line: u32,
40 pub original_column: u32,
41 pub name: Option<u32>,
42}
43
44#[derive(Debug)]
46pub struct SourceMapGenerator {
47 file: Option<String>,
48 source_root: Option<String>,
49 sources: Vec<String>,
50 sources_content: Vec<Option<String>>,
51 names: Vec<String>,
52 mappings: Vec<Mapping>,
53 ignore_list: Vec<u32>,
54
55 source_map: HashMap<String, u32>,
57 name_map: HashMap<String, u32>,
58}
59
60impl SourceMapGenerator {
61 pub fn new(file: Option<String>) -> Self {
63 Self {
64 file,
65 source_root: None,
66 sources: Vec::new(),
67 sources_content: Vec::new(),
68 names: Vec::new(),
69 mappings: Vec::new(),
70 ignore_list: Vec::new(),
71 source_map: HashMap::new(),
72 name_map: HashMap::new(),
73 }
74 }
75
76 pub fn set_source_root(&mut self, root: String) {
78 self.source_root = Some(root);
79 }
80
81 pub fn add_source(&mut self, source: &str) -> u32 {
83 if let Some(&idx) = self.source_map.get(source) {
84 return idx;
85 }
86 let idx = self.sources.len() as u32;
87 self.sources.push(source.to_string());
88 self.sources_content.push(None);
89 self.source_map.insert(source.to_string(), idx);
90 idx
91 }
92
93 pub fn set_source_content(&mut self, source_idx: u32, content: String) {
95 if (source_idx as usize) < self.sources_content.len() {
96 self.sources_content[source_idx as usize] = Some(content);
97 }
98 }
99
100 pub fn add_name(&mut self, name: &str) -> u32 {
102 if let Some(&idx) = self.name_map.get(name) {
103 return idx;
104 }
105 let idx = self.names.len() as u32;
106 self.names.push(name.to_string());
107 self.name_map.insert(name.to_string(), idx);
108 idx
109 }
110
111 pub fn add_to_ignore_list(&mut self, source_idx: u32) {
113 if !self.ignore_list.contains(&source_idx) {
114 self.ignore_list.push(source_idx);
115 }
116 }
117
118 pub fn add_generated_mapping(&mut self, generated_line: u32, generated_column: u32) {
120 self.mappings.push(Mapping {
121 generated_line,
122 generated_column,
123 source: None,
124 original_line: 0,
125 original_column: 0,
126 name: None,
127 });
128 }
129
130 pub fn add_mapping(
132 &mut self,
133 generated_line: u32,
134 generated_column: u32,
135 source: u32,
136 original_line: u32,
137 original_column: u32,
138 ) {
139 self.mappings.push(Mapping {
140 generated_line,
141 generated_column,
142 source: Some(source),
143 original_line,
144 original_column,
145 name: None,
146 });
147 }
148
149 pub fn add_named_mapping(
151 &mut self,
152 generated_line: u32,
153 generated_column: u32,
154 source: u32,
155 original_line: u32,
156 original_column: u32,
157 name: u32,
158 ) {
159 self.mappings.push(Mapping {
160 generated_line,
161 generated_column,
162 source: Some(source),
163 original_line,
164 original_column,
165 name: Some(name),
166 });
167 }
168
169 pub fn maybe_add_mapping(
175 &mut self,
176 generated_line: u32,
177 generated_column: u32,
178 source: u32,
179 original_line: u32,
180 original_column: u32,
181 ) -> bool {
182 if let Some(last) = self.mappings.last()
183 && last.generated_line == generated_line
184 && last.source == Some(source)
185 && last.original_line == original_line
186 && last.original_column == original_column
187 {
188 return false;
189 }
190 self.add_mapping(
191 generated_line,
192 generated_column,
193 source,
194 original_line,
195 original_column,
196 );
197 true
198 }
199
200 fn encode_mappings(&self) -> String {
202 if self.mappings.is_empty() {
203 return String::new();
204 }
205
206 let mut sorted: Vec<&Mapping> = self.mappings.iter().collect();
208 sorted.sort_unstable_by(|a, b| {
209 a.generated_line
210 .cmp(&b.generated_line)
211 .then(a.generated_column.cmp(&b.generated_column))
212 });
213
214 #[cfg(feature = "parallel")]
215 if sorted.len() >= 4096 {
216 return Self::encode_parallel_impl(&sorted);
217 }
218
219 Self::encode_sequential_impl(&sorted)
220 }
221
222 fn encode_sequential_impl(sorted: &[&Mapping]) -> String {
223 let mut out: Vec<u8> = Vec::with_capacity(sorted.len() * 6);
224
225 let mut prev_gen_col: i64 = 0;
226 let mut prev_source: i64 = 0;
227 let mut prev_orig_line: i64 = 0;
228 let mut prev_orig_col: i64 = 0;
229 let mut prev_name: i64 = 0;
230 let mut prev_gen_line: u32 = 0;
231 let mut first_in_line = true;
232
233 for m in sorted {
234 while prev_gen_line < m.generated_line {
235 out.push(b';');
236 prev_gen_line += 1;
237 prev_gen_col = 0;
238 first_in_line = true;
239 }
240
241 if !first_in_line {
242 out.push(b',');
243 }
244 first_in_line = false;
245
246 vlq_encode(&mut out, m.generated_column as i64 - prev_gen_col);
247 prev_gen_col = m.generated_column as i64;
248
249 if let Some(source) = m.source {
250 vlq_encode(&mut out, source as i64 - prev_source);
251 prev_source = source as i64;
252
253 vlq_encode(&mut out, m.original_line as i64 - prev_orig_line);
254 prev_orig_line = m.original_line as i64;
255
256 vlq_encode(&mut out, m.original_column as i64 - prev_orig_col);
257 prev_orig_col = m.original_column as i64;
258
259 if let Some(name) = m.name {
260 vlq_encode(&mut out, name as i64 - prev_name);
261 prev_name = name as i64;
262 }
263 }
264 }
265
266 unsafe { String::from_utf8_unchecked(out) }
268 }
269
270 #[cfg(feature = "parallel")]
271 fn encode_parallel_impl(sorted: &[&Mapping]) -> String {
272 use rayon::prelude::*;
273
274 let max_line = sorted.last().unwrap().generated_line as usize;
275
276 let mut line_ranges: Vec<(usize, usize)> = vec![(0, 0); max_line + 1];
278 let mut i = 0;
279 while i < sorted.len() {
280 let line = sorted[i].generated_line as usize;
281 let start = i;
282 while i < sorted.len() && sorted[i].generated_line as usize == line {
283 i += 1;
284 }
285 line_ranges[line] = (start, i);
286 }
287
288 let mut states: Vec<(i64, i64, i64, i64)> = Vec::with_capacity(max_line + 1);
290 let mut prev_source: i64 = 0;
291 let mut prev_orig_line: i64 = 0;
292 let mut prev_orig_col: i64 = 0;
293 let mut prev_name: i64 = 0;
294
295 for &(start, end) in &line_ranges {
296 states.push((prev_source, prev_orig_line, prev_orig_col, prev_name));
297 for m in &sorted[start..end] {
298 if let Some(source) = m.source {
299 prev_source = source as i64;
300 prev_orig_line = m.original_line as i64;
301 prev_orig_col = m.original_column as i64;
302 if let Some(name) = m.name {
303 prev_name = name as i64;
304 }
305 }
306 }
307 }
308
309 let encoded_lines: Vec<Vec<u8>> = line_ranges
311 .par_iter()
312 .zip(states.par_iter())
313 .map(|(&(start, end), &(s, ol, oc, n))| {
314 if start == end {
315 return Vec::new();
316 }
317 encode_mapping_slice(&sorted[start..end], s, ol, oc, n)
318 })
319 .collect();
320
321 let total_len =
323 encoded_lines.iter().map(|l| l.len()).sum::<usize>() + max_line;
324 let mut out: Vec<u8> = Vec::with_capacity(total_len);
325 for (i, bytes) in encoded_lines.iter().enumerate() {
326 if i > 0 {
327 out.push(b';');
328 }
329 out.extend_from_slice(bytes);
330 }
331
332 unsafe { String::from_utf8_unchecked(out) }
334 }
335
336 pub fn to_json(&self) -> String {
338 let mappings = self.encode_mappings();
339
340 let mut json = String::with_capacity(256 + mappings.len());
341 json.push_str(r#"{"version":3"#);
342
343 if let Some(ref file) = self.file {
344 json.push_str(r#","file":"#);
345 json.push_str(&json_quote(file));
346 }
347
348 if let Some(ref root) = self.source_root {
349 json.push_str(r#","sourceRoot":"#);
350 json.push_str(&json_quote(root));
351 }
352
353 json.push_str(r#","sources":["#);
355 for (i, s) in self.sources.iter().enumerate() {
356 if i > 0 {
357 json.push(',');
358 }
359 json.push_str(&json_quote(s));
360 }
361 json.push(']');
362
363 if self.sources_content.iter().any(|c| c.is_some()) {
365 json.push_str(r#","sourcesContent":["#);
366
367 #[cfg(feature = "parallel")]
368 {
369 use rayon::prelude::*;
370
371 let total_content: usize = self
372 .sources_content
373 .iter()
374 .map(|c| c.as_ref().map_or(0, |s| s.len()))
375 .sum();
376
377 if self.sources_content.len() >= 8 && total_content >= 8192 {
378 let quoted: Vec<String> = self
379 .sources_content
380 .par_iter()
381 .map(|c| match c {
382 Some(content) => json_quote(content),
383 None => "null".to_string(),
384 })
385 .collect();
386 for (i, q) in quoted.iter().enumerate() {
387 if i > 0 {
388 json.push(',');
389 }
390 json.push_str(q);
391 }
392 } else {
393 for (i, c) in self.sources_content.iter().enumerate() {
394 if i > 0 {
395 json.push(',');
396 }
397 match c {
398 Some(content) => json.push_str(&json_quote(content)),
399 None => json.push_str("null"),
400 }
401 }
402 }
403 }
404
405 #[cfg(not(feature = "parallel"))]
406 for (i, c) in self.sources_content.iter().enumerate() {
407 if i > 0 {
408 json.push(',');
409 }
410 match c {
411 Some(content) => json.push_str(&json_quote(content)),
412 None => json.push_str("null"),
413 }
414 }
415
416 json.push(']');
417 }
418
419 json.push_str(r#","names":["#);
421 for (i, n) in self.names.iter().enumerate() {
422 if i > 0 {
423 json.push(',');
424 }
425 json.push_str(&json_quote(n));
426 }
427 json.push(']');
428
429 json.push_str(r#","mappings":"#);
431 json.push_str(&json_quote(&mappings));
432
433 if !self.ignore_list.is_empty() {
435 json.push_str(r#","ignoreList":["#);
436 for (i, &idx) in self.ignore_list.iter().enumerate() {
437 if i > 0 {
438 json.push(',');
439 }
440 json.push_str(&idx.to_string());
441 }
442 json.push(']');
443 }
444
445 json.push('}');
446 json
447 }
448
449 pub fn mapping_count(&self) -> usize {
451 self.mappings.len()
452 }
453}
454
455#[cfg(feature = "parallel")]
460fn encode_mapping_slice(
461 mappings: &[&Mapping],
462 init_source: i64,
463 init_orig_line: i64,
464 init_orig_col: i64,
465 init_name: i64,
466) -> Vec<u8> {
467 let mut buf = Vec::with_capacity(mappings.len() * 6);
468 let mut prev_gen_col: i64 = 0;
469 let mut prev_source = init_source;
470 let mut prev_orig_line = init_orig_line;
471 let mut prev_orig_col = init_orig_col;
472 let mut prev_name = init_name;
473 let mut first = true;
474
475 for m in mappings {
476 if !first {
477 buf.push(b',');
478 }
479 first = false;
480
481 vlq_encode(&mut buf, m.generated_column as i64 - prev_gen_col);
482 prev_gen_col = m.generated_column as i64;
483
484 if let Some(source) = m.source {
485 vlq_encode(&mut buf, source as i64 - prev_source);
486 prev_source = source as i64;
487
488 vlq_encode(&mut buf, m.original_line as i64 - prev_orig_line);
489 prev_orig_line = m.original_line as i64;
490
491 vlq_encode(&mut buf, m.original_column as i64 - prev_orig_col);
492 prev_orig_col = m.original_column as i64;
493
494 if let Some(name) = m.name {
495 vlq_encode(&mut buf, name as i64 - prev_name);
496 prev_name = name as i64;
497 }
498 }
499 }
500
501 buf
502}
503
504fn json_quote(s: &str) -> String {
506 let mut out = String::with_capacity(s.len() + 2);
507 out.push('"');
508 for c in s.chars() {
509 match c {
510 '"' => out.push_str("\\\""),
511 '\\' => out.push_str("\\\\"),
512 '\n' => out.push_str("\\n"),
513 '\r' => out.push_str("\\r"),
514 '\t' => out.push_str("\\t"),
515 c if c < '\x20' => {
516 out.push_str(&format!("\\u{:04x}", c as u32));
517 }
518 c => out.push(c),
519 }
520 }
521 out.push('"');
522 out
523}
524
525#[cfg(test)]
528mod tests {
529 use super::*;
530
531 #[test]
532 fn empty_generator() {
533 let builder = SourceMapGenerator::new(None);
534 let json = builder.to_json();
535 assert!(json.contains(r#""version":3"#));
536 assert!(json.contains(r#""mappings":"""#));
537 }
538
539 #[test]
540 fn simple_mapping() {
541 let mut builder = SourceMapGenerator::new(Some("output.js".to_string()));
542 let src = builder.add_source("input.js");
543 builder.add_mapping(0, 0, src, 0, 0);
544
545 let json = builder.to_json();
546 assert!(json.contains(r#""file":"output.js""#));
547 assert!(json.contains(r#""sources":["input.js"]"#));
548
549 let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
551 let loc = sm.original_position_for(0, 0).unwrap();
552 assert_eq!(sm.source(loc.source), "input.js");
553 assert_eq!(loc.line, 0);
554 assert_eq!(loc.column, 0);
555 }
556
557 #[test]
558 fn mapping_with_name() {
559 let mut builder = SourceMapGenerator::new(None);
560 let src = builder.add_source("input.js");
561 let name = builder.add_name("myFunction");
562 builder.add_named_mapping(0, 0, src, 0, 0, name);
563
564 let json = builder.to_json();
565 assert!(json.contains(r#""names":["myFunction"]"#));
566
567 let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
568 let loc = sm.original_position_for(0, 0).unwrap();
569 assert_eq!(loc.name, Some(0));
570 assert_eq!(sm.name(0), "myFunction");
571 }
572
573 #[test]
574 fn multiple_lines() {
575 let mut builder = SourceMapGenerator::new(None);
576 let src = builder.add_source("input.js");
577 builder.add_mapping(0, 0, src, 0, 0);
578 builder.add_mapping(1, 4, src, 1, 2);
579 builder.add_mapping(2, 0, src, 2, 0);
580
581 let json = builder.to_json();
582 let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
583 assert_eq!(sm.line_count(), 3);
584
585 let loc = sm.original_position_for(1, 4).unwrap();
586 assert_eq!(loc.line, 1);
587 assert_eq!(loc.column, 2);
588 }
589
590 #[test]
591 fn multiple_sources() {
592 let mut builder = SourceMapGenerator::new(None);
593 let a = builder.add_source("a.js");
594 let b = builder.add_source("b.js");
595 builder.add_mapping(0, 0, a, 0, 0);
596 builder.add_mapping(1, 0, b, 0, 0);
597
598 let json = builder.to_json();
599 let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
600
601 let loc0 = sm.original_position_for(0, 0).unwrap();
602 let loc1 = sm.original_position_for(1, 0).unwrap();
603 assert_eq!(sm.source(loc0.source), "a.js");
604 assert_eq!(sm.source(loc1.source), "b.js");
605 }
606
607 #[test]
608 fn source_content() {
609 let mut builder = SourceMapGenerator::new(None);
610 let src = builder.add_source("input.js");
611 builder.set_source_content(src, "var x = 1;".to_string());
612 builder.add_mapping(0, 0, src, 0, 0);
613
614 let json = builder.to_json();
615 assert!(json.contains(r#""sourcesContent":["var x = 1;"]"#));
616
617 let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
618 assert_eq!(sm.sources_content[0], Some("var x = 1;".to_string()));
619 }
620
621 #[test]
622 fn source_root() {
623 let mut builder = SourceMapGenerator::new(None);
624 builder.set_source_root("src/".to_string());
625 let src = builder.add_source("input.js");
626 builder.add_mapping(0, 0, src, 0, 0);
627
628 let json = builder.to_json();
629 assert!(json.contains(r#""sourceRoot":"src/""#));
630 }
631
632 #[test]
633 fn ignore_list() {
634 let mut builder = SourceMapGenerator::new(None);
635 let _app = builder.add_source("app.js");
636 let lib = builder.add_source("node_modules/lib.js");
637 builder.add_to_ignore_list(lib);
638 builder.add_mapping(0, 0, lib, 0, 0);
639
640 let json = builder.to_json();
641 assert!(json.contains(r#""ignoreList":[1]"#));
642 }
643
644 #[test]
645 fn generated_only_mapping() {
646 let mut builder = SourceMapGenerator::new(None);
647 builder.add_generated_mapping(0, 0);
648
649 let json = builder.to_json();
650 let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
651 assert!(sm.original_position_for(0, 0).is_none());
653 }
654
655 #[test]
656 fn dedup_sources_and_names() {
657 let mut builder = SourceMapGenerator::new(None);
658 let s1 = builder.add_source("input.js");
659 let s2 = builder.add_source("input.js"); assert_eq!(s1, s2);
661
662 let n1 = builder.add_name("foo");
663 let n2 = builder.add_name("foo"); assert_eq!(n1, n2);
665
666 assert_eq!(builder.sources.len(), 1);
667 assert_eq!(builder.names.len(), 1);
668 }
669
670 #[test]
671 fn large_roundtrip() {
672 let mut builder = SourceMapGenerator::new(Some("bundle.js".to_string()));
673
674 for i in 0..5 {
675 builder.add_source(&format!("src/file{i}.js"));
676 }
677 for i in 0..10 {
678 builder.add_name(&format!("var{i}"));
679 }
680
681 for line in 0..100u32 {
683 for col in 0..10u32 {
684 let src = (line as u32 * 10 + col) % 5;
685 let name = if col % 3 == 0 {
686 Some((col % 10) as u32)
687 } else {
688 None
689 };
690
691 match name {
692 Some(n) => builder.add_named_mapping(line, col * 10, src, line, col * 5, n),
693 None => builder.add_mapping(line, col * 10, src, line, col * 5),
694 }
695 }
696 }
697
698 let json = builder.to_json();
699 let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
700
701 assert_eq!(sm.mapping_count(), 1000);
702 assert_eq!(sm.line_count(), 100);
703
704 let loc = sm.original_position_for(50, 30).unwrap();
706 assert_eq!(loc.line, 50);
707 assert_eq!(loc.column, 15);
708 }
709
710 #[test]
711 fn json_escaping() {
712 let mut builder = SourceMapGenerator::new(None);
713 let src = builder.add_source("path/with\"quotes.js");
714 builder.set_source_content(src, "line1\nline2\ttab".to_string());
715 builder.add_mapping(0, 0, src, 0, 0);
716
717 let json = builder.to_json();
718 let _: serde_json::Value = serde_json::from_str(&json).unwrap();
720 }
721
722 #[test]
723 fn maybe_add_mapping_skips_redundant() {
724 let mut builder = SourceMapGenerator::new(None);
725 let src = builder.add_source("input.js");
726
727 assert!(builder.maybe_add_mapping(0, 0, src, 10, 0));
729 assert!(!builder.maybe_add_mapping(0, 5, src, 10, 0));
731 assert!(builder.maybe_add_mapping(0, 10, src, 11, 0));
733 assert!(builder.maybe_add_mapping(1, 0, src, 11, 0));
735
736 assert_eq!(builder.mapping_count(), 3);
737
738 let json = builder.to_json();
739 let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
740 assert_eq!(sm.mapping_count(), 3);
741 }
742
743 #[test]
744 fn maybe_add_mapping_different_source() {
745 let mut builder = SourceMapGenerator::new(None);
746 let a = builder.add_source("a.js");
747 let b = builder.add_source("b.js");
748
749 assert!(builder.maybe_add_mapping(0, 0, a, 0, 0));
750 assert!(builder.maybe_add_mapping(0, 5, b, 0, 0));
752
753 assert_eq!(builder.mapping_count(), 2);
754 }
755
756 #[test]
757 fn empty_lines_between_mappings() {
758 let mut builder = SourceMapGenerator::new(None);
759 let src = builder.add_source("input.js");
760 builder.add_mapping(0, 0, src, 0, 0);
761 builder.add_mapping(5, 0, src, 5, 0);
763
764 let json = builder.to_json();
765 let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
766
767 assert!(sm.original_position_for(0, 0).is_some());
769 assert!(sm.original_position_for(2, 0).is_none());
771 assert!(sm.original_position_for(5, 0).is_some());
773 }
774
775 #[cfg(feature = "parallel")]
776 mod parallel_tests {
777 use super::*;
778
779 fn build_large_generator(lines: u32, cols_per_line: u32) -> SourceMapGenerator {
780 let mut builder =
781 SourceMapGenerator::new(Some("bundle.js".to_string()));
782 for i in 0..10 {
783 let src = builder.add_source(&format!("src/file{i}.js"));
784 builder.set_source_content(
785 src,
786 format!("// source file {i}\n{}", "x = 1;\n".repeat(100)),
787 );
788 }
789 for i in 0..20 {
790 builder.add_name(&format!("var{i}"));
791 }
792
793 for line in 0..lines {
794 for col in 0..cols_per_line {
795 let src = (line * cols_per_line + col) % 10;
796 let name = if col % 3 == 0 {
797 Some((col % 20) as u32)
798 } else {
799 None
800 };
801 match name {
802 Some(n) => builder.add_named_mapping(
803 line,
804 col * 10,
805 src,
806 line,
807 col * 5,
808 n,
809 ),
810 None => builder.add_mapping(
811 line,
812 col * 10,
813 src,
814 line,
815 col * 5,
816 ),
817 }
818 }
819 }
820 builder
821 }
822
823 #[test]
824 fn parallel_large_roundtrip() {
825 let builder = build_large_generator(500, 20);
826 let json = builder.to_json();
827 let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
828 assert_eq!(sm.mapping_count(), 10000);
829 assert_eq!(sm.line_count(), 500);
830
831 let loc = sm.original_position_for(250, 50).unwrap();
833 assert_eq!(loc.line, 250);
834 assert_eq!(loc.column, 25);
835 }
836
837 #[test]
838 fn parallel_matches_sequential() {
839 let builder = build_large_generator(500, 20);
840
841 let mut sorted: Vec<&Mapping> = builder.mappings.iter().collect();
843 sorted.sort_unstable_by(|a, b| {
844 a.generated_line
845 .cmp(&b.generated_line)
846 .then(a.generated_column.cmp(&b.generated_column))
847 });
848
849 let sequential = SourceMapGenerator::encode_sequential_impl(&sorted);
850 let parallel = SourceMapGenerator::encode_parallel_impl(&sorted);
851 assert_eq!(sequential, parallel);
852 }
853
854 #[test]
855 fn parallel_with_sparse_lines() {
856 let mut builder = SourceMapGenerator::new(None);
857 let src = builder.add_source("input.js");
858
859 for i in 0..50 {
861 let line = i * 100;
862 for col in 0..100u32 {
863 builder.add_mapping(line, col * 10, src, line, col * 5);
864 }
865 }
866
867 let json = builder.to_json();
868 let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
869 assert_eq!(sm.mapping_count(), 5000);
870
871 assert!(sm.original_position_for(50, 0).is_none());
873 let loc = sm.original_position_for(200, 50).unwrap();
875 assert_eq!(loc.line, 200);
876 assert_eq!(loc.column, 25);
877 }
878 }
879}