Skip to main content

parcel_sourcemap/
utils.rs

1// Based on https://github.com/getsentry/rust-sourcemap/blob/master/src/utils.rs
2use std::borrow::Cow;
3use std::iter::repeat;
4
5pub fn is_abs_path(s: &str) -> bool {
6    if s.starts_with('/') || s.starts_with('\\') {
7        return true;
8    } else if s.len() > 3 {
9        let b = s.as_bytes();
10        if b[1] == b':'
11            && (b[2] == b'/' || b[2] == b'\\')
12            && ((b[0] >= b'a' && b[0] <= b'z') || (b[0] >= b'A' && b[0] <= b'Z'))
13        {
14            return true;
15        }
16    }
17
18    // fallback to false
19    false
20}
21
22fn get_common_prefix_len<'a>(items: &'a [Cow<'a, [&'a str]>]) -> usize {
23    if items.is_empty() {
24        return 0;
25    }
26
27    let shortest = &items[0];
28    let mut max_idx = None;
29    for seq in items.iter() {
30        let mut seq_max_idx = None;
31        for (idx, &comp) in shortest.iter().enumerate() {
32            if seq.get(idx) != Some(&comp) {
33                break;
34            }
35            seq_max_idx = Some(idx);
36        }
37        if max_idx.is_none() || seq_max_idx < max_idx {
38            max_idx = seq_max_idx;
39        }
40    }
41
42    if let Some(max_idx) = max_idx {
43        max_idx + 1
44    } else {
45        0
46    }
47}
48
49fn chunk_path(p: &str) -> Vec<&str> {
50    return p
51        .split(&['/', '\\'][..])
52        .filter(|x| !x.is_empty() && *x != ".")
53        .collect();
54}
55
56// Helper function to calculate the path from a base file to a target file.
57pub fn make_relative_path(base: &str, target: &str) -> String {
58    let mut target_str = target;
59    {
60        let target_lower = target.to_ascii_lowercase();
61        if target_lower.strip_prefix("file://").is_some() {
62            target_str = &target[7..];
63        }
64    }
65
66    if !is_abs_path(target_str) {
67        if target_str.contains(':') {
68            String::from(target_str)
69        } else {
70            return chunk_path(target_str).join("/");
71        }
72    } else {
73        let target_path: Vec<&str> = chunk_path(target_str);
74        let base_dir: Vec<&str> = chunk_path(base);
75        let items = vec![
76            Cow::Borrowed(base_dir.as_slice()),
77            Cow::Borrowed(target_path.as_slice()),
78        ];
79        let prefix_len = get_common_prefix_len(&items);
80        let mut rel_list: Vec<&str> = repeat("..").take(base_dir.len() - prefix_len).collect();
81        rel_list.extend_from_slice(&target_path[prefix_len..]);
82        rel_list.join("/")
83    }
84}
85
86#[test]
87fn test_make_relative_path() {
88    assert_eq!(
89        &make_relative_path("/foo/bar", "/foo/bar/baz.map"),
90        "baz.map"
91    );
92    assert_eq!(
93        &make_relative_path("/foo/bar/.", "/foo/bar/baz.map"),
94        "baz.map"
95    );
96    assert_eq!(
97        &make_relative_path("/foo/bar", "/foo/baz.map"),
98        "../baz.map"
99    );
100    assert_eq!(&make_relative_path("/some/abs/path", "foo.js"), "foo.js");
101    assert_eq!(
102        &make_relative_path("C:\\blah\\sub", "C:\\blah\\foo.js"),
103        "../foo.js"
104    );
105    assert_eq!(&make_relative_path("/", "./test.js"), "test.js");
106}