Skip to main content

swc_sourcemap/
detector.rs

1use std::{
2    io::{BufRead, BufReader, Read},
3    str,
4};
5
6use url::Url;
7
8use crate::{
9    decoder::{decode_data_url, strip_junk_header, StripHeaderReader},
10    errors::Result,
11    jsontypes::MinimalRawSourceMap,
12    types::DecodedMap,
13};
14
15/// Represents a reference to a sourcemap
16#[derive(PartialEq, Eq, Debug)]
17pub enum SourceMapRef {
18    /// A regular URL reference
19    Ref(String),
20    /// A legacy URL reference
21    LegacyRef(String),
22}
23
24fn resolve_url(ref_url: &str, minified_url: &Url) -> Option<Url> {
25    minified_url.join(ref_url).ok()
26}
27
28impl SourceMapRef {
29    /// Return the URL of the reference
30    pub fn get_url(&self) -> &str {
31        match *self {
32            SourceMapRef::Ref(ref u) => u.as_str(),
33            SourceMapRef::LegacyRef(ref u) => u.as_str(),
34        }
35    }
36
37    /// Resolves the reference.
38    ///
39    /// The given minified URL needs to be the URL of the minified file.  The
40    /// result is the fully resolved URL of where the source map can be located.
41    pub fn resolve(&self, minified_url: &str) -> Option<String> {
42        let url = self.get_url();
43        if url.starts_with("data:") {
44            return None;
45        }
46        resolve_url(url, &Url::parse(minified_url).ok()?).map(|x| x.to_string())
47    }
48
49    /// Resolves the reference against a local file path
50    ///
51    /// This is similar to `resolve` but operates on file paths.
52    #[cfg(any(unix, windows, target_os = "redox"))]
53    pub fn resolve_path(&self, minified_path: &std::path::Path) -> Option<std::path::PathBuf> {
54        let url = self.get_url();
55        if url.starts_with("data:") {
56            return None;
57        }
58        resolve_url(url, &Url::from_file_path(minified_path).ok()?)
59            .and_then(|x| x.to_file_path().ok())
60    }
61
62    /// Load an embedded sourcemap if there is a data URL.
63    pub fn get_embedded_sourcemap(&self) -> Result<Option<DecodedMap>> {
64        let url = self.get_url();
65        if url.starts_with("data:") {
66            Ok(Some(decode_data_url(url)?))
67        } else {
68            Ok(None)
69        }
70    }
71}
72
73/// Locates a sourcemap reference
74///
75/// Given a reader to a JavaScript file this tries to find the correct
76/// sourcemap reference comment and return it.
77pub fn locate_sourcemap_reference<R: Read>(rdr: R) -> Result<Option<SourceMapRef>> {
78    for line in BufReader::new(rdr).lines() {
79        let line = line?;
80        if line.starts_with("//# sourceMappingURL=") || line.starts_with("//@ sourceMappingURL=") {
81            let url = str::from_utf8(&line.as_bytes()[21..])?.trim().to_owned();
82            if line.starts_with("//@") {
83                return Ok(Some(SourceMapRef::LegacyRef(url)));
84            } else {
85                return Ok(Some(SourceMapRef::Ref(url)));
86            }
87        }
88    }
89    Ok(None)
90}
91
92/// Locates a sourcemap reference in a slice
93///
94/// This is an alternative to `locate_sourcemap_reference` that operates
95/// on slices.
96pub fn locate_sourcemap_reference_slice(slice: &[u8]) -> Result<Option<SourceMapRef>> {
97    locate_sourcemap_reference(slice)
98}
99
100fn is_sourcemap_common(rsm: MinimalRawSourceMap) -> bool {
101    (rsm.version.is_some() || rsm.file.is_some())
102        && ((rsm.sources.is_some()
103            || rsm.source_root.is_some()
104            || rsm.sources_content.is_some()
105            || rsm.names.is_some())
106            && rsm.mappings.is_some())
107        || rsm.sections.is_some()
108}
109
110fn is_sourcemap_impl<R: Read>(rdr: R) -> Result<bool> {
111    let mut rdr = StripHeaderReader::new(rdr);
112    let mut rdr = BufReader::new(&mut rdr);
113    let rsm: MinimalRawSourceMap = serde_json::from_reader(&mut rdr)?;
114    Ok(is_sourcemap_common(rsm))
115}
116
117fn is_sourcemap_slice_impl(slice: &[u8]) -> Result<bool> {
118    let content = strip_junk_header(slice)?;
119    let rsm: MinimalRawSourceMap = serde_json::from_slice(content)?;
120    Ok(is_sourcemap_common(rsm))
121}
122
123/// Checks if a valid sourcemap can be read from the given reader
124pub fn is_sourcemap<R: Read>(rdr: R) -> bool {
125    is_sourcemap_impl(rdr).unwrap_or(false)
126}
127
128/// Checks if the given byte slice contains a sourcemap
129pub fn is_sourcemap_slice(slice: &[u8]) -> bool {
130    is_sourcemap_slice_impl(slice).unwrap_or(false)
131}