1 | /**
|
2 | * Specifications for the Source-Map version 3 can be found
|
3 | * [here](https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit?hl=en_US&pli=1&pli=1).
|
4 | *
|
5 | * Here are some example of numbers encoded in VLQ64.
|
6 | * 13 -> 1101
|
7 | * -> positive, we add a trailing 0
|
8 | * -> 11010 = 26
|
9 | * -> "a"
|
10 | * -6 -> -110
|
11 | * -> negative, we add a trailing 1
|
12 | * -> 1101 = 13
|
13 | * -> "N"
|
14 | * 1974 -> 11110110110
|
15 | * -> positive, we add a trailing 0
|
16 | * -> 111101101100
|
17 | * -> too big for Base64, cut it in chunks of 5
|
18 | * -> 00011 11011 01100
|
19 | * -> reverse order
|
20 | * -> 01100 11011 00011
|
21 | * -> add continuation bits (1 for all except the last)
|
22 | * -> 101100 111011 000011
|
23 | * -> 44 59 3
|
24 | * -> "s7D"
|
25 | *
|
26 | * The attribute `mappings`of a source map is a big string is a
|
27 | * concatenation of the lines of the generated code, separated by a `;`.
|
28 | * Note that for minified generated code, it is usual to find only one
|
29 | * line, hence you will not find any `;` at all in `mapping`. Each line
|
30 | * is made of __segments__ separated with a `,`.
|
31 | * A __segment__ is a
|
32 | * [VLQ](https://en.wikipedia.org/wiki/Variable-length_quantity)
|
33 | * encoding of (at most) five integers :
|
34 | * * column in the generated file,
|
35 | * * index of the original file (see `sources` and `sourcesContent` in
|
36 | the source-map),
|
37 | * * line number in the original file,
|
38 | * * column in the original file,
|
39 | * * index of the original name (see `names` in the source-map)
|
40 | *
|
41 | */
|
42 |
|
43 |
|
44 | // Base64 alphabet taken from
|
45 | // [wikipedia](https://en.wikipedia.org/wiki/Base64#Variants_summary_table)
|
46 | var BASE64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
47 |
|
48 |
|
49 | /**
|
50 | * Read a Base64 encoded line with `;` and `,` separators as integral
|
51 | * numbers between -3 and 63.
|
52 | */
|
53 | var LineParser = function(line) {
|
54 | this._line = line;
|
55 | this._cursor = 0;
|
56 | };
|
57 |
|
58 | /**
|
59 | * @return Next Base64 value or :
|
60 | * * -1: for `,`.
|
61 | * * -2: for `;`.
|
62 | * * -3: unexpected char.
|
63 | */
|
64 | LineParser.prototype.next = function() {
|
65 | while (true) {
|
66 | var c = this._line.charAt(this._cursor);
|
67 | this._cursor++;
|
68 | if (c == ',') return -1;
|
69 | if (c == ';') return -2;
|
70 | // Ignore blank chars.
|
71 | if (c <= ' ') continue;
|
72 | var v = BASE64.indexOf(c);
|
73 | if (v < 0) return -3;
|
74 | return v;
|
75 | }
|
76 | };
|
77 |
|
78 | /**
|
79 | * @return Next number
|
80 | */
|
81 | LineParser.prototype.nextNumber = function(n) {
|
82 | var continuationBit = n & 32;
|
83 | var signBit = n & 1;
|
84 | var shift = 5;
|
85 | var v;
|
86 | // Remove continuation bit (the left most).
|
87 | n = n & 31;
|
88 | while (continuationBit) {
|
89 | v = this.next();
|
90 | if (v < 0) {
|
91 | throw "[SourceMap] Unexpected char after a continuation bit in position " + this._cursor;
|
92 | }
|
93 | n += v << shift;
|
94 | shift += 5;
|
95 | continuationBit = v & 32;
|
96 | }
|
97 | if (n & 1) {
|
98 | return -(n >> 1);
|
99 | }
|
100 | return n >> 1;
|
101 | };
|
102 |
|
103 | /**
|
104 | * @return `true` if there are chars to parse, and `false` if we have
|
105 | * reached the end of the line.
|
106 | */
|
107 | LineParser.prototype.hasMoreChars = function() {
|
108 | return this._cursor < this._line.length;
|
109 | };
|
110 |
|
111 | /**
|
112 | * @return Current cursor position. Used to report errors.
|
113 | */
|
114 | LineParser.prototype.pos = function() {
|
115 | return this._cursor;
|
116 | };
|
117 |
|
118 | var SourceMap = function(sourcemap, generatedContent) {
|
119 | this.sourcemap(sourcemap);
|
120 | this.generatedContent(generatedContent);
|
121 | };
|
122 |
|
123 |
|
124 | /**
|
125 | * @return void
|
126 | */
|
127 | SourceMap.prototype.generatedContent = function(generatedContent) {
|
128 | if (typeof generatedContent === 'undefined') return this._generatedContent;
|
129 | // Remove trailing spaces and new lines.
|
130 | generatedContent = generatedContent.trimRight();
|
131 | // Counting lines.
|
132 | var lines = 1;
|
133 | // Position of the last line in the generated content.
|
134 | var lastLinePos = 0;
|
135 | // Index in the content,
|
136 | var i;
|
137 | // Current char.
|
138 | var c;
|
139 | for (i = 0; i < generatedContent.length; i++) {
|
140 | c = generatedContent.charAt(i);
|
141 | if (c == "\n") {
|
142 | lines++;
|
143 | lastLinePos = i+1;
|
144 | }
|
145 | }
|
146 | // Remove the last comment for source-maps, if any.
|
147 | if (generatedContent.substr(lastLinePos, 3) === '//#') {
|
148 | generatedContent = generatedContent.substr(0, lastLinePos).trimRight();
|
149 | }
|
150 | this._generatedContent = generatedContent;
|
151 | this._generatedContentLinesCount = lines;
|
152 | return this;
|
153 | };
|
154 |
|
155 |
|
156 | /**
|
157 | * Getter/setter for the attribute `sourcemap`.
|
158 | */
|
159 | SourceMap.prototype.sourcemap = function(v) {
|
160 | if (typeof v === 'undefined') return this._sourcemap;
|
161 | this._sourcemap = v;
|
162 | // Parsing.
|
163 | this._lines = this.decodeMappings(v.mappings);
|
164 | return this;
|
165 | };
|
166 |
|
167 |
|
168 | /**
|
169 | * @return {array} Array of lines. Each __line__ is an array of
|
170 | * items. An __item__ is the absolute values od a segment.
|
171 | */
|
172 | SourceMap.prototype.decodeMappings = function(mappings) {
|
173 | var parser = new LineParser(mappings);
|
174 | var lines = [];
|
175 | var line = [];
|
176 | var item = [];
|
177 | var accumulator = [0,0,0,0,0];
|
178 | var n;
|
179 | while (parser.hasMoreChars()) {
|
180 | n = parser.next();
|
181 | if (n == -3) {
|
182 | throw "[SourceMap] Bad char in `mappings` attribute at position " + parser.pos() + '!';
|
183 | }
|
184 | if (n == -1 || n == -2) {
|
185 | // Next item.
|
186 | line.push(item);
|
187 | item = [];
|
188 | if (n == -2) {
|
189 | // End of generated file's line.
|
190 | lines.push(line);
|
191 | line = [];
|
192 | }
|
193 | }
|
194 | else {
|
195 | accumulator[item.length] += parser.nextNumber(n);
|
196 | item.push(accumulator[item.length]);
|
197 | }
|
198 | }
|
199 | if (item.length > 0) line.push(item);
|
200 | if (line.length > 0) lines.push(line);
|
201 | return lines;
|
202 | };
|
203 |
|
204 |
|
205 | /**
|
206 | * @param {array} lines Array of lines. Each __line__ is an array of
|
207 | * items. An __item__ is the absolute values od a segment.
|
208 | *
|
209 | * @return {string} A mappings VLQ64 string. For instance:
|
210 | * "AAAA,QAAQ,2BAAA,CAA4B,2BAAA,CAA4B,uBAAA,CAAwB,2CACxF".
|
211 | */
|
212 | SourceMap.prototype.encodeMappings = function(lines) {
|
213 | var mappings = '';
|
214 | var absoluteItem = [0,0,0,0,0];
|
215 | lines.forEach(function (line) {
|
216 | // Start line: column == 0.
|
217 | absoluteItem[0] = 0;
|
218 | if (mappings != '') {
|
219 | // New line is marked with a `;`.
|
220 | mappings += ';';
|
221 | }
|
222 | // Flag used to add a `,` between segments.
|
223 | var firstItem = true;
|
224 | line.forEach(function (segment) {
|
225 | if (firstItem) {
|
226 | firstItem = false;
|
227 | } else {
|
228 | mappings += ',';
|
229 | }
|
230 | segment.forEach(function (value, index) {
|
231 | // `low` is used to cut a big number in chuncks of 5
|
232 | // bits.
|
233 | var low;
|
234 | // Tranform absolute value into relative value.
|
235 | value -= absoluteItem[index];
|
236 | absoluteItem[index] += value;
|
237 | // Convert value into VLQ64.
|
238 | if (value == 0) {
|
239 | // Special case of zero.
|
240 | mappings += "A";
|
241 | return;
|
242 | }
|
243 | if (value < 0) {
|
244 | // Negative value: add a tailing 1.
|
245 | value = ((-value) << 1) | 1;
|
246 | } else {
|
247 | // Positive value: add a tailing 0.
|
248 | value = value << 1;
|
249 | }
|
250 | while (value > 0) {
|
251 | low = value & 31;
|
252 | value = (value - low) >> 5;
|
253 | if (value > 0) {
|
254 | // Add continuation bit.
|
255 | low |= 32;
|
256 | }
|
257 | mappings += BASE64.charAt(low);
|
258 | }
|
259 | });
|
260 | });
|
261 | });
|
262 | return mappings;
|
263 | };
|
264 |
|
265 |
|
266 | /**
|
267 | *
|
268 | * @return this
|
269 | */
|
270 | SourceMap.prototype.append = function(srcMapToAppend) {
|
271 | // Protect against `null` or `undefined`.
|
272 | if (!srcMapToAppend) return this;
|
273 | // Check that the argument is of type `SourceMap`.
|
274 | if (typeof srcMapToAppend.append !== 'function' || typeof srcMapToAppend.encodeMappings !== 'function') {
|
275 | throw "[SourceMap] `srcMapToAppend` must be an instance of the class SourceMap!";
|
276 | }
|
277 |
|
278 | var srcmap1 = this.sourcemap();
|
279 | var srcmap2 = srcMapToAppend.sourcemap();
|
280 |
|
281 | //-------------------------------------------------
|
282 | // Step 1: Compute shifts.
|
283 | var shiftSources = srcmap1.sources.length;
|
284 | var shiftNames = srcmap1.names.length;
|
285 | //-------------------------------------------------
|
286 | // Step 2: Add `sources`, `sourcesContent` and `names`.
|
287 | ['sources', 'sourcesContent', 'names'].forEach(function (attributeName) {
|
288 | srcmap2[attributeName].forEach(function (item) {
|
289 | srcmap1[attributeName].push(item);
|
290 | });
|
291 | });
|
292 | //-------------------------------------------------
|
293 | // Step 3: Append content.
|
294 | this.generatedContent(this.generatedContent() + "\n" + srcMapToAppend.generatedContent());
|
295 | //-------------------------------------------------
|
296 | // Step 4: Add `mappings` while adding shifts.
|
297 | srcMapToAppend._lines.forEach(function (lineToAppend) {
|
298 | var currentLine = [];
|
299 | lineToAppend.forEach(function (itemToAppend) {
|
300 | // This is the structure of an item (all values are absolute) :
|
301 | // [generated column, original file index, orig. line, orig. column, name index]
|
302 | var item = itemToAppend.slice(); // Clone it.
|
303 | // `item` length can be 1, 4 or 5.
|
304 | if (item.length > 1) {
|
305 | item[1] += shiftSources; // Original file index.
|
306 | if (item.length > 4) {
|
307 | item[4] += shiftNames;
|
308 | }
|
309 | }
|
310 | currentLine.push(item);
|
311 | });
|
312 | this._lines.push(currentLine);
|
313 | }, this);
|
314 | //--------------------------------------------------
|
315 | // Step 5: Compact `_lines` into `mappings` according to VLQ64.
|
316 | var mappings = this.encodeMappings(this._lines);
|
317 | this._sourcemap.mappings = mappings;
|
318 | return this;
|
319 | };
|
320 |
|
321 | module.exports = SourceMap;
|