1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
use crate::SourceMapError;
use relative_path::PathExt;
use sourcemap::SourceMapBuilder;
use std::fs;
use std::path::{Path, PathBuf};

const LINK_HEADER: &str = "//# sourceMappingURL=";

pub struct SourceMap {
    pub src_path: PathBuf,
    pub dst_path: PathBuf,
    pub map_path: PathBuf,
    pub src_path_from_map: String,
    pub map_path_from_dst: String,
    builder: SourceMapBuilder,
    source_map: Option<sourcemap::SourceMap>,
}

impl SourceMap {
    pub fn new(src_path: &Path, dst_path: &Path, map_path: &Path) -> Self {
        let src_path = src_path.to_path_buf();
        let dst_path = dst_path.to_path_buf();
        let map_path = map_path.to_path_buf();
        let src_path_from_map = if let Ok(x) = src_path.relative_to(map_path.parent().unwrap()) {
            x.as_str().to_owned()
        } else {
            src_path.to_string_lossy().to_string()
        };
        let map_path_from_dst = if let Ok(x) = map_path.relative_to(dst_path.parent().unwrap()) {
            x.as_str().to_owned()
        } else {
            map_path.to_string_lossy().to_string()
        };
        let builder = SourceMapBuilder::new(Some(&map_path.file_name().unwrap().to_string_lossy()));
        Self {
            src_path,
            dst_path,
            map_path,
            src_path_from_map,
            map_path_from_dst,
            builder,
            source_map: None,
        }
    }

    pub fn from_src(src_path: &Path) -> Result<Self, SourceMapError> {
        let src = fs::read_to_string(src_path)?;

        if let Some(line) = src.lines().last() {
            if line.starts_with(LINK_HEADER) {
                let map_path = line.strip_prefix(LINK_HEADER).unwrap();
                let map_path = src_path.parent().unwrap().join(map_path);
                let text = fs::read(&map_path)?;

                let src_path = src_path.to_path_buf();
                let dst_path = PathBuf::new();
                let src_path_from_map = String::new();
                let map_path_from_dst = String::new();
                let builder =
                    SourceMapBuilder::new(Some(&map_path.file_name().unwrap().to_string_lossy()));
                let source_map = Some(sourcemap::SourceMap::from_reader(text.as_slice())?);

                return Ok(Self {
                    src_path,
                    dst_path,
                    map_path,
                    src_path_from_map,
                    map_path_from_dst,
                    builder,
                    source_map,
                });
            }
        }

        Err(SourceMapError::NotFound)
    }

    pub fn add(
        &mut self,
        dst_line: u32,
        dst_column: u32,
        src_line: u32,
        src_column: u32,
        name: &str,
    ) {
        // Line and column of sourcemap crate is 0-based
        let dst_line = dst_line - 1;
        let dst_column = dst_column - 1;
        let src_line = src_line - 1;
        let src_column = src_column - 1;

        self.builder.add(
            dst_line,
            dst_column,
            src_line,
            src_column,
            Some(&self.src_path_from_map),
            Some(name),
            false,
        );
    }

    pub fn set_source_content(&mut self, content: &str) {
        let id = self.builder.add_source(&self.src_path_from_map);
        self.builder.set_source_contents(id, Some(content));
    }

    pub fn build(&mut self) {
        let mut builder =
            SourceMapBuilder::new(Some(&self.map_path.file_name().unwrap().to_string_lossy()));
        std::mem::swap(&mut builder, &mut self.builder);
        self.source_map = Some(builder.into_sourcemap());
    }

    pub fn get_link(&self) -> String {
        format!("{}{}", LINK_HEADER, self.map_path_from_dst)
    }

    pub fn to_bytes(&self) -> Result<Vec<u8>, SourceMapError> {
        if let Some(ref x) = self.source_map {
            let mut ret = Vec::new();
            x.to_writer(&mut ret)?;
            Ok(ret)
        } else {
            Err(SourceMapError::NotFound)
        }
    }

    pub fn lookup(&self, line: u32, column: u32) -> Option<(PathBuf, u32, u32)> {
        if let Some(ref x) = self.source_map {
            if let Some(token) = x.lookup_token(line - 1, column - 1) {
                if let Some(path) = token.get_source() {
                    let path = self.map_path.parent().unwrap().join(path);
                    if let Ok(path) = fs::canonicalize(path) {
                        let line = token.get_src_line() + 1;
                        let column = token.get_src_col() + 1;
                        Some((path, line, column))
                    } else {
                        None
                    }
                } else {
                    None
                }
            } else {
                None
            }
        } else {
            None
        }
    }
}