rusty_source_map/
util.rs

1#![allow(dead_code)]
2
3use crate::mapping::Mapping;
4use crate::util::UrlType::{Absolute, PathAbsolute, PathRelative, SchemeRelative};
5use lazy_static::lazy_static;
6use regex::Regex;
7use std::collections::LinkedList;
8use url::Url;
9
10pub fn strcmp(a: Option<String>, b: Option<String>) -> i32 {
11    if a == b {
12        return 0;
13    }
14
15    if a.is_none() {
16        return 1;
17    }
18
19    if b.is_none() {
20        return -1;
21    }
22
23    if a.unwrap() > b.unwrap() {
24        return 1;
25    }
26
27    -1
28}
29
30pub fn compare_by_generated_pos_inflated(a: &Mapping, b: &Mapping) -> i32 {
31    let mut cmp = a.generated.line - b.generated.line;
32    if cmp != 0 {
33        return cmp;
34    }
35
36    cmp = a.generated.column - b.generated.column;
37    if cmp != 0 {
38        return cmp;
39    }
40
41    cmp = strcmp(a.source.clone(), b.source.clone());
42    if cmp != 0 {
43        return cmp;
44    }
45    if a.original.is_some() && b.original.is_some() {
46        cmp = a.original.as_ref().unwrap().line - b.original.as_ref().unwrap().line;
47        if cmp != 0 {
48            return cmp;
49        }
50
51        cmp = a.original.as_ref().unwrap().column - b.original.as_ref().unwrap().column;
52        if cmp != 0 {
53            return cmp;
54        }
55    }
56
57    strcmp(a.name.clone(), b.name.clone())
58}
59
60// We use 'http' as the base here because we want URLs processed relative
61// to the safe base to be treated as "special" URLs during parsing using
62// the WHATWG URL parsing. This ensures that backslash normalization
63// applies to the path and such.
64const PROTOCOL: &str = "http:";
65const ABSOLUTE_SCHEME: &str = r"^[A-Za-z0-9\+\-\.]+:/";
66lazy_static! {
67    static ref ABSOLUTE_SCHEME_REGEXP: Regex = Regex::new(ABSOLUTE_SCHEME).unwrap();
68    static ref PROTOCOL_AND_HOST: String = format!("{}//host", PROTOCOL);
69}
70
71#[derive(Debug, Clone, PartialEq)]
72enum UrlType {
73    Absolute,
74    PathAbsolute,
75    PathRelative,
76    SchemeRelative,
77}
78
79fn get_url_type(input: &str) -> UrlType {
80    let mut iter = input.chars();
81    let first = iter.next().unwrap();
82    let second = iter.next().unwrap();
83    if first == '/' {
84        if second == '/' {
85            return SchemeRelative;
86        }
87        return PathAbsolute;
88    }
89    if ABSOLUTE_SCHEME_REGEXP.is_match(input) {
90        Absolute
91    } else {
92        PathRelative
93    }
94}
95
96fn build_unique_segment(prefix: &str, input: &str) -> String {
97    let mut id = 0;
98    loop {
99        let ident = format!("{}{}", prefix, id);
100        id += 1;
101        if !input.contains(ident.as_str()) {
102            return ident;
103        }
104    }
105}
106
107fn build_safe_base(input: &str) -> String {
108    let max_dot_parts = input.split("..").count() - 1;
109
110    // If we used a segment that also existed in `str`, then we would be unable
111    // to compute relative paths. For example, if `segment` were just "a":
112    //
113    //   const url = "../../a/"
114    //   const base = buildSafeBase(url); // http://host/a/a/
115    //   const joined = "http://host/a/";
116    //   const result = relative(base, joined);
117    //
118    // Expected: "../../a/";
119    // Actual: "a/"
120    //
121
122    let segment = build_unique_segment("p", input);
123
124    let mut base = format!("{}/", *PROTOCOL_AND_HOST);
125    for _ in 0..max_dot_parts {
126        base.push_str(&segment);
127        base.push('/');
128    }
129
130    base
131}
132
133fn compute_relative_url(root_url: &str, target_url: &str) -> String {
134    let root_url = Url::parse(root_url).unwrap();
135    let target_url = Url::parse(target_url).unwrap();
136
137    let mut target_parts = target_url.path().split('/').collect::<LinkedList<_>>();
138    let mut root_parts = root_url.path().split('/').collect::<LinkedList<_>>();
139
140    if !root_parts.is_empty() && root_parts.back().unwrap().is_empty() {
141        root_parts.pop_back();
142    }
143
144    while !target_parts.is_empty()
145        && !root_parts.is_empty()
146        && target_parts.front().unwrap() == root_parts.front().unwrap()
147    {
148        target_parts.pop_front();
149        root_parts.pop_front();
150    }
151
152    let mut relative_path: String = root_parts
153        .iter()
154        .map(|_| "..")
155        .chain(target_parts)
156        .collect::<Vec<_>>()
157        .join("/");
158
159    if let Some(query) = target_url.query() {
160        relative_path.push_str(query);
161    }
162
163    if let Some(frag) = target_url.fragment() {
164        relative_path.push_str(frag);
165    }
166    relative_path
167}
168
169fn create_safe_handler(cb: Box<dyn Fn(&mut Url) + Sync>) -> Box<dyn Fn(String) -> String + Sync> {
170    Box::new(move |input: String| -> String {
171        let t = get_url_type(input.as_str());
172        let base = build_safe_base(input.as_str());
173        let urlx = Url::parse(base.as_str()).unwrap();
174        let mut urlx = urlx.join(input.as_str()).unwrap();
175
176        cb(&mut urlx);
177
178        let result = urlx.to_string();
179
180        match t {
181            Absolute => result,
182            SchemeRelative => result.chars().skip(PROTOCOL.len()).collect(),
183            PathAbsolute => result.chars().skip(PROTOCOL_AND_HOST.len()).collect(),
184            _ => compute_relative_url(base.as_str(), result.as_str()),
185        }
186    })
187}
188
189type UtilityFn = Box<dyn Fn(String) -> String + Sync>;
190
191lazy_static! {
192    static ref ENSURE_DIRECTORY: UtilityFn = create_safe_handler(Box::new(|url| {
193        // replace(/\/?$/, "/");
194        let reg = Regex::new("/?$").unwrap();
195
196        let path = reg.replace(url.path(), "/").to_string();
197        url.set_path(&path);
198    }));
199
200    static ref TRIM_FILENAME: UtilityFn = create_safe_handler(Box::new(|url| {
201        let path = url.path().to_string();
202        let mut path = path.split('/').collect::<Vec<_>>();
203
204        if !path.last().unwrap().is_empty() {
205            path.pop();
206        }
207
208        url.set_path(&path.join(""));
209    }));
210
211    static ref NORMALIZE: UtilityFn = create_safe_handler(Box::new(|_url|{}));
212}
213
214fn replace_if_possible(root: &str, target: &str) -> Option<String> {
215    let url_type = get_url_type(root);
216    if url_type != get_url_type(target) {
217        return None;
218    }
219
220    let base = build_safe_base(&format!("{}{}", root, target));
221    let root_url = Url::parse(&base).unwrap();
222    let root_url = root_url.join(root).unwrap();
223    let target_url = Url::parse(&base).unwrap();
224    let target_url = target_url.join(target).unwrap();
225    match target_url.join("") {
226        Ok(_) => {}
227        Err(_) => return None,
228    };
229
230    if target_url.scheme() != root_url.scheme()
231        || target_url.username() != root_url.username()
232        || target_url.password() != root_url.password()
233        || target_url.host() != root_url.host()
234        || target_url.port() != root_url.port()
235    {
236        return None;
237    }
238
239    Some(compute_relative_url(root, target))
240}
241
242pub fn relative(root: String, target: String) -> String {
243    match replace_if_possible(root.as_str(), target.as_str()) {
244        None => NORMALIZE(target),
245        Some(r) => r,
246    }
247}
248
249fn with_base(url: &str, base: Option<&str>) -> String {
250    match base {
251        Some(base) => Url::parse(base)
252            .unwrap()
253            .join(url)
254            .unwrap()
255            .as_str()
256            .to_string(),
257        None => url.to_string(),
258    }
259}
260
261pub fn join(root: &str, path: &str) -> String {
262    let path_t = get_url_type(path);
263    let root_t = get_url_type(root);
264
265    let root = ENSURE_DIRECTORY(root.to_owned());
266
267    if path_t == Absolute {
268        return with_base(path, None);
269    }
270
271    if root_t == Absolute {
272        return with_base(path, Some(root.as_str()));
273    }
274
275    if path_t == SchemeRelative {
276        return NORMALIZE(path.to_string());
277    }
278
279    if root_t == SchemeRelative {
280        return with_base(
281            path,
282            Some(with_base(root.as_str(), Some(PROTOCOL_AND_HOST.as_str())).as_str()),
283        )
284        .chars()
285        .skip(PROTOCOL.len())
286        .collect();
287    }
288
289    if path_t == PathAbsolute {
290        return NORMALIZE(path.to_string());
291    }
292
293    if root_t == PathAbsolute {
294        return with_base(
295            path,
296            Some(with_base(root.as_str(), Some(PROTOCOL_AND_HOST.as_str())).as_str()),
297        )
298        .chars()
299        .skip(PROTOCOL_AND_HOST.len())
300        .collect();
301    }
302
303    let base = build_safe_base(format!("{}{}", path, root).as_str());
304    let new_path = with_base(
305        path,
306        Some(with_base(root.as_str(), Some(base.as_str())).as_str()),
307    );
308    compute_relative_url(base.as_str(), new_path.as_str())
309}
310
311pub fn compute_source_url(
312    source_root: Option<&str>,
313    source_url: &str,
314    source_map_url: Option<&str>,
315) -> String {
316    // The source map spec states that "sourceRoot" and "sources" entries are to be appended. While
317    // that is a little vague, implementations have generally interpreted that as joining the
318    // URLs with a `/` between then, assuming the "sourceRoot" doesn't already end with one.
319    // For example,
320    //
321    //   sourceRoot: "some-dir",
322    //   sources: ["/some-path.js"]
323    //
324    // and
325    //
326    //   sourceRoot: "some-dir/",
327    //   sources: ["/some-path.js"]
328    //
329    // must behave as "some-dir/some-path.js".
330    //
331    // With this library's the transition to a more URL-focused implementation, that behavior is
332    // preserved here. To acheive that, we trim the "/" from absolute-path when a sourceRoot value
333    // is present in order to make the sources entries behave as if they are relative to the
334    // "sourceRoot", as they would have if the two strings were simply concated.
335
336    let mut source_url = source_url;
337    let after = source_url.replacen("/", "", 1);
338
339    if source_root.is_some() && get_url_type(source_url) == PathAbsolute {
340        // sourceURL = sourceURL.replace(/^\//, "");
341        source_url = after.as_str();
342    }
343
344    let mut url = NORMALIZE(source_url.to_string());
345
346    if let Some(source_root) = source_root {
347        url = join(source_root, url.as_str());
348    }
349
350    if let Some(source_map_url) = source_map_url {
351        url = join(
352            TRIM_FILENAME(source_map_url.to_string()).as_str(),
353            url.as_str(),
354        );
355    }
356
357    url
358}