]> git.r.bdr.sh - rbdr/dotfiles/blob
cfaa299a51f3119659d07664c3289aedbea7aec4
[rbdr/dotfiles] /
1 /* -*- Mode: js; js-indent-level: 2; -*- */
2 /*
3 * Copyright 2011 Mozilla Foundation and contributors
4 * Licensed under the New BSD license. See LICENSE or:
5 * http://opensource.org/licenses/BSD-3-Clause
6 */
7 if (typeof define !== 'function') {
8 var define = require('amdefine')(module, require);
9 }
10 define(function (require, exports, module) {
11
12 var util = require('./util');
13 var binarySearch = require('./binary-search');
14 var ArraySet = require('./array-set').ArraySet;
15 var base64VLQ = require('./base64-vlq');
16
17 /**
18 * A SourceMapConsumer instance represents a parsed source map which we can
19 * query for information about the original file positions by giving it a file
20 * position in the generated source.
21 *
22 * The only parameter is the raw source map (either as a JSON string, or
23 * already parsed to an object). According to the spec, source maps have the
24 * following attributes:
25 *
26 * - version: Which version of the source map spec this map is following.
27 * - sources: An array of URLs to the original source files.
28 * - names: An array of identifiers which can be referrenced by individual mappings.
29 * - sourceRoot: Optional. The URL root from which all sources are relative.
30 * - sourcesContent: Optional. An array of contents of the original source files.
31 * - mappings: A string of base64 VLQs which contain the actual mappings.
32 * - file: Optional. The generated file this source map is associated with.
33 *
34 * Here is an example source map, taken from the source map spec[0]:
35 *
36 * {
37 * version : 3,
38 * file: "out.js",
39 * sourceRoot : "",
40 * sources: ["foo.js", "bar.js"],
41 * names: ["src", "maps", "are", "fun"],
42 * mappings: "AA,AB;;ABCDE;"
43 * }
44 *
45 * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit?pli=1#
46 */
47 function SourceMapConsumer(aSourceMap) {
48 var sourceMap = aSourceMap;
49 if (typeof aSourceMap === 'string') {
50 sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, ''));
51 }
52
53 var version = util.getArg(sourceMap, 'version');
54 var sources = util.getArg(sourceMap, 'sources');
55 // Sass 3.3 leaves out the 'names' array, so we deviate from the spec (which
56 // requires the array) to play nice here.
57 var names = util.getArg(sourceMap, 'names', []);
58 var sourceRoot = util.getArg(sourceMap, 'sourceRoot', null);
59 var sourcesContent = util.getArg(sourceMap, 'sourcesContent', null);
60 var mappings = util.getArg(sourceMap, 'mappings');
61 var file = util.getArg(sourceMap, 'file', null);
62
63 // Once again, Sass deviates from the spec and supplies the version as a
64 // string rather than a number, so we use loose equality checking here.
65 if (version != this._version) {
66 throw new Error('Unsupported version: ' + version);
67 }
68
69 // Some source maps produce relative source paths like "./foo.js" instead of
70 // "foo.js". Normalize these first so that future comparisons will succeed.
71 // See bugzil.la/1090768.
72 sources = sources.map(util.normalize);
73
74 // Pass `true` below to allow duplicate names and sources. While source maps
75 // are intended to be compressed and deduplicated, the TypeScript compiler
76 // sometimes generates source maps with duplicates in them. See Github issue
77 // #72 and bugzil.la/889492.
78 this._names = ArraySet.fromArray(names, true);
79 this._sources = ArraySet.fromArray(sources, true);
80
81 this.sourceRoot = sourceRoot;
82 this.sourcesContent = sourcesContent;
83 this._mappings = mappings;
84 this.file = file;
85 }
86
87 /**
88 * Create a SourceMapConsumer from a SourceMapGenerator.
89 *
90 * @param SourceMapGenerator aSourceMap
91 * The source map that will be consumed.
92 * @returns SourceMapConsumer
93 */
94 SourceMapConsumer.fromSourceMap =
95 function SourceMapConsumer_fromSourceMap(aSourceMap) {
96 var smc = Object.create(SourceMapConsumer.prototype);
97
98 smc._names = ArraySet.fromArray(aSourceMap._names.toArray(), true);
99 smc._sources = ArraySet.fromArray(aSourceMap._sources.toArray(), true);
100 smc.sourceRoot = aSourceMap._sourceRoot;
101 smc.sourcesContent = aSourceMap._generateSourcesContent(smc._sources.toArray(),
102 smc.sourceRoot);
103 smc.file = aSourceMap._file;
104
105 smc.__generatedMappings = aSourceMap._mappings.toArray().slice();
106 smc.__originalMappings = aSourceMap._mappings.toArray().slice()
107 .sort(util.compareByOriginalPositions);
108
109 return smc;
110 };
111
112 /**
113 * The version of the source mapping spec that we are consuming.
114 */
115 SourceMapConsumer.prototype._version = 3;
116
117 /**
118 * The list of original sources.
119 */
120 Object.defineProperty(SourceMapConsumer.prototype, 'sources', {
121 get: function () {
122 return this._sources.toArray().map(function (s) {
123 return this.sourceRoot != null ? util.join(this.sourceRoot, s) : s;
124 }, this);
125 }
126 });
127
128 // `__generatedMappings` and `__originalMappings` are arrays that hold the
129 // parsed mapping coordinates from the source map's "mappings" attribute. They
130 // are lazily instantiated, accessed via the `_generatedMappings` and
131 // `_originalMappings` getters respectively, and we only parse the mappings
132 // and create these arrays once queried for a source location. We jump through
133 // these hoops because there can be many thousands of mappings, and parsing
134 // them is expensive, so we only want to do it if we must.
135 //
136 // Each object in the arrays is of the form:
137 //
138 // {
139 // generatedLine: The line number in the generated code,
140 // generatedColumn: The column number in the generated code,
141 // source: The path to the original source file that generated this
142 // chunk of code,
143 // originalLine: The line number in the original source that
144 // corresponds to this chunk of generated code,
145 // originalColumn: The column number in the original source that
146 // corresponds to this chunk of generated code,
147 // name: The name of the original symbol which generated this chunk of
148 // code.
149 // }
150 //
151 // All properties except for `generatedLine` and `generatedColumn` can be
152 // `null`.
153 //
154 // `_generatedMappings` is ordered by the generated positions.
155 //
156 // `_originalMappings` is ordered by the original positions.
157
158 SourceMapConsumer.prototype.__generatedMappings = null;
159 Object.defineProperty(SourceMapConsumer.prototype, '_generatedMappings', {
160 get: function () {
161 if (!this.__generatedMappings) {
162 this.__generatedMappings = [];
163 this.__originalMappings = [];
164 this._parseMappings(this._mappings, this.sourceRoot);
165 }
166
167 return this.__generatedMappings;
168 }
169 });
170
171 SourceMapConsumer.prototype.__originalMappings = null;
172 Object.defineProperty(SourceMapConsumer.prototype, '_originalMappings', {
173 get: function () {
174 if (!this.__originalMappings) {
175 this.__generatedMappings = [];
176 this.__originalMappings = [];
177 this._parseMappings(this._mappings, this.sourceRoot);
178 }
179
180 return this.__originalMappings;
181 }
182 });
183
184 SourceMapConsumer.prototype._nextCharIsMappingSeparator =
185 function SourceMapConsumer_nextCharIsMappingSeparator(aStr) {
186 var c = aStr.charAt(0);
187 return c === ";" || c === ",";
188 };
189
190 /**
191 * Parse the mappings in a string in to a data structure which we can easily
192 * query (the ordered arrays in the `this.__generatedMappings` and
193 * `this.__originalMappings` properties).
194 */
195 SourceMapConsumer.prototype._parseMappings =
196 function SourceMapConsumer_parseMappings(aStr, aSourceRoot) {
197 var generatedLine = 1;
198 var previousGeneratedColumn = 0;
199 var previousOriginalLine = 0;
200 var previousOriginalColumn = 0;
201 var previousSource = 0;
202 var previousName = 0;
203 var str = aStr;
204 var temp = {};
205 var mapping;
206
207 while (str.length > 0) {
208 if (str.charAt(0) === ';') {
209 generatedLine++;
210 str = str.slice(1);
211 previousGeneratedColumn = 0;
212 }
213 else if (str.charAt(0) === ',') {
214 str = str.slice(1);
215 }
216 else {
217 mapping = {};
218 mapping.generatedLine = generatedLine;
219
220 // Generated column.
221 base64VLQ.decode(str, temp);
222 mapping.generatedColumn = previousGeneratedColumn + temp.value;
223 previousGeneratedColumn = mapping.generatedColumn;
224 str = temp.rest;
225
226 if (str.length > 0 && !this._nextCharIsMappingSeparator(str)) {
227 // Original source.
228 base64VLQ.decode(str, temp);
229 mapping.source = this._sources.at(previousSource + temp.value);
230 previousSource += temp.value;
231 str = temp.rest;
232 if (str.length === 0 || this._nextCharIsMappingSeparator(str)) {
233 throw new Error('Found a source, but no line and column');
234 }
235
236 // Original line.
237 base64VLQ.decode(str, temp);
238 mapping.originalLine = previousOriginalLine + temp.value;
239 previousOriginalLine = mapping.originalLine;
240 // Lines are stored 0-based
241 mapping.originalLine += 1;
242 str = temp.rest;
243 if (str.length === 0 || this._nextCharIsMappingSeparator(str)) {
244 throw new Error('Found a source and line, but no column');
245 }
246
247 // Original column.
248 base64VLQ.decode(str, temp);
249 mapping.originalColumn = previousOriginalColumn + temp.value;
250 previousOriginalColumn = mapping.originalColumn;
251 str = temp.rest;
252
253 if (str.length > 0 && !this._nextCharIsMappingSeparator(str)) {
254 // Original name.
255 base64VLQ.decode(str, temp);
256 mapping.name = this._names.at(previousName + temp.value);
257 previousName += temp.value;
258 str = temp.rest;
259 }
260 }
261
262 this.__generatedMappings.push(mapping);
263 if (typeof mapping.originalLine === 'number') {
264 this.__originalMappings.push(mapping);
265 }
266 }
267 }
268
269 this.__generatedMappings.sort(util.compareByGeneratedPositions);
270 this.__originalMappings.sort(util.compareByOriginalPositions);
271 };
272
273 /**
274 * Find the mapping that best matches the hypothetical "needle" mapping that
275 * we are searching for in the given "haystack" of mappings.
276 */
277 SourceMapConsumer.prototype._findMapping =
278 function SourceMapConsumer_findMapping(aNeedle, aMappings, aLineName,
279 aColumnName, aComparator) {
280 // To return the position we are searching for, we must first find the
281 // mapping for the given position and then return the opposite position it
282 // points to. Because the mappings are sorted, we can use binary search to
283 // find the best mapping.
284
285 if (aNeedle[aLineName] <= 0) {
286 throw new TypeError('Line must be greater than or equal to 1, got '
287 + aNeedle[aLineName]);
288 }
289 if (aNeedle[aColumnName] < 0) {
290 throw new TypeError('Column must be greater than or equal to 0, got '
291 + aNeedle[aColumnName]);
292 }
293
294 return binarySearch.search(aNeedle, aMappings, aComparator);
295 };
296
297 /**
298 * Compute the last column for each generated mapping. The last column is
299 * inclusive.
300 */
301 SourceMapConsumer.prototype.computeColumnSpans =
302 function SourceMapConsumer_computeColumnSpans() {
303 for (var index = 0; index < this._generatedMappings.length; ++index) {
304 var mapping = this._generatedMappings[index];
305
306 // Mappings do not contain a field for the last generated columnt. We
307 // can come up with an optimistic estimate, however, by assuming that
308 // mappings are contiguous (i.e. given two consecutive mappings, the
309 // first mapping ends where the second one starts).
310 if (index + 1 < this._generatedMappings.length) {
311 var nextMapping = this._generatedMappings[index + 1];
312
313 if (mapping.generatedLine === nextMapping.generatedLine) {
314 mapping.lastGeneratedColumn = nextMapping.generatedColumn - 1;
315 continue;
316 }
317 }
318
319 // The last mapping for each line spans the entire line.
320 mapping.lastGeneratedColumn = Infinity;
321 }
322 };
323
324 /**
325 * Returns the original source, line, and column information for the generated
326 * source's line and column positions provided. The only argument is an object
327 * with the following properties:
328 *
329 * - line: The line number in the generated source.
330 * - column: The column number in the generated source.
331 *
332 * and an object is returned with the following properties:
333 *
334 * - source: The original source file, or null.
335 * - line: The line number in the original source, or null.
336 * - column: The column number in the original source, or null.
337 * - name: The original identifier, or null.
338 */
339 SourceMapConsumer.prototype.originalPositionFor =
340 function SourceMapConsumer_originalPositionFor(aArgs) {
341 var needle = {
342 generatedLine: util.getArg(aArgs, 'line'),
343 generatedColumn: util.getArg(aArgs, 'column')
344 };
345
346 var index = this._findMapping(needle,
347 this._generatedMappings,
348 "generatedLine",
349 "generatedColumn",
350 util.compareByGeneratedPositions);
351
352 if (index >= 0) {
353 var mapping = this._generatedMappings[index];
354
355 if (mapping.generatedLine === needle.generatedLine) {
356 var source = util.getArg(mapping, 'source', null);
357 if (source != null && this.sourceRoot != null) {
358 source = util.join(this.sourceRoot, source);
359 }
360 return {
361 source: source,
362 line: util.getArg(mapping, 'originalLine', null),
363 column: util.getArg(mapping, 'originalColumn', null),
364 name: util.getArg(mapping, 'name', null)
365 };
366 }
367 }
368
369 return {
370 source: null,
371 line: null,
372 column: null,
373 name: null
374 };
375 };
376
377 /**
378 * Returns the original source content. The only argument is the url of the
379 * original source file. Returns null if no original source content is
380 * availible.
381 */
382 SourceMapConsumer.prototype.sourceContentFor =
383 function SourceMapConsumer_sourceContentFor(aSource) {
384 if (!this.sourcesContent) {
385 return null;
386 }
387
388 if (this.sourceRoot != null) {
389 aSource = util.relative(this.sourceRoot, aSource);
390 }
391
392 if (this._sources.has(aSource)) {
393 return this.sourcesContent[this._sources.indexOf(aSource)];
394 }
395
396 var url;
397 if (this.sourceRoot != null
398 && (url = util.urlParse(this.sourceRoot))) {
399 // XXX: file:// URIs and absolute paths lead to unexpected behavior for
400 // many users. We can help them out when they expect file:// URIs to
401 // behave like it would if they were running a local HTTP server. See
402 // https://bugzilla.mozilla.org/show_bug.cgi?id=885597.
403 var fileUriAbsPath = aSource.replace(/^file:\/\//, "");
404 if (url.scheme == "file"
405 && this._sources.has(fileUriAbsPath)) {
406 return this.sourcesContent[this._sources.indexOf(fileUriAbsPath)]
407 }
408
409 if ((!url.path || url.path == "/")
410 && this._sources.has("/" + aSource)) {
411 return this.sourcesContent[this._sources.indexOf("/" + aSource)];
412 }
413 }
414
415 throw new Error('"' + aSource + '" is not in the SourceMap.');
416 };
417
418 /**
419 * Returns the generated line and column information for the original source,
420 * line, and column positions provided. The only argument is an object with
421 * the following properties:
422 *
423 * - source: The filename of the original source.
424 * - line: The line number in the original source.
425 * - column: The column number in the original source.
426 *
427 * and an object is returned with the following properties:
428 *
429 * - line: The line number in the generated source, or null.
430 * - column: The column number in the generated source, or null.
431 */
432 SourceMapConsumer.prototype.generatedPositionFor =
433 function SourceMapConsumer_generatedPositionFor(aArgs) {
434 var needle = {
435 source: util.getArg(aArgs, 'source'),
436 originalLine: util.getArg(aArgs, 'line'),
437 originalColumn: util.getArg(aArgs, 'column')
438 };
439
440 if (this.sourceRoot != null) {
441 needle.source = util.relative(this.sourceRoot, needle.source);
442 }
443
444 var index = this._findMapping(needle,
445 this._originalMappings,
446 "originalLine",
447 "originalColumn",
448 util.compareByOriginalPositions);
449
450 if (index >= 0) {
451 var mapping = this._originalMappings[index];
452
453 return {
454 line: util.getArg(mapping, 'generatedLine', null),
455 column: util.getArg(mapping, 'generatedColumn', null),
456 lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null)
457 };
458 }
459
460 return {
461 line: null,
462 column: null,
463 lastColumn: null
464 };
465 };
466
467 /**
468 * Returns all generated line and column information for the original source
469 * and line provided. The only argument is an object with the following
470 * properties:
471 *
472 * - source: The filename of the original source.
473 * - line: The line number in the original source.
474 *
475 * and an array of objects is returned, each with the following properties:
476 *
477 * - line: The line number in the generated source, or null.
478 * - column: The column number in the generated source, or null.
479 */
480 SourceMapConsumer.prototype.allGeneratedPositionsFor =
481 function SourceMapConsumer_allGeneratedPositionsFor(aArgs) {
482 // When there is no exact match, SourceMapConsumer.prototype._findMapping
483 // returns the index of the closest mapping less than the needle. By
484 // setting needle.originalColumn to Infinity, we thus find the last
485 // mapping for the given line, provided such a mapping exists.
486 var needle = {
487 source: util.getArg(aArgs, 'source'),
488 originalLine: util.getArg(aArgs, 'line'),
489 originalColumn: Infinity
490 };
491
492 if (this.sourceRoot != null) {
493 needle.source = util.relative(this.sourceRoot, needle.source);
494 }
495
496 var mappings = [];
497
498 var index = this._findMapping(needle,
499 this._originalMappings,
500 "originalLine",
501 "originalColumn",
502 util.compareByOriginalPositions);
503 if (index >= 0) {
504 var mapping = this._originalMappings[index];
505
506 while (mapping && mapping.originalLine === needle.originalLine) {
507 mappings.push({
508 line: util.getArg(mapping, 'generatedLine', null),
509 column: util.getArg(mapping, 'generatedColumn', null),
510 lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null)
511 });
512
513 mapping = this._originalMappings[--index];
514 }
515 }
516
517 return mappings.reverse();
518 };
519
520 SourceMapConsumer.GENERATED_ORDER = 1;
521 SourceMapConsumer.ORIGINAL_ORDER = 2;
522
523 /**
524 * Iterate over each mapping between an original source/line/column and a
525 * generated line/column in this source map.
526 *
527 * @param Function aCallback
528 * The function that is called with each mapping.
529 * @param Object aContext
530 * Optional. If specified, this object will be the value of `this` every
531 * time that `aCallback` is called.
532 * @param aOrder
533 * Either `SourceMapConsumer.GENERATED_ORDER` or
534 * `SourceMapConsumer.ORIGINAL_ORDER`. Specifies whether you want to
535 * iterate over the mappings sorted by the generated file's line/column
536 * order or the original's source/line/column order, respectively. Defaults to
537 * `SourceMapConsumer.GENERATED_ORDER`.
538 */
539 SourceMapConsumer.prototype.eachMapping =
540 function SourceMapConsumer_eachMapping(aCallback, aContext, aOrder) {
541 var context = aContext || null;
542 var order = aOrder || SourceMapConsumer.GENERATED_ORDER;
543
544 var mappings;
545 switch (order) {
546 case SourceMapConsumer.GENERATED_ORDER:
547 mappings = this._generatedMappings;
548 break;
549 case SourceMapConsumer.ORIGINAL_ORDER:
550 mappings = this._originalMappings;
551 break;
552 default:
553 throw new Error("Unknown order of iteration.");
554 }
555
556 var sourceRoot = this.sourceRoot;
557 mappings.map(function (mapping) {
558 var source = mapping.source;
559 if (source != null && sourceRoot != null) {
560 source = util.join(sourceRoot, source);
561 }
562 return {
563 source: source,
564 generatedLine: mapping.generatedLine,
565 generatedColumn: mapping.generatedColumn,
566 originalLine: mapping.originalLine,
567 originalColumn: mapping.originalColumn,
568 name: mapping.name
569 };
570 }).forEach(aCallback, context);
571 };
572
573 exports.SourceMapConsumer = SourceMapConsumer;
574
575 });