symbolic_debuginfo/
js.rs

1//! Utilities specifically for working with JavaScript specific debug info.
2//!
3//! This for the most part only contains utility functions to parse references
4//! out of minified JavaScript files and source maps.  For actually working
5//! with source maps this module is insufficient.
6
7use debugid::DebugId;
8use serde::Deserialize;
9
10/// Parses a sourceMappingURL comment in a file to discover a sourcemap reference.
11///
12/// Any query string or fragments the URL might contain will be stripped away.
13pub fn discover_sourcemaps_location(contents: &str) -> Option<&str> {
14    for line in contents.lines().rev() {
15        if line.starts_with("//# sourceMappingURL=") || line.starts_with("//@ sourceMappingURL=") {
16            let url = line[21..].trim();
17
18            // The URL might contain a query string or fragment. Strip those away before recording the URL.
19            let without_query = url.split_once('?').map(|x| x.0).unwrap_or(url);
20            let without_fragment = without_query
21                .split_once('#')
22                .map(|x| x.0)
23                .unwrap_or(without_query);
24
25            return Some(without_fragment);
26        }
27    }
28    None
29}
30
31/// Quickly reads the embedded `debug_id` key from a source map.
32///
33/// Both `debugId` and `debug_id` are supported as field names. If both
34/// are set, the latter takes precedence.
35pub fn discover_sourcemap_embedded_debug_id(contents: &str) -> Option<DebugId> {
36    // Deserialize from `"debugId"` or `"debug_id"`,
37    // preferring the latter.
38    #[derive(Deserialize)]
39    struct DebugIdInSourceMap {
40        #[serde(rename = "debugId")]
41        debug_id_new: Option<DebugId>,
42        #[serde(rename = "debug_id")]
43        debug_id_old: Option<DebugId>,
44    }
45
46    serde_json::from_str(contents)
47        .ok()
48        .and_then(|x: DebugIdInSourceMap| x.debug_id_old.or(x.debug_id_new))
49}
50
51/// Parses a `debugId` comment in a file to discover a sourcemap's debug ID.
52pub fn discover_debug_id(contents: &str) -> Option<DebugId> {
53    for line in contents.lines().rev() {
54        if let Some(rest) = line.strip_prefix("//# debugId=") {
55            return rest.trim().parse().ok();
56        }
57    }
58    None
59}
60
61#[cfg(test)]
62mod tests {
63    use debugid::DebugId;
64
65    use crate::js::discover_sourcemap_embedded_debug_id;
66
67    #[test]
68    fn test_debugid_snake_case() {
69        let input = r#"{
70         "version":3,
71         "sources":["coolstuff.js"],
72         "names":["x","alert"],
73         "mappings":"AAAA,GAAIA,GAAI,EACR,IAAIA,GAAK,EAAG,CACVC,MAAM",
74         "debug_id":"00000000-0000-0000-0000-000000000000"
75     }"#;
76
77        assert_eq!(
78            discover_sourcemap_embedded_debug_id(input),
79            Some(DebugId::default())
80        );
81    }
82
83    #[test]
84    fn test_debugid_camel_case() {
85        let input = r#"{
86         "version":3,
87         "sources":["coolstuff.js"],
88         "names":["x","alert"],
89         "mappings":"AAAA,GAAIA,GAAI,EACR,IAAIA,GAAK,EAAG,CACVC,MAAM",
90         "debugId":"00000000-0000-0000-0000-000000000000"
91     }"#;
92
93        assert_eq!(
94            discover_sourcemap_embedded_debug_id(input),
95            Some(DebugId::default())
96        );
97    }
98
99    #[test]
100    fn test_debugid_both() {
101        let input = r#"{
102         "version":3,
103         "sources":["coolstuff.js"],
104         "names":["x","alert"],
105         "mappings":"AAAA,GAAIA,GAAI,EACR,IAAIA,GAAK,EAAG,CACVC,MAAM",
106         "debug_id":"00000000-0000-0000-0000-000000000000",
107         "debugId":"11111111-1111-1111-1111-111111111111"
108     }"#;
109
110        assert_eq!(
111            discover_sourcemap_embedded_debug_id(input),
112            Some(DebugId::default())
113        );
114    }
115}