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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
use crate::vlq;
use crate::serde::ser::{ Serialize, SerializeStruct, Serializer, };

use crate::lexer::span::{ Loc, Span, LineColumn, };

use std::io::{ Write, Cursor, };
use std::path::{ Path, PathBuf, };

// Source Map Revision 3 Proposal
//      https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#
//      http://www.ruanyifeng.com/blog/2013/01/javascript_source_map.html
// 
// Tail
// @ sourceMappingURL=/path/to/file.js.map
// 
// JSON Format:
// {
//     "version" : 3,
//     "file": "out.js",
//     "sourceRoot": "",
//     "sources": ["foo.js", "bar.js"],
//     "sourcesContent": [null, null],
//     "names": ["src", "maps", "are", "fun"],
//     "mappings": "A,AAAB;;ABCDE;"
// }
// 
// SourceMap example:
// http://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js
// http://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.map

// NOTE:
//      https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Errors/Deprecated_source_map_pragma
//      使用 `//# sourceMappingURL=http://example.com/path/to/your/sourcemap.map`
//      替代 `//@ sourceMappingURL=http://example.com/path/to/your/sourcemap.map`
// 

const VERSION: u8      = 3u8;
const COMMA: &[u8]     = b",";
const SEMICOLON: &[u8] = b";";


#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub struct Position {
    pub dst_column: usize,          // A
    pub src_file_index: usize,      // B
    pub src_line: usize,            // C
    pub src_column: usize,          // D
    pub ident_index: Option<usize>, // E
}


#[derive(Debug)]
pub struct SourceMap<'a> {
    file: PathBuf,
    source_root: Option<PathBuf>,
    sources: Vec<PathBuf>,
    sources_content: &'a [&'a str],
    names: &'a [&'a [char]],
    mappings: Cursor<Vec<u8>>,
}

impl<'a> SourceMap<'a> {
    

    pub fn new<T>(dst_filepath: T,
                  src_filepaths: Vec<PathBuf>,
                  src_contents: &'a [&'a str],
                  src_idents: &'a [&'a [char]],) -> Self
    where
        T: Into<PathBuf> 
    {
        Self {
            file: dst_filepath.into(),
            source_root: None,
            sources: src_filepaths,
            sources_content: src_contents,
            names: src_idents,
            mappings: Cursor::new(Vec::with_capacity(src_idents.len() * 5 )),
        }
    }

    pub fn add_line(&mut self) {
        self.mappings.write(SEMICOLON);
    }

    pub fn add_pos(&mut self, pos: Position) {
        let is_beginning = match self.mappings.get_ref().last() {
            Some(last) => last == &SEMICOLON[0],
            None => true,
        };

        if !is_beginning {
            self.mappings.write(COMMA);
        }
        
        vlq::encode(pos.dst_column as i64, &mut self.mappings).expect("Ooops ...");
        vlq::encode(pos.src_file_index as i64, &mut self.mappings).expect("Ooops ...");
        vlq::encode(pos.src_line as i64, &mut self.mappings).expect("Ooops ...");
        vlq::encode(pos.src_column as i64, &mut self.mappings).expect("Ooops ...");

        if let Some(ident_index) = pos.ident_index {
            vlq::encode(ident_index as i64, &mut self.mappings).expect("Ooops ...");
        }
    }

    pub fn tail(&self) -> String {
        format!("//@ sourceMappingURL=/path/to/file.js.map")
    }
}




impl<'a> Serialize for SourceMap<'a> {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut s = serializer.serialize_struct("", 1)?;
        
        s.serialize_field("version", &VERSION)?;
        s.serialize_field("file", &self.file)?;
        
        match self.source_root {
            Some(ref root) => s.serialize_field("sourceRoot", &root)?,
            _ => s.serialize_field("sourceRoot", "")?,
        }

        s.serialize_field("sources", &self.sources)?;
        s.serialize_field("sourcesContent", &self.sources_content)?;
            
        let names = self.names.iter().map(|name| name.iter().collect::<String>()).collect::<Vec<String>>();
        s.serialize_field("names", &names)?;

        let mappings = unsafe {
            String::from_utf8_unchecked(self.mappings.get_ref().to_owned())
        };

        s.serialize_field("mappings", &mappings )?;

        s.end()
    }
}



#[test]
fn test_serialize() {
    use crate::toolshed::{ Arena, };
    use crate::serde_json;

    use std::io::{ Write, Cursor, };
    use std::path::{Path, PathBuf};


    let arena = Arena::new();

    let file = "dist/main.js".into();
    let source_root = None;
    let sources = vec![];
    let sources_content = arena.alloc_vec(vec![ arena.alloc_str("这是一份源代码:)") ]);

    let names = arena.alloc_vec(vec![
        arena.alloc_vec("let".chars().collect::<Vec<char>>()),
    ]);
    let mappings = Cursor::new(Vec::new());

    let source_map = SourceMap { file, source_root, sources, sources_content, names, mappings };

    let res = serde_json::to_string(&source_map);
    assert_eq!(res.is_ok(), true);

    let res = res.unwrap();
    let json = r#"{"version":3,"file":"dist/main.js","sourceRoot":"","sources":[],"sourcesContent":["这是一份源代码:)"],"names":["let"],"mappings":""}"#;
    assert_eq!(res, json);
}