1use std::path::{Path, PathBuf};
4
5use crate::{GeneratedLocation, Mapping, OriginalLocation, ParseError, SourceMap};
6
7pub fn find_common_prefix<'a>(paths: impl Iterator<Item = &'a str>) -> Option<String> {
15 let abs_paths: Vec<&str> = paths.filter(|p| p.starts_with('/')).collect();
16 if abs_paths.len() < 2 {
17 return None;
18 }
19
20 let first_all: Vec<&str> = abs_paths[0].split('/').collect();
22 let first_dir = &first_all[..first_all.len().saturating_sub(1)];
23 let mut common_len = first_dir.len();
24
25 for path in &abs_paths[1..] {
26 let components: Vec<&str> = path.split('/').collect();
27 let dir = &components[..components.len().saturating_sub(1)];
28 let mut match_len = 0;
29 for (a, b) in first_dir.iter().zip(dir.iter()) {
30 if a != b {
31 break;
32 }
33 match_len += 1;
34 }
35 common_len = common_len.min(match_len);
36 }
37
38 if common_len < 2 {
40 return None;
41 }
42
43 let prefix = first_dir[..common_len].join("/");
44 if prefix.is_empty() || prefix == "/" {
45 return None;
46 }
47
48 Some(format!("{prefix}/"))
49}
50
51pub fn make_relative_path(base: &str, target: &str) -> String {
63 if base == target {
64 return ".".to_string();
65 }
66
67 let base_parts: Vec<&str> = base.split('/').collect();
68 let target_parts: Vec<&str> = target.split('/').collect();
69
70 let base_dir = &base_parts[..base_parts.len().saturating_sub(1)];
72 let target_dir = &target_parts[..target_parts.len().saturating_sub(1)];
73 let target_file = target_parts.last().unwrap_or(&"");
74
75 let mut common = 0;
77 for (a, b) in base_dir.iter().zip(target_dir.iter()) {
78 if a != b {
79 break;
80 }
81 common += 1;
82 }
83
84 let ups = base_dir.len() - common;
85 let mut result = String::new();
86
87 for _ in 0..ups {
88 result.push_str("../");
89 }
90
91 for part in &target_dir[common..] {
92 result.push_str(part);
93 result.push('/');
94 }
95
96 result.push_str(target_file);
97
98 if result.is_empty() { ".".to_string() } else { result }
99}
100
101pub fn is_sourcemap(json: &str) -> bool {
110 let Ok(val) = serde_json::from_str::<serde_json::Value>(json) else {
111 return false;
112 };
113
114 let Some(obj) = val.as_object() else {
115 return false;
116 };
117
118 if obj.contains_key("sections") {
120 return true;
121 }
122
123 let has_version = obj.contains_key("version");
125 let has_mappings = obj.contains_key("mappings");
126 let has_source_field = obj.contains_key("sources")
127 || obj.contains_key("names")
128 || obj.contains_key("sourceRoot")
129 || obj.contains_key("sourcesContent");
130
131 has_version && has_mappings && has_source_field
132}
133
134pub fn resolve_source_map_url(minified_url: &str, source_map_ref: &str) -> Option<String> {
152 if source_map_ref.starts_with("data:") {
154 return None;
155 }
156
157 if source_map_ref.starts_with("http://")
159 || source_map_ref.starts_with("https://")
160 || source_map_ref.starts_with('/')
161 {
162 return Some(source_map_ref.to_string());
163 }
164
165 if let Some(last_slash) = minified_url.rfind('/') {
167 let base = &minified_url[..=last_slash];
168 let combined = format!("{base}{source_map_ref}");
169 Some(normalize_path_components(&combined))
170 } else {
171 Some(source_map_ref.to_string())
173 }
174}
175
176pub fn resolve_source_map_path(minified_path: &Path, source_map_ref: &str) -> Option<PathBuf> {
181 let parent = minified_path.parent()?;
182 let joined = parent.join(source_map_ref);
183
184 Some(normalize_pathbuf(&joined))
186}
187
188fn normalize_path_components(url: &str) -> String {
190 let (prefix, path) = if let Some(idx) = url.find("://") {
192 let after_proto = &url[idx + 3..];
193 if let Some(slash_idx) = after_proto.find('/') {
194 let split_at = idx + 3 + slash_idx;
195 (&url[..split_at], &url[split_at..])
196 } else {
197 return url.to_string();
198 }
199 } else {
200 ("", url)
201 };
202
203 let mut segments: Vec<&str> = Vec::new();
204 for segment in path.split('/') {
205 match segment {
206 ".." => {
207 if segments.len() > 1 {
209 segments.pop();
210 }
211 }
212 "." | "" if !segments.is_empty() => {
213 }
215 _ => {
216 segments.push(segment);
217 }
218 }
219 }
220
221 let normalized = segments.join("/");
222 format!("{prefix}{normalized}")
223}
224
225fn normalize_pathbuf(path: &Path) -> PathBuf {
227 let mut components = Vec::new();
228 for component in path.components() {
229 match component {
230 std::path::Component::ParentDir => {
231 components.pop();
232 }
233 std::path::Component::CurDir => {}
234 _ => {
235 components.push(component);
236 }
237 }
238 }
239 components.iter().collect()
240}
241
242const BASE64_CHARS: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
245
246pub fn to_data_url(json: &str) -> String {
258 let encoded = base64_encode(json.as_bytes());
259 format!("data:application/json;base64,{encoded}")
260}
261
262fn base64_encode(input: &[u8]) -> String {
264 let mut result = String::with_capacity(input.len().div_ceil(3) * 4);
265 let chunks = input.chunks(3);
266
267 for chunk in chunks {
268 let b0 = chunk[0] as u32;
269 let b1 = if chunk.len() > 1 { chunk[1] as u32 } else { 0 };
270 let b2 = if chunk.len() > 2 { chunk[2] as u32 } else { 0 };
271
272 let triple = (b0 << 16) | (b1 << 8) | b2;
273
274 result.push(BASE64_CHARS[((triple >> 18) & 0x3F) as usize] as char);
275 result.push(BASE64_CHARS[((triple >> 12) & 0x3F) as usize] as char);
276
277 if chunk.len() > 1 {
278 result.push(BASE64_CHARS[((triple >> 6) & 0x3F) as usize] as char);
279 } else {
280 result.push('=');
281 }
282
283 if chunk.len() > 2 {
284 result.push(BASE64_CHARS[(triple & 0x3F) as usize] as char);
285 } else {
286 result.push('=');
287 }
288 }
289
290 result
291}
292
293pub struct RewriteOptions<'a> {
297 pub with_names: bool,
299 pub with_source_contents: bool,
301 pub strip_prefixes: &'a [&'a str],
304}
305
306impl Default for RewriteOptions<'_> {
307 fn default() -> Self {
308 Self { with_names: true, with_source_contents: true, strip_prefixes: &[] }
309 }
310}
311
312pub fn rewrite_sources(sm: &SourceMap, options: &RewriteOptions<'_>) -> SourceMap {
322 let auto_prefix = if options.strip_prefixes.contains(&"~") {
324 find_common_prefix(sm.sources.iter().map(|s| s.as_str()))
325 } else {
326 None
327 };
328
329 let explicit_prefixes: Vec<&str> =
330 options.strip_prefixes.iter().filter(|&&p| p != "~").copied().collect();
331
332 let sources: Vec<String> = sm
334 .sources
335 .iter()
336 .map(|s| {
337 let mut result = s.as_str();
338
339 if let Some(ref prefix) = auto_prefix
341 && let Some(stripped) = result.strip_prefix(prefix.as_str())
342 {
343 result = stripped;
344 }
345
346 for prefix in &explicit_prefixes {
348 if let Some(stripped) = result.strip_prefix(prefix) {
349 result = stripped;
350 break;
351 }
352 }
353
354 result.to_string()
355 })
356 .collect();
357
358 let sources_content = if options.with_source_contents {
360 sm.sources_content.clone()
361 } else {
362 vec![None; sm.sources_content.len()]
363 };
364
365 let (names, mappings) = if options.with_names {
367 (sm.names.clone(), sm.all_mappings().to_vec())
368 } else {
369 let cleared_mappings: Vec<Mapping> =
370 sm.all_mappings().iter().map(|m| Mapping { name: u32::MAX, ..*m }).collect();
371 (Vec::new(), cleared_mappings)
372 };
373
374 let mut result = SourceMap::from_parts(
375 sm.file.clone(),
376 sm.source_root.clone(),
377 sources,
378 sources_content,
379 names,
380 mappings,
381 sm.ignore_list.clone(),
382 sm.debug_id.clone(),
383 sm.scopes.clone(),
384 );
385
386 result.extensions = sm.extensions.clone();
388
389 result
390}
391
392pub enum DecodedMap {
400 Regular(SourceMap),
402}
403
404impl DecodedMap {
405 pub fn from_json(json: &str) -> Result<Self, ParseError> {
407 let sm = SourceMap::from_json(json)?;
408 Ok(Self::Regular(sm))
409 }
410
411 pub fn original_position_for(&self, line: u32, column: u32) -> Option<OriginalLocation> {
413 match self {
414 DecodedMap::Regular(sm) => sm.original_position_for(line, column),
415 }
416 }
417
418 pub fn generated_position_for(
420 &self,
421 source: &str,
422 line: u32,
423 column: u32,
424 ) -> Option<GeneratedLocation> {
425 match self {
426 DecodedMap::Regular(sm) => sm.generated_position_for(source, line, column),
427 }
428 }
429
430 pub fn sources(&self) -> &[String] {
432 match self {
433 DecodedMap::Regular(sm) => &sm.sources,
434 }
435 }
436
437 pub fn names(&self) -> &[String] {
439 match self {
440 DecodedMap::Regular(sm) => &sm.names,
441 }
442 }
443
444 pub fn source(&self, idx: u32) -> &str {
450 match self {
451 DecodedMap::Regular(sm) => sm.source(idx),
452 }
453 }
454
455 pub fn name(&self, idx: u32) -> &str {
461 match self {
462 DecodedMap::Regular(sm) => sm.name(idx),
463 }
464 }
465
466 pub fn debug_id(&self) -> Option<&str> {
468 match self {
469 DecodedMap::Regular(sm) => sm.debug_id.as_deref(),
470 }
471 }
472
473 pub fn set_debug_id(&mut self, id: impl Into<String>) {
475 match self {
476 DecodedMap::Regular(sm) => sm.debug_id = Some(id.into()),
477 }
478 }
479
480 pub fn to_json(&self) -> String {
482 match self {
483 DecodedMap::Regular(sm) => sm.to_json(),
484 }
485 }
486
487 pub fn into_source_map(self) -> Option<SourceMap> {
489 match self {
490 DecodedMap::Regular(sm) => Some(sm),
491 }
492 }
493}
494
495#[cfg(test)]
498mod tests {
499 use super::*;
500
501 #[test]
504 fn common_prefix_basic() {
505 let paths = vec!["/a/b/c/file1.js", "/a/b/c/file2.js", "/a/b/c/file3.js"];
506 let result = find_common_prefix(paths.into_iter());
507 assert_eq!(result, Some("/a/b/c/".to_string()));
508 }
509
510 #[test]
511 fn common_prefix_different_depths() {
512 let paths = vec!["/a/b/c/file1.js", "/a/b/d/file2.js"];
513 let result = find_common_prefix(paths.into_iter());
514 assert_eq!(result, Some("/a/b/".to_string()));
515 }
516
517 #[test]
518 fn common_prefix_only_root() {
519 let paths = vec!["/a/file1.js", "/b/file2.js"];
520 let result = find_common_prefix(paths.into_iter());
521 assert_eq!(result, None);
523 }
524
525 #[test]
526 fn common_prefix_single_path() {
527 let paths = vec!["/a/b/c.js"];
528 let result = find_common_prefix(paths.into_iter());
529 assert_eq!(result, None);
530 }
531
532 #[test]
533 fn common_prefix_no_absolute_paths() {
534 let paths = vec!["a/b/c.js", "a/b/d.js"];
535 let result = find_common_prefix(paths.into_iter());
536 assert_eq!(result, None);
537 }
538
539 #[test]
540 fn common_prefix_mixed_absolute_relative() {
541 let paths = vec!["/a/b/c.js", "a/b/d.js", "/a/b/e.js"];
542 let result = find_common_prefix(paths.into_iter());
543 assert_eq!(result, Some("/a/b/".to_string()));
545 }
546
547 #[test]
548 fn common_prefix_empty_iterator() {
549 let paths: Vec<&str> = vec![];
550 let result = find_common_prefix(paths.into_iter());
551 assert_eq!(result, None);
552 }
553
554 #[test]
555 fn common_prefix_identical_paths() {
556 let paths = vec!["/a/b/c.js", "/a/b/c.js"];
557 let result = find_common_prefix(paths.into_iter());
558 assert_eq!(result, Some("/a/b/".to_string()));
559 }
560
561 #[test]
564 fn relative_path_sibling_dirs() {
565 assert_eq!(make_relative_path("/a/b/c.js", "/a/d/e.js"), "../d/e.js");
566 }
567
568 #[test]
569 fn relative_path_same_dir() {
570 assert_eq!(make_relative_path("/a/b/c.js", "/a/b/d.js"), "d.js");
571 }
572
573 #[test]
574 fn relative_path_same_file() {
575 assert_eq!(make_relative_path("/a/b/c.js", "/a/b/c.js"), ".");
576 }
577
578 #[test]
579 fn relative_path_deeper_target() {
580 assert_eq!(make_relative_path("/a/b/c.js", "/a/b/d/e/f.js"), "d/e/f.js");
581 }
582
583 #[test]
584 fn relative_path_multiple_ups() {
585 assert_eq!(make_relative_path("/a/b/c/d.js", "/a/e.js"), "../../e.js");
586 }
587
588 #[test]
589 fn relative_path_completely_different() {
590 assert_eq!(make_relative_path("/a/b/c.js", "/x/y/z.js"), "../../x/y/z.js");
591 }
592
593 #[test]
596 fn is_sourcemap_regular() {
597 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
598 assert!(is_sourcemap(json));
599 }
600
601 #[test]
602 fn is_sourcemap_indexed() {
603 let json = r#"{"version":3,"sections":[{"offset":{"line":0,"column":0},"map":{"version":3,"sources":[],"names":[],"mappings":""}}]}"#;
604 assert!(is_sourcemap(json));
605 }
606
607 #[test]
608 fn is_sourcemap_with_source_root() {
609 let json = r#"{"version":3,"sourceRoot":"/src/","mappings":"AAAA"}"#;
610 assert!(is_sourcemap(json));
611 }
612
613 #[test]
614 fn is_sourcemap_with_sources_content() {
615 let json = r#"{"version":3,"sourcesContent":["var x;"],"mappings":"AAAA"}"#;
616 assert!(is_sourcemap(json));
617 }
618
619 #[test]
620 fn is_sourcemap_invalid_json() {
621 assert!(!is_sourcemap("not json"));
622 }
623
624 #[test]
625 fn is_sourcemap_missing_version() {
626 let json = r#"{"sources":["a.js"],"mappings":"AAAA"}"#;
627 assert!(!is_sourcemap(json));
628 }
629
630 #[test]
631 fn is_sourcemap_missing_mappings() {
632 let json = r#"{"version":3,"sources":["a.js"]}"#;
633 assert!(!is_sourcemap(json));
634 }
635
636 #[test]
637 fn is_sourcemap_empty_object() {
638 assert!(!is_sourcemap("{}"));
639 }
640
641 #[test]
642 fn is_sourcemap_array() {
643 assert!(!is_sourcemap("[]"));
644 }
645
646 #[test]
649 fn resolve_url_relative() {
650 let result = resolve_source_map_url("https://example.com/js/app.js", "app.js.map");
651 assert_eq!(result, Some("https://example.com/js/app.js.map".to_string()));
652 }
653
654 #[test]
655 fn resolve_url_parent_traversal() {
656 let result = resolve_source_map_url("https://example.com/js/app.js", "../maps/app.js.map");
657 assert_eq!(result, Some("https://example.com/maps/app.js.map".to_string()));
658 }
659
660 #[test]
661 fn resolve_url_absolute_http() {
662 let result = resolve_source_map_url(
663 "https://example.com/js/app.js",
664 "https://cdn.example.com/maps/app.js.map",
665 );
666 assert_eq!(result, Some("https://cdn.example.com/maps/app.js.map".to_string()));
667 }
668
669 #[test]
670 fn resolve_url_absolute_slash() {
671 let result = resolve_source_map_url("https://example.com/js/app.js", "/maps/app.js.map");
672 assert_eq!(result, Some("/maps/app.js.map".to_string()));
673 }
674
675 #[test]
676 fn resolve_url_data_url() {
677 let result = resolve_source_map_url(
678 "https://example.com/js/app.js",
679 "data:application/json;base64,abc",
680 );
681 assert_eq!(result, None);
682 }
683
684 #[test]
685 fn resolve_url_filesystem_path() {
686 let result = resolve_source_map_url("/js/app.js", "app.js.map");
687 assert_eq!(result, Some("/js/app.js.map".to_string()));
688 }
689
690 #[test]
691 fn resolve_url_no_directory() {
692 let result = resolve_source_map_url("app.js", "app.js.map");
693 assert_eq!(result, Some("app.js.map".to_string()));
694 }
695
696 #[test]
697 fn resolve_url_excessive_traversal() {
698 let result =
700 resolve_source_map_url("https://example.com/js/app.js", "../../../maps/app.js.map");
701 assert_eq!(result, Some("https://example.com/maps/app.js.map".to_string()));
702 }
703
704 #[test]
707 fn resolve_path_simple() {
708 let result = resolve_source_map_path(Path::new("/js/app.js"), "app.js.map");
709 assert_eq!(result, Some(PathBuf::from("/js/app.js.map")));
710 }
711
712 #[test]
713 fn resolve_path_parent_traversal() {
714 let result = resolve_source_map_path(Path::new("/js/app.js"), "../maps/app.js.map");
715 assert_eq!(result, Some(PathBuf::from("/maps/app.js.map")));
716 }
717
718 #[test]
719 fn resolve_path_subdirectory() {
720 let result = resolve_source_map_path(Path::new("/src/app.js"), "maps/app.js.map");
721 assert_eq!(result, Some(PathBuf::from("/src/maps/app.js.map")));
722 }
723
724 #[test]
727 fn data_url_prefix() {
728 let url = to_data_url(r#"{"version":3}"#);
729 assert!(url.starts_with("data:application/json;base64,"));
730 }
731
732 #[test]
733 fn data_url_roundtrip() {
734 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
735 let url = to_data_url(json);
736 let encoded = url.strip_prefix("data:application/json;base64,").unwrap();
737 let decoded = base64_decode(encoded);
738 assert_eq!(decoded, json);
739 }
740
741 #[test]
742 fn data_url_empty_json() {
743 let url = to_data_url("{}");
744 let encoded = url.strip_prefix("data:application/json;base64,").unwrap();
745 let decoded = base64_decode(encoded);
746 assert_eq!(decoded, "{}");
747 }
748
749 #[test]
750 fn base64_encode_padding_1() {
751 let encoded = base64_encode(b"A");
753 assert_eq!(encoded, "QQ==");
754 }
755
756 #[test]
757 fn base64_encode_padding_2() {
758 let encoded = base64_encode(b"AB");
760 assert_eq!(encoded, "QUI=");
761 }
762
763 #[test]
764 fn base64_encode_no_padding() {
765 let encoded = base64_encode(b"ABC");
767 assert_eq!(encoded, "QUJD");
768 }
769
770 #[test]
771 fn base64_encode_empty() {
772 assert_eq!(base64_encode(b""), "");
773 }
774
775 fn base64_decode(input: &str) -> String {
777 let mut lookup = [0u8; 128];
778 for (i, &c) in BASE64_CHARS.iter().enumerate() {
779 lookup[c as usize] = i as u8;
780 }
781
782 let bytes: Vec<u8> = input.bytes().filter(|&b| b != b'=').collect();
783 let mut result = Vec::with_capacity(bytes.len() * 3 / 4);
784
785 for chunk in bytes.chunks(4) {
786 let vals: Vec<u8> = chunk.iter().map(|&b| lookup[b as usize]).collect();
787 if vals.len() >= 2 {
788 result.push((vals[0] << 2) | (vals[1] >> 4));
789 }
790 if vals.len() >= 3 {
791 result.push((vals[1] << 4) | (vals[2] >> 2));
792 }
793 if vals.len() >= 4 {
794 result.push((vals[2] << 6) | vals[3]);
795 }
796 }
797
798 String::from_utf8(result).unwrap()
799 }
800
801 #[test]
804 fn rewrite_options_default() {
805 let opts = RewriteOptions::default();
806 assert!(opts.with_names);
807 assert!(opts.with_source_contents);
808 assert!(opts.strip_prefixes.is_empty());
809 }
810
811 fn make_test_sourcemap() -> SourceMap {
812 let json = r#"{
813 "version": 3,
814 "sources": ["/src/app/main.js", "/src/app/utils.js"],
815 "names": ["foo", "bar"],
816 "mappings": "AACA,SCCA",
817 "sourcesContent": ["var foo;", "var bar;"]
818 }"#;
819 SourceMap::from_json(json).unwrap()
820 }
821
822 #[test]
823 fn rewrite_strip_explicit_prefix() {
824 let sm = make_test_sourcemap();
825 let opts = RewriteOptions { strip_prefixes: &["/src/app/"], ..Default::default() };
826 let rewritten = rewrite_sources(&sm, &opts);
827 assert_eq!(rewritten.sources, vec!["main.js", "utils.js"]);
828 }
829
830 #[test]
831 fn rewrite_strip_auto_prefix() {
832 let sm = make_test_sourcemap();
833 let opts = RewriteOptions { strip_prefixes: &["~"], ..Default::default() };
834 let rewritten = rewrite_sources(&sm, &opts);
835 assert_eq!(rewritten.sources, vec!["main.js", "utils.js"]);
836 }
837
838 #[test]
839 fn rewrite_without_names() {
840 let sm = make_test_sourcemap();
841 let opts = RewriteOptions { with_names: false, ..Default::default() };
842 let rewritten = rewrite_sources(&sm, &opts);
843 for m in rewritten.all_mappings() {
845 assert_eq!(m.name, u32::MAX);
846 }
847 }
848
849 #[test]
850 fn rewrite_without_sources_content() {
851 let sm = make_test_sourcemap();
852 let opts = RewriteOptions { with_source_contents: false, ..Default::default() };
853 let rewritten = rewrite_sources(&sm, &opts);
854 for content in &rewritten.sources_content {
855 assert!(content.is_none());
856 }
857 }
858
859 #[test]
860 fn rewrite_preserves_mappings() {
861 let sm = make_test_sourcemap();
862 let opts = RewriteOptions::default();
863 let rewritten = rewrite_sources(&sm, &opts);
864 assert_eq!(rewritten.all_mappings().len(), sm.all_mappings().len());
865 let loc = rewritten.original_position_for(0, 0);
867 assert!(loc.is_some());
868 }
869
870 #[test]
871 fn rewrite_preserves_debug_id() {
872 let json = r#"{
873 "version": 3,
874 "sources": ["a.js"],
875 "names": [],
876 "mappings": "AAAA",
877 "debugId": "test-id-123"
878 }"#;
879 let sm = SourceMap::from_json(json).unwrap();
880 let opts = RewriteOptions::default();
881 let rewritten = rewrite_sources(&sm, &opts);
882 assert_eq!(rewritten.debug_id.as_deref(), Some("test-id-123"));
883 }
884
885 #[test]
886 fn rewrite_preserves_extensions() {
887 let json = r#"{
888 "version": 3,
889 "sources": ["a.js"],
890 "names": [],
891 "mappings": "AAAA",
892 "x_facebook_sources": [[{"names": ["<global>"], "mappings": "AAA"}]]
893 }"#;
894 let sm = SourceMap::from_json(json).unwrap();
895 assert!(sm.extensions.contains_key("x_facebook_sources"));
896
897 let opts = RewriteOptions::default();
898 let rewritten = rewrite_sources(&sm, &opts);
899 assert!(rewritten.extensions.contains_key("x_facebook_sources"));
900 assert_eq!(sm.extensions["x_facebook_sources"], rewritten.extensions["x_facebook_sources"]);
901 }
902
903 #[test]
904 fn rewrite_without_names_clears_names_vec() {
905 let sm = make_test_sourcemap();
906 let opts = RewriteOptions { with_names: false, ..Default::default() };
907 let rewritten = rewrite_sources(&sm, &opts);
908 assert!(rewritten.names.is_empty());
909 }
910
911 #[test]
912 fn rewrite_strip_no_match() {
913 let sm = make_test_sourcemap();
914 let opts = RewriteOptions { strip_prefixes: &["/other/"], ..Default::default() };
915 let rewritten = rewrite_sources(&sm, &opts);
916 assert_eq!(rewritten.sources, sm.sources);
917 }
918
919 #[test]
922 fn decoded_map_from_json() {
923 let json = r#"{"version":3,"sources":["a.js"],"names":["foo"],"mappings":"AACAA"}"#;
924 let dm = DecodedMap::from_json(json).unwrap();
925 assert_eq!(dm.sources(), &["a.js"]);
926 assert_eq!(dm.names(), &["foo"]);
927 }
928
929 #[test]
930 fn decoded_map_original_position() {
931 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
932 let dm = DecodedMap::from_json(json).unwrap();
933 let loc = dm.original_position_for(0, 0).unwrap();
934 assert_eq!(dm.source(loc.source), "a.js");
935 assert_eq!(loc.line, 0);
936 assert_eq!(loc.column, 0);
937 }
938
939 #[test]
940 fn decoded_map_generated_position() {
941 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
942 let dm = DecodedMap::from_json(json).unwrap();
943 let pos = dm.generated_position_for("a.js", 0, 0).unwrap();
944 assert_eq!(pos.line, 0);
945 assert_eq!(pos.column, 0);
946 }
947
948 #[test]
949 fn decoded_map_source_and_name() {
950 let json =
951 r#"{"version":3,"sources":["a.js","b.js"],"names":["x","y"],"mappings":"AACAA,GCCA"}"#;
952 let dm = DecodedMap::from_json(json).unwrap();
953 assert_eq!(dm.source(0), "a.js");
954 assert_eq!(dm.source(1), "b.js");
955 assert_eq!(dm.name(0), "x");
956 assert_eq!(dm.name(1), "y");
957 }
958
959 #[test]
960 fn decoded_map_debug_id() {
961 let json = r#"{"version":3,"sources":[],"names":[],"mappings":"","debugId":"abc-123"}"#;
962 let dm = DecodedMap::from_json(json).unwrap();
963 assert_eq!(dm.debug_id(), Some("abc-123"));
964 }
965
966 #[test]
967 fn decoded_map_set_debug_id() {
968 let json = r#"{"version":3,"sources":[],"names":[],"mappings":""}"#;
969 let mut dm = DecodedMap::from_json(json).unwrap();
970 assert_eq!(dm.debug_id(), None);
971 dm.set_debug_id("new-id");
972 assert_eq!(dm.debug_id(), Some("new-id"));
973 }
974
975 #[test]
976 fn decoded_map_to_json() {
977 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
978 let dm = DecodedMap::from_json(json).unwrap();
979 let output = dm.to_json();
980 assert!(output.contains("\"version\":3"));
982 assert!(output.contains("\"sources\":[\"a.js\"]"));
983 }
984
985 #[test]
986 fn decoded_map_into_source_map() {
987 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
988 let dm = DecodedMap::from_json(json).unwrap();
989 let sm = dm.into_source_map().unwrap();
990 assert_eq!(sm.sources, vec!["a.js"]);
991 }
992
993 #[test]
994 fn decoded_map_invalid_json() {
995 let result = DecodedMap::from_json("not json");
996 assert!(result.is_err());
997 }
998
999 #[test]
1000 fn decoded_map_roundtrip() {
1001 let json = r#"{"version":3,"sources":["a.js"],"names":["foo"],"mappings":"AACAA","sourcesContent":["var foo;"]}"#;
1002 let dm = DecodedMap::from_json(json).unwrap();
1003 let output = dm.to_json();
1004 let dm2 = DecodedMap::from_json(&output).unwrap();
1005 assert_eq!(dm2.sources(), &["a.js"]);
1006 assert_eq!(dm2.names(), &["foo"]);
1007 }
1008
1009 #[test]
1012 fn data_url_with_is_sourcemap() {
1013 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
1014 assert!(is_sourcemap(json));
1015 let url = to_data_url(json);
1016 assert!(url.starts_with("data:application/json;base64,"));
1017 }
1018
1019 #[test]
1020 fn rewrite_then_serialize() {
1021 let sm = make_test_sourcemap();
1022 let opts = RewriteOptions {
1023 strip_prefixes: &["~"],
1024 with_source_contents: false,
1025 ..Default::default()
1026 };
1027 let rewritten = rewrite_sources(&sm, &opts);
1028 let json = rewritten.to_json();
1029 assert!(is_sourcemap(&json));
1030
1031 let parsed = SourceMap::from_json(&json).unwrap();
1033 assert_eq!(parsed.sources, vec!["main.js", "utils.js"]);
1034 }
1035
1036 #[test]
1037 fn decoded_map_rewrite_roundtrip() {
1038 let json = r#"{"version":3,"sources":["/src/a.js","/src/b.js"],"names":["x"],"mappings":"AACAA,GCAA","sourcesContent":["var x;","var y;"]}"#;
1039 let dm = DecodedMap::from_json(json).unwrap();
1040 let sm = dm.into_source_map().unwrap();
1041
1042 let opts = RewriteOptions {
1043 strip_prefixes: &["~"],
1044 with_source_contents: true,
1045 ..Default::default()
1046 };
1047 let rewritten = rewrite_sources(&sm, &opts);
1048 assert_eq!(rewritten.sources, vec!["a.js", "b.js"]);
1049
1050 let dm2 = DecodedMap::Regular(rewritten);
1051 let output = dm2.to_json();
1052 assert!(is_sourcemap(&output));
1053 }
1054}