Initial commit
[yaffs-website] / node_modules / source-map / lib / source-map-consumer.js
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
8 var util = require('./util');
9 var binarySearch = require('./binary-search');
10 var ArraySet = require('./array-set').ArraySet;
11 var base64VLQ = require('./base64-vlq');
12 var quickSort = require('./quick-sort').quickSort;
13
14 function SourceMapConsumer(aSourceMap) {
15   var sourceMap = aSourceMap;
16   if (typeof aSourceMap === 'string') {
17     sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, ''));
18   }
19
20   return sourceMap.sections != null
21     ? new IndexedSourceMapConsumer(sourceMap)
22     : new BasicSourceMapConsumer(sourceMap);
23 }
24
25 SourceMapConsumer.fromSourceMap = function(aSourceMap) {
26   return BasicSourceMapConsumer.fromSourceMap(aSourceMap);
27 }
28
29 /**
30  * The version of the source mapping spec that we are consuming.
31  */
32 SourceMapConsumer.prototype._version = 3;
33
34 // `__generatedMappings` and `__originalMappings` are arrays that hold the
35 // parsed mapping coordinates from the source map's "mappings" attribute. They
36 // are lazily instantiated, accessed via the `_generatedMappings` and
37 // `_originalMappings` getters respectively, and we only parse the mappings
38 // and create these arrays once queried for a source location. We jump through
39 // these hoops because there can be many thousands of mappings, and parsing
40 // them is expensive, so we only want to do it if we must.
41 //
42 // Each object in the arrays is of the form:
43 //
44 //     {
45 //       generatedLine: The line number in the generated code,
46 //       generatedColumn: The column number in the generated code,
47 //       source: The path to the original source file that generated this
48 //               chunk of code,
49 //       originalLine: The line number in the original source that
50 //                     corresponds to this chunk of generated code,
51 //       originalColumn: The column number in the original source that
52 //                       corresponds to this chunk of generated code,
53 //       name: The name of the original symbol which generated this chunk of
54 //             code.
55 //     }
56 //
57 // All properties except for `generatedLine` and `generatedColumn` can be
58 // `null`.
59 //
60 // `_generatedMappings` is ordered by the generated positions.
61 //
62 // `_originalMappings` is ordered by the original positions.
63
64 SourceMapConsumer.prototype.__generatedMappings = null;
65 Object.defineProperty(SourceMapConsumer.prototype, '_generatedMappings', {
66   get: function () {
67     if (!this.__generatedMappings) {
68       this._parseMappings(this._mappings, this.sourceRoot);
69     }
70
71     return this.__generatedMappings;
72   }
73 });
74
75 SourceMapConsumer.prototype.__originalMappings = null;
76 Object.defineProperty(SourceMapConsumer.prototype, '_originalMappings', {
77   get: function () {
78     if (!this.__originalMappings) {
79       this._parseMappings(this._mappings, this.sourceRoot);
80     }
81
82     return this.__originalMappings;
83   }
84 });
85
86 SourceMapConsumer.prototype._charIsMappingSeparator =
87   function SourceMapConsumer_charIsMappingSeparator(aStr, index) {
88     var c = aStr.charAt(index);
89     return c === ";" || c === ",";
90   };
91
92 /**
93  * Parse the mappings in a string in to a data structure which we can easily
94  * query (the ordered arrays in the `this.__generatedMappings` and
95  * `this.__originalMappings` properties).
96  */
97 SourceMapConsumer.prototype._parseMappings =
98   function SourceMapConsumer_parseMappings(aStr, aSourceRoot) {
99     throw new Error("Subclasses must implement _parseMappings");
100   };
101
102 SourceMapConsumer.GENERATED_ORDER = 1;
103 SourceMapConsumer.ORIGINAL_ORDER = 2;
104
105 SourceMapConsumer.GREATEST_LOWER_BOUND = 1;
106 SourceMapConsumer.LEAST_UPPER_BOUND = 2;
107
108 /**
109  * Iterate over each mapping between an original source/line/column and a
110  * generated line/column in this source map.
111  *
112  * @param Function aCallback
113  *        The function that is called with each mapping.
114  * @param Object aContext
115  *        Optional. If specified, this object will be the value of `this` every
116  *        time that `aCallback` is called.
117  * @param aOrder
118  *        Either `SourceMapConsumer.GENERATED_ORDER` or
119  *        `SourceMapConsumer.ORIGINAL_ORDER`. Specifies whether you want to
120  *        iterate over the mappings sorted by the generated file's line/column
121  *        order or the original's source/line/column order, respectively. Defaults to
122  *        `SourceMapConsumer.GENERATED_ORDER`.
123  */
124 SourceMapConsumer.prototype.eachMapping =
125   function SourceMapConsumer_eachMapping(aCallback, aContext, aOrder) {
126     var context = aContext || null;
127     var order = aOrder || SourceMapConsumer.GENERATED_ORDER;
128
129     var mappings;
130     switch (order) {
131     case SourceMapConsumer.GENERATED_ORDER:
132       mappings = this._generatedMappings;
133       break;
134     case SourceMapConsumer.ORIGINAL_ORDER:
135       mappings = this._originalMappings;
136       break;
137     default:
138       throw new Error("Unknown order of iteration.");
139     }
140
141     var sourceRoot = this.sourceRoot;
142     mappings.map(function (mapping) {
143       var source = mapping.source === null ? null : this._sources.at(mapping.source);
144       if (source != null && sourceRoot != null) {
145         source = util.join(sourceRoot, source);
146       }
147       return {
148         source: source,
149         generatedLine: mapping.generatedLine,
150         generatedColumn: mapping.generatedColumn,
151         originalLine: mapping.originalLine,
152         originalColumn: mapping.originalColumn,
153         name: mapping.name === null ? null : this._names.at(mapping.name)
154       };
155     }, this).forEach(aCallback, context);
156   };
157
158 /**
159  * Returns all generated line and column information for the original source,
160  * line, and column provided. If no column is provided, returns all mappings
161  * corresponding to a either the line we are searching for or the next
162  * closest line that has any mappings. Otherwise, returns all mappings
163  * corresponding to the given line and either the column we are searching for
164  * or the next closest column that has any offsets.
165  *
166  * The only argument is an object with the following properties:
167  *
168  *   - source: The filename of the original source.
169  *   - line: The line number in the original source.
170  *   - column: Optional. the column number in the original source.
171  *
172  * and an array of objects is returned, each with the following properties:
173  *
174  *   - line: The line number in the generated source, or null.
175  *   - column: The column number in the generated source, or null.
176  */
177 SourceMapConsumer.prototype.allGeneratedPositionsFor =
178   function SourceMapConsumer_allGeneratedPositionsFor(aArgs) {
179     var line = util.getArg(aArgs, 'line');
180
181     // When there is no exact match, BasicSourceMapConsumer.prototype._findMapping
182     // returns the index of the closest mapping less than the needle. By
183     // setting needle.originalColumn to 0, we thus find the last mapping for
184     // the given line, provided such a mapping exists.
185     var needle = {
186       source: util.getArg(aArgs, 'source'),
187       originalLine: line,
188       originalColumn: util.getArg(aArgs, 'column', 0)
189     };
190
191     if (this.sourceRoot != null) {
192       needle.source = util.relative(this.sourceRoot, needle.source);
193     }
194     if (!this._sources.has(needle.source)) {
195       return [];
196     }
197     needle.source = this._sources.indexOf(needle.source);
198
199     var mappings = [];
200
201     var index = this._findMapping(needle,
202                                   this._originalMappings,
203                                   "originalLine",
204                                   "originalColumn",
205                                   util.compareByOriginalPositions,
206                                   binarySearch.LEAST_UPPER_BOUND);
207     if (index >= 0) {
208       var mapping = this._originalMappings[index];
209
210       if (aArgs.column === undefined) {
211         var originalLine = mapping.originalLine;
212
213         // Iterate until either we run out of mappings, or we run into
214         // a mapping for a different line than the one we found. Since
215         // mappings are sorted, this is guaranteed to find all mappings for
216         // the line we found.
217         while (mapping && mapping.originalLine === originalLine) {
218           mappings.push({
219             line: util.getArg(mapping, 'generatedLine', null),
220             column: util.getArg(mapping, 'generatedColumn', null),
221             lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null)
222           });
223
224           mapping = this._originalMappings[++index];
225         }
226       } else {
227         var originalColumn = mapping.originalColumn;
228
229         // Iterate until either we run out of mappings, or we run into
230         // a mapping for a different line than the one we were searching for.
231         // Since mappings are sorted, this is guaranteed to find all mappings for
232         // the line we are searching for.
233         while (mapping &&
234                mapping.originalLine === line &&
235                mapping.originalColumn == originalColumn) {
236           mappings.push({
237             line: util.getArg(mapping, 'generatedLine', null),
238             column: util.getArg(mapping, 'generatedColumn', null),
239             lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null)
240           });
241
242           mapping = this._originalMappings[++index];
243         }
244       }
245     }
246
247     return mappings;
248   };
249
250 exports.SourceMapConsumer = SourceMapConsumer;
251
252 /**
253  * A BasicSourceMapConsumer instance represents a parsed source map which we can
254  * query for information about the original file positions by giving it a file
255  * position in the generated source.
256  *
257  * The only parameter is the raw source map (either as a JSON string, or
258  * already parsed to an object). According to the spec, source maps have the
259  * following attributes:
260  *
261  *   - version: Which version of the source map spec this map is following.
262  *   - sources: An array of URLs to the original source files.
263  *   - names: An array of identifiers which can be referrenced by individual mappings.
264  *   - sourceRoot: Optional. The URL root from which all sources are relative.
265  *   - sourcesContent: Optional. An array of contents of the original source files.
266  *   - mappings: A string of base64 VLQs which contain the actual mappings.
267  *   - file: Optional. The generated file this source map is associated with.
268  *
269  * Here is an example source map, taken from the source map spec[0]:
270  *
271  *     {
272  *       version : 3,
273  *       file: "out.js",
274  *       sourceRoot : "",
275  *       sources: ["foo.js", "bar.js"],
276  *       names: ["src", "maps", "are", "fun"],
277  *       mappings: "AA,AB;;ABCDE;"
278  *     }
279  *
280  * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit?pli=1#
281  */
282 function BasicSourceMapConsumer(aSourceMap) {
283   var sourceMap = aSourceMap;
284   if (typeof aSourceMap === 'string') {
285     sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, ''));
286   }
287
288   var version = util.getArg(sourceMap, 'version');
289   var sources = util.getArg(sourceMap, 'sources');
290   // Sass 3.3 leaves out the 'names' array, so we deviate from the spec (which
291   // requires the array) to play nice here.
292   var names = util.getArg(sourceMap, 'names', []);
293   var sourceRoot = util.getArg(sourceMap, 'sourceRoot', null);
294   var sourcesContent = util.getArg(sourceMap, 'sourcesContent', null);
295   var mappings = util.getArg(sourceMap, 'mappings');
296   var file = util.getArg(sourceMap, 'file', null);
297
298   // Once again, Sass deviates from the spec and supplies the version as a
299   // string rather than a number, so we use loose equality checking here.
300   if (version != this._version) {
301     throw new Error('Unsupported version: ' + version);
302   }
303
304   sources = sources
305     .map(String)
306     // Some source maps produce relative source paths like "./foo.js" instead of
307     // "foo.js".  Normalize these first so that future comparisons will succeed.
308     // See bugzil.la/1090768.
309     .map(util.normalize)
310     // Always ensure that absolute sources are internally stored relative to
311     // the source root, if the source root is absolute. Not doing this would
312     // be particularly problematic when the source root is a prefix of the
313     // source (valid, but why??). See github issue #199 and bugzil.la/1188982.
314     .map(function (source) {
315       return sourceRoot && util.isAbsolute(sourceRoot) && util.isAbsolute(source)
316         ? util.relative(sourceRoot, source)
317         : source;
318     });
319
320   // Pass `true` below to allow duplicate names and sources. While source maps
321   // are intended to be compressed and deduplicated, the TypeScript compiler
322   // sometimes generates source maps with duplicates in them. See Github issue
323   // #72 and bugzil.la/889492.
324   this._names = ArraySet.fromArray(names.map(String), true);
325   this._sources = ArraySet.fromArray(sources, true);
326
327   this.sourceRoot = sourceRoot;
328   this.sourcesContent = sourcesContent;
329   this._mappings = mappings;
330   this.file = file;
331 }
332
333 BasicSourceMapConsumer.prototype = Object.create(SourceMapConsumer.prototype);
334 BasicSourceMapConsumer.prototype.consumer = SourceMapConsumer;
335
336 /**
337  * Create a BasicSourceMapConsumer from a SourceMapGenerator.
338  *
339  * @param SourceMapGenerator aSourceMap
340  *        The source map that will be consumed.
341  * @returns BasicSourceMapConsumer
342  */
343 BasicSourceMapConsumer.fromSourceMap =
344   function SourceMapConsumer_fromSourceMap(aSourceMap) {
345     var smc = Object.create(BasicSourceMapConsumer.prototype);
346
347     var names = smc._names = ArraySet.fromArray(aSourceMap._names.toArray(), true);
348     var sources = smc._sources = ArraySet.fromArray(aSourceMap._sources.toArray(), true);
349     smc.sourceRoot = aSourceMap._sourceRoot;
350     smc.sourcesContent = aSourceMap._generateSourcesContent(smc._sources.toArray(),
351                                                             smc.sourceRoot);
352     smc.file = aSourceMap._file;
353
354     // Because we are modifying the entries (by converting string sources and
355     // names to indices into the sources and names ArraySets), we have to make
356     // a copy of the entry or else bad things happen. Shared mutable state
357     // strikes again! See github issue #191.
358
359     var generatedMappings = aSourceMap._mappings.toArray().slice();
360     var destGeneratedMappings = smc.__generatedMappings = [];
361     var destOriginalMappings = smc.__originalMappings = [];
362
363     for (var i = 0, length = generatedMappings.length; i < length; i++) {
364       var srcMapping = generatedMappings[i];
365       var destMapping = new Mapping;
366       destMapping.generatedLine = srcMapping.generatedLine;
367       destMapping.generatedColumn = srcMapping.generatedColumn;
368
369       if (srcMapping.source) {
370         destMapping.source = sources.indexOf(srcMapping.source);
371         destMapping.originalLine = srcMapping.originalLine;
372         destMapping.originalColumn = srcMapping.originalColumn;
373
374         if (srcMapping.name) {
375           destMapping.name = names.indexOf(srcMapping.name);
376         }
377
378         destOriginalMappings.push(destMapping);
379       }
380
381       destGeneratedMappings.push(destMapping);
382     }
383
384     quickSort(smc.__originalMappings, util.compareByOriginalPositions);
385
386     return smc;
387   };
388
389 /**
390  * The version of the source mapping spec that we are consuming.
391  */
392 BasicSourceMapConsumer.prototype._version = 3;
393
394 /**
395  * The list of original sources.
396  */
397 Object.defineProperty(BasicSourceMapConsumer.prototype, 'sources', {
398   get: function () {
399     return this._sources.toArray().map(function (s) {
400       return this.sourceRoot != null ? util.join(this.sourceRoot, s) : s;
401     }, this);
402   }
403 });
404
405 /**
406  * Provide the JIT with a nice shape / hidden class.
407  */
408 function Mapping() {
409   this.generatedLine = 0;
410   this.generatedColumn = 0;
411   this.source = null;
412   this.originalLine = null;
413   this.originalColumn = null;
414   this.name = null;
415 }
416
417 /**
418  * Parse the mappings in a string in to a data structure which we can easily
419  * query (the ordered arrays in the `this.__generatedMappings` and
420  * `this.__originalMappings` properties).
421  */
422 BasicSourceMapConsumer.prototype._parseMappings =
423   function SourceMapConsumer_parseMappings(aStr, aSourceRoot) {
424     var generatedLine = 1;
425     var previousGeneratedColumn = 0;
426     var previousOriginalLine = 0;
427     var previousOriginalColumn = 0;
428     var previousSource = 0;
429     var previousName = 0;
430     var length = aStr.length;
431     var index = 0;
432     var cachedSegments = {};
433     var temp = {};
434     var originalMappings = [];
435     var generatedMappings = [];
436     var mapping, str, segment, end, value;
437
438     while (index < length) {
439       if (aStr.charAt(index) === ';') {
440         generatedLine++;
441         index++;
442         previousGeneratedColumn = 0;
443       }
444       else if (aStr.charAt(index) === ',') {
445         index++;
446       }
447       else {
448         mapping = new Mapping();
449         mapping.generatedLine = generatedLine;
450
451         // Because each offset is encoded relative to the previous one,
452         // many segments often have the same encoding. We can exploit this
453         // fact by caching the parsed variable length fields of each segment,
454         // allowing us to avoid a second parse if we encounter the same
455         // segment again.
456         for (end = index; end < length; end++) {
457           if (this._charIsMappingSeparator(aStr, end)) {
458             break;
459           }
460         }
461         str = aStr.slice(index, end);
462
463         segment = cachedSegments[str];
464         if (segment) {
465           index += str.length;
466         } else {
467           segment = [];
468           while (index < end) {
469             base64VLQ.decode(aStr, index, temp);
470             value = temp.value;
471             index = temp.rest;
472             segment.push(value);
473           }
474
475           if (segment.length === 2) {
476             throw new Error('Found a source, but no line and column');
477           }
478
479           if (segment.length === 3) {
480             throw new Error('Found a source and line, but no column');
481           }
482
483           cachedSegments[str] = segment;
484         }
485
486         // Generated column.
487         mapping.generatedColumn = previousGeneratedColumn + segment[0];
488         previousGeneratedColumn = mapping.generatedColumn;
489
490         if (segment.length > 1) {
491           // Original source.
492           mapping.source = previousSource + segment[1];
493           previousSource += segment[1];
494
495           // Original line.
496           mapping.originalLine = previousOriginalLine + segment[2];
497           previousOriginalLine = mapping.originalLine;
498           // Lines are stored 0-based
499           mapping.originalLine += 1;
500
501           // Original column.
502           mapping.originalColumn = previousOriginalColumn + segment[3];
503           previousOriginalColumn = mapping.originalColumn;
504
505           if (segment.length > 4) {
506             // Original name.
507             mapping.name = previousName + segment[4];
508             previousName += segment[4];
509           }
510         }
511
512         generatedMappings.push(mapping);
513         if (typeof mapping.originalLine === 'number') {
514           originalMappings.push(mapping);
515         }
516       }
517     }
518
519     quickSort(generatedMappings, util.compareByGeneratedPositionsDeflated);
520     this.__generatedMappings = generatedMappings;
521
522     quickSort(originalMappings, util.compareByOriginalPositions);
523     this.__originalMappings = originalMappings;
524   };
525
526 /**
527  * Find the mapping that best matches the hypothetical "needle" mapping that
528  * we are searching for in the given "haystack" of mappings.
529  */
530 BasicSourceMapConsumer.prototype._findMapping =
531   function SourceMapConsumer_findMapping(aNeedle, aMappings, aLineName,
532                                          aColumnName, aComparator, aBias) {
533     // To return the position we are searching for, we must first find the
534     // mapping for the given position and then return the opposite position it
535     // points to. Because the mappings are sorted, we can use binary search to
536     // find the best mapping.
537
538     if (aNeedle[aLineName] <= 0) {
539       throw new TypeError('Line must be greater than or equal to 1, got '
540                           + aNeedle[aLineName]);
541     }
542     if (aNeedle[aColumnName] < 0) {
543       throw new TypeError('Column must be greater than or equal to 0, got '
544                           + aNeedle[aColumnName]);
545     }
546
547     return binarySearch.search(aNeedle, aMappings, aComparator, aBias);
548   };
549
550 /**
551  * Compute the last column for each generated mapping. The last column is
552  * inclusive.
553  */
554 BasicSourceMapConsumer.prototype.computeColumnSpans =
555   function SourceMapConsumer_computeColumnSpans() {
556     for (var index = 0; index < this._generatedMappings.length; ++index) {
557       var mapping = this._generatedMappings[index];
558
559       // Mappings do not contain a field for the last generated columnt. We
560       // can come up with an optimistic estimate, however, by assuming that
561       // mappings are contiguous (i.e. given two consecutive mappings, the
562       // first mapping ends where the second one starts).
563       if (index + 1 < this._generatedMappings.length) {
564         var nextMapping = this._generatedMappings[index + 1];
565
566         if (mapping.generatedLine === nextMapping.generatedLine) {
567           mapping.lastGeneratedColumn = nextMapping.generatedColumn - 1;
568           continue;
569         }
570       }
571
572       // The last mapping for each line spans the entire line.
573       mapping.lastGeneratedColumn = Infinity;
574     }
575   };
576
577 /**
578  * Returns the original source, line, and column information for the generated
579  * source's line and column positions provided. The only argument is an object
580  * with the following properties:
581  *
582  *   - line: The line number in the generated source.
583  *   - column: The column number in the generated source.
584  *   - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or
585  *     'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the
586  *     closest element that is smaller than or greater than the one we are
587  *     searching for, respectively, if the exact element cannot be found.
588  *     Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'.
589  *
590  * and an object is returned with the following properties:
591  *
592  *   - source: The original source file, or null.
593  *   - line: The line number in the original source, or null.
594  *   - column: The column number in the original source, or null.
595  *   - name: The original identifier, or null.
596  */
597 BasicSourceMapConsumer.prototype.originalPositionFor =
598   function SourceMapConsumer_originalPositionFor(aArgs) {
599     var needle = {
600       generatedLine: util.getArg(aArgs, 'line'),
601       generatedColumn: util.getArg(aArgs, 'column')
602     };
603
604     var index = this._findMapping(
605       needle,
606       this._generatedMappings,
607       "generatedLine",
608       "generatedColumn",
609       util.compareByGeneratedPositionsDeflated,
610       util.getArg(aArgs, 'bias', SourceMapConsumer.GREATEST_LOWER_BOUND)
611     );
612
613     if (index >= 0) {
614       var mapping = this._generatedMappings[index];
615
616       if (mapping.generatedLine === needle.generatedLine) {
617         var source = util.getArg(mapping, 'source', null);
618         if (source !== null) {
619           source = this._sources.at(source);
620           if (this.sourceRoot != null) {
621             source = util.join(this.sourceRoot, source);
622           }
623         }
624         var name = util.getArg(mapping, 'name', null);
625         if (name !== null) {
626           name = this._names.at(name);
627         }
628         return {
629           source: source,
630           line: util.getArg(mapping, 'originalLine', null),
631           column: util.getArg(mapping, 'originalColumn', null),
632           name: name
633         };
634       }
635     }
636
637     return {
638       source: null,
639       line: null,
640       column: null,
641       name: null
642     };
643   };
644
645 /**
646  * Return true if we have the source content for every source in the source
647  * map, false otherwise.
648  */
649 BasicSourceMapConsumer.prototype.hasContentsOfAllSources =
650   function BasicSourceMapConsumer_hasContentsOfAllSources() {
651     if (!this.sourcesContent) {
652       return false;
653     }
654     return this.sourcesContent.length >= this._sources.size() &&
655       !this.sourcesContent.some(function (sc) { return sc == null; });
656   };
657
658 /**
659  * Returns the original source content. The only argument is the url of the
660  * original source file. Returns null if no original source content is
661  * available.
662  */
663 BasicSourceMapConsumer.prototype.sourceContentFor =
664   function SourceMapConsumer_sourceContentFor(aSource, nullOnMissing) {
665     if (!this.sourcesContent) {
666       return null;
667     }
668
669     if (this.sourceRoot != null) {
670       aSource = util.relative(this.sourceRoot, aSource);
671     }
672
673     if (this._sources.has(aSource)) {
674       return this.sourcesContent[this._sources.indexOf(aSource)];
675     }
676
677     var url;
678     if (this.sourceRoot != null
679         && (url = util.urlParse(this.sourceRoot))) {
680       // XXX: file:// URIs and absolute paths lead to unexpected behavior for
681       // many users. We can help them out when they expect file:// URIs to
682       // behave like it would if they were running a local HTTP server. See
683       // https://bugzilla.mozilla.org/show_bug.cgi?id=885597.
684       var fileUriAbsPath = aSource.replace(/^file:\/\//, "");
685       if (url.scheme == "file"
686           && this._sources.has(fileUriAbsPath)) {
687         return this.sourcesContent[this._sources.indexOf(fileUriAbsPath)]
688       }
689
690       if ((!url.path || url.path == "/")
691           && this._sources.has("/" + aSource)) {
692         return this.sourcesContent[this._sources.indexOf("/" + aSource)];
693       }
694     }
695
696     // This function is used recursively from
697     // IndexedSourceMapConsumer.prototype.sourceContentFor. In that case, we
698     // don't want to throw if we can't find the source - we just want to
699     // return null, so we provide a flag to exit gracefully.
700     if (nullOnMissing) {
701       return null;
702     }
703     else {
704       throw new Error('"' + aSource + '" is not in the SourceMap.');
705     }
706   };
707
708 /**
709  * Returns the generated line and column information for the original source,
710  * line, and column positions provided. The only argument is an object with
711  * the following properties:
712  *
713  *   - source: The filename of the original source.
714  *   - line: The line number in the original source.
715  *   - column: The column number in the original source.
716  *   - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or
717  *     'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the
718  *     closest element that is smaller than or greater than the one we are
719  *     searching for, respectively, if the exact element cannot be found.
720  *     Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'.
721  *
722  * and an object is returned with the following properties:
723  *
724  *   - line: The line number in the generated source, or null.
725  *   - column: The column number in the generated source, or null.
726  */
727 BasicSourceMapConsumer.prototype.generatedPositionFor =
728   function SourceMapConsumer_generatedPositionFor(aArgs) {
729     var source = util.getArg(aArgs, 'source');
730     if (this.sourceRoot != null) {
731       source = util.relative(this.sourceRoot, source);
732     }
733     if (!this._sources.has(source)) {
734       return {
735         line: null,
736         column: null,
737         lastColumn: null
738       };
739     }
740     source = this._sources.indexOf(source);
741
742     var needle = {
743       source: source,
744       originalLine: util.getArg(aArgs, 'line'),
745       originalColumn: util.getArg(aArgs, 'column')
746     };
747
748     var index = this._findMapping(
749       needle,
750       this._originalMappings,
751       "originalLine",
752       "originalColumn",
753       util.compareByOriginalPositions,
754       util.getArg(aArgs, 'bias', SourceMapConsumer.GREATEST_LOWER_BOUND)
755     );
756
757     if (index >= 0) {
758       var mapping = this._originalMappings[index];
759
760       if (mapping.source === needle.source) {
761         return {
762           line: util.getArg(mapping, 'generatedLine', null),
763           column: util.getArg(mapping, 'generatedColumn', null),
764           lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null)
765         };
766       }
767     }
768
769     return {
770       line: null,
771       column: null,
772       lastColumn: null
773     };
774   };
775
776 exports.BasicSourceMapConsumer = BasicSourceMapConsumer;
777
778 /**
779  * An IndexedSourceMapConsumer instance represents a parsed source map which
780  * we can query for information. It differs from BasicSourceMapConsumer in
781  * that it takes "indexed" source maps (i.e. ones with a "sections" field) as
782  * input.
783  *
784  * The only parameter is a raw source map (either as a JSON string, or already
785  * parsed to an object). According to the spec for indexed source maps, they
786  * have the following attributes:
787  *
788  *   - version: Which version of the source map spec this map is following.
789  *   - file: Optional. The generated file this source map is associated with.
790  *   - sections: A list of section definitions.
791  *
792  * Each value under the "sections" field has two fields:
793  *   - offset: The offset into the original specified at which this section
794  *       begins to apply, defined as an object with a "line" and "column"
795  *       field.
796  *   - map: A source map definition. This source map could also be indexed,
797  *       but doesn't have to be.
798  *
799  * Instead of the "map" field, it's also possible to have a "url" field
800  * specifying a URL to retrieve a source map from, but that's currently
801  * unsupported.
802  *
803  * Here's an example source map, taken from the source map spec[0], but
804  * modified to omit a section which uses the "url" field.
805  *
806  *  {
807  *    version : 3,
808  *    file: "app.js",
809  *    sections: [{
810  *      offset: {line:100, column:10},
811  *      map: {
812  *        version : 3,
813  *        file: "section.js",
814  *        sources: ["foo.js", "bar.js"],
815  *        names: ["src", "maps", "are", "fun"],
816  *        mappings: "AAAA,E;;ABCDE;"
817  *      }
818  *    }],
819  *  }
820  *
821  * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#heading=h.535es3xeprgt
822  */
823 function IndexedSourceMapConsumer(aSourceMap) {
824   var sourceMap = aSourceMap;
825   if (typeof aSourceMap === 'string') {
826     sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, ''));
827   }
828
829   var version = util.getArg(sourceMap, 'version');
830   var sections = util.getArg(sourceMap, 'sections');
831
832   if (version != this._version) {
833     throw new Error('Unsupported version: ' + version);
834   }
835
836   this._sources = new ArraySet();
837   this._names = new ArraySet();
838
839   var lastOffset = {
840     line: -1,
841     column: 0
842   };
843   this._sections = sections.map(function (s) {
844     if (s.url) {
845       // The url field will require support for asynchronicity.
846       // See https://github.com/mozilla/source-map/issues/16
847       throw new Error('Support for url field in sections not implemented.');
848     }
849     var offset = util.getArg(s, 'offset');
850     var offsetLine = util.getArg(offset, 'line');
851     var offsetColumn = util.getArg(offset, 'column');
852
853     if (offsetLine < lastOffset.line ||
854         (offsetLine === lastOffset.line && offsetColumn < lastOffset.column)) {
855       throw new Error('Section offsets must be ordered and non-overlapping.');
856     }
857     lastOffset = offset;
858
859     return {
860       generatedOffset: {
861         // The offset fields are 0-based, but we use 1-based indices when
862         // encoding/decoding from VLQ.
863         generatedLine: offsetLine + 1,
864         generatedColumn: offsetColumn + 1
865       },
866       consumer: new SourceMapConsumer(util.getArg(s, 'map'))
867     }
868   });
869 }
870
871 IndexedSourceMapConsumer.prototype = Object.create(SourceMapConsumer.prototype);
872 IndexedSourceMapConsumer.prototype.constructor = SourceMapConsumer;
873
874 /**
875  * The version of the source mapping spec that we are consuming.
876  */
877 IndexedSourceMapConsumer.prototype._version = 3;
878
879 /**
880  * The list of original sources.
881  */
882 Object.defineProperty(IndexedSourceMapConsumer.prototype, 'sources', {
883   get: function () {
884     var sources = [];
885     for (var i = 0; i < this._sections.length; i++) {
886       for (var j = 0; j < this._sections[i].consumer.sources.length; j++) {
887         sources.push(this._sections[i].consumer.sources[j]);
888       }
889     }
890     return sources;
891   }
892 });
893
894 /**
895  * Returns the original source, line, and column information for the generated
896  * source's line and column positions provided. The only argument is an object
897  * with the following properties:
898  *
899  *   - line: The line number in the generated source.
900  *   - column: The column number in the generated source.
901  *
902  * and an object is returned with the following properties:
903  *
904  *   - source: The original source file, or null.
905  *   - line: The line number in the original source, or null.
906  *   - column: The column number in the original source, or null.
907  *   - name: The original identifier, or null.
908  */
909 IndexedSourceMapConsumer.prototype.originalPositionFor =
910   function IndexedSourceMapConsumer_originalPositionFor(aArgs) {
911     var needle = {
912       generatedLine: util.getArg(aArgs, 'line'),
913       generatedColumn: util.getArg(aArgs, 'column')
914     };
915
916     // Find the section containing the generated position we're trying to map
917     // to an original position.
918     var sectionIndex = binarySearch.search(needle, this._sections,
919       function(needle, section) {
920         var cmp = needle.generatedLine - section.generatedOffset.generatedLine;
921         if (cmp) {
922           return cmp;
923         }
924
925         return (needle.generatedColumn -
926                 section.generatedOffset.generatedColumn);
927       });
928     var section = this._sections[sectionIndex];
929
930     if (!section) {
931       return {
932         source: null,
933         line: null,
934         column: null,
935         name: null
936       };
937     }
938
939     return section.consumer.originalPositionFor({
940       line: needle.generatedLine -
941         (section.generatedOffset.generatedLine - 1),
942       column: needle.generatedColumn -
943         (section.generatedOffset.generatedLine === needle.generatedLine
944          ? section.generatedOffset.generatedColumn - 1
945          : 0),
946       bias: aArgs.bias
947     });
948   };
949
950 /**
951  * Return true if we have the source content for every source in the source
952  * map, false otherwise.
953  */
954 IndexedSourceMapConsumer.prototype.hasContentsOfAllSources =
955   function IndexedSourceMapConsumer_hasContentsOfAllSources() {
956     return this._sections.every(function (s) {
957       return s.consumer.hasContentsOfAllSources();
958     });
959   };
960
961 /**
962  * Returns the original source content. The only argument is the url of the
963  * original source file. Returns null if no original source content is
964  * available.
965  */
966 IndexedSourceMapConsumer.prototype.sourceContentFor =
967   function IndexedSourceMapConsumer_sourceContentFor(aSource, nullOnMissing) {
968     for (var i = 0; i < this._sections.length; i++) {
969       var section = this._sections[i];
970
971       var content = section.consumer.sourceContentFor(aSource, true);
972       if (content) {
973         return content;
974       }
975     }
976     if (nullOnMissing) {
977       return null;
978     }
979     else {
980       throw new Error('"' + aSource + '" is not in the SourceMap.');
981     }
982   };
983
984 /**
985  * Returns the generated line and column information for the original source,
986  * line, and column positions provided. The only argument is an object with
987  * the following properties:
988  *
989  *   - source: The filename of the original source.
990  *   - line: The line number in the original source.
991  *   - column: The column number in the original source.
992  *
993  * and an object is returned with the following properties:
994  *
995  *   - line: The line number in the generated source, or null.
996  *   - column: The column number in the generated source, or null.
997  */
998 IndexedSourceMapConsumer.prototype.generatedPositionFor =
999   function IndexedSourceMapConsumer_generatedPositionFor(aArgs) {
1000     for (var i = 0; i < this._sections.length; i++) {
1001       var section = this._sections[i];
1002
1003       // Only consider this section if the requested source is in the list of
1004       // sources of the consumer.
1005       if (section.consumer.sources.indexOf(util.getArg(aArgs, 'source')) === -1) {
1006         continue;
1007       }
1008       var generatedPosition = section.consumer.generatedPositionFor(aArgs);
1009       if (generatedPosition) {
1010         var ret = {
1011           line: generatedPosition.line +
1012             (section.generatedOffset.generatedLine - 1),
1013           column: generatedPosition.column +
1014             (section.generatedOffset.generatedLine === generatedPosition.line
1015              ? section.generatedOffset.generatedColumn - 1
1016              : 0)
1017         };
1018         return ret;
1019       }
1020     }
1021
1022     return {
1023       line: null,
1024       column: null
1025     };
1026   };
1027
1028 /**
1029  * Parse the mappings in a string in to a data structure which we can easily
1030  * query (the ordered arrays in the `this.__generatedMappings` and
1031  * `this.__originalMappings` properties).
1032  */
1033 IndexedSourceMapConsumer.prototype._parseMappings =
1034   function IndexedSourceMapConsumer_parseMappings(aStr, aSourceRoot) {
1035     this.__generatedMappings = [];
1036     this.__originalMappings = [];
1037     for (var i = 0; i < this._sections.length; i++) {
1038       var section = this._sections[i];
1039       var sectionMappings = section.consumer._generatedMappings;
1040       for (var j = 0; j < sectionMappings.length; j++) {
1041         var mapping = sectionMappings[j];
1042
1043         var source = section.consumer._sources.at(mapping.source);
1044         if (section.consumer.sourceRoot !== null) {
1045           source = util.join(section.consumer.sourceRoot, source);
1046         }
1047         this._sources.add(source);
1048         source = this._sources.indexOf(source);
1049
1050         var name = section.consumer._names.at(mapping.name);
1051         this._names.add(name);
1052         name = this._names.indexOf(name);
1053
1054         // The mappings coming from the consumer for the section have
1055         // generated positions relative to the start of the section, so we
1056         // need to offset them to be relative to the start of the concatenated
1057         // generated file.
1058         var adjustedMapping = {
1059           source: source,
1060           generatedLine: mapping.generatedLine +
1061             (section.generatedOffset.generatedLine - 1),
1062           generatedColumn: mapping.generatedColumn +
1063             (section.generatedOffset.generatedLine === mapping.generatedLine
1064             ? section.generatedOffset.generatedColumn - 1
1065             : 0),
1066           originalLine: mapping.originalLine,
1067           originalColumn: mapping.originalColumn,
1068           name: name
1069         };
1070
1071         this.__generatedMappings.push(adjustedMapping);
1072         if (typeof adjustedMapping.originalLine === 'number') {
1073           this.__originalMappings.push(adjustedMapping);
1074         }
1075       }
1076     }
1077
1078     quickSort(this.__generatedMappings, util.compareByGeneratedPositionsDeflated);
1079     quickSort(this.__originalMappings, util.compareByOriginalPositions);
1080   };
1081
1082 exports.IndexedSourceMapConsumer = IndexedSourceMapConsumer;