UNPKG

10.1 kBJavaScriptView Raw
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)
46var BASE64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
47
48
49/**
50 * Read a Base64 encoded line with `;` and `,` separators as integral
51 * numbers between -3 and 63.
52 */
53var 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 */
64LineParser.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 */
81LineParser.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 */
107LineParser.prototype.hasMoreChars = function() {
108 return this._cursor < this._line.length;
109};
110
111/**
112 * @return Current cursor position. Used to report errors.
113 */
114LineParser.prototype.pos = function() {
115 return this._cursor;
116};
117
118var SourceMap = function(sourcemap, generatedContent) {
119 this.sourcemap(sourcemap);
120 this.generatedContent(generatedContent);
121};
122
123
124/**
125 * @return void
126 */
127SourceMap.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 */
159SourceMap.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 */
172SourceMap.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 */
212SourceMap.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 */
270SourceMap.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
321module.exports = SourceMap;