1 /* -*- Mode: js; js-indent-level: 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
7 if (typeof define !== 'function') {
8 var define = require('amdefine')(module, require);
10 define(function (require, exports, module) {
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 var quickSort = require('./quick-sort').quickSort;
18 function SourceMapConsumer(aSourceMap) {
19 var sourceMap = aSourceMap;
20 if (typeof aSourceMap === 'string') {
21 sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, ''));
24 return sourceMap.sections != null
25 ? new IndexedSourceMapConsumer(sourceMap)
26 : new BasicSourceMapConsumer(sourceMap);
29 SourceMapConsumer.fromSourceMap = function(aSourceMap) {
30 return BasicSourceMapConsumer.fromSourceMap(aSourceMap);
34 * The version of the source mapping spec that we are consuming.
36 SourceMapConsumer.prototype._version = 3;
38 // `__generatedMappings` and `__originalMappings` are arrays that hold the
39 // parsed mapping coordinates from the source map's "mappings" attribute. They
40 // are lazily instantiated, accessed via the `_generatedMappings` and
41 // `_originalMappings` getters respectively, and we only parse the mappings
42 // and create these arrays once queried for a source location. We jump through
43 // these hoops because there can be many thousands of mappings, and parsing
44 // them is expensive, so we only want to do it if we must.
46 // Each object in the arrays is of the form:
49 // generatedLine: The line number in the generated code,
50 // generatedColumn: The column number in the generated code,
51 // source: The path to the original source file that generated this
53 // originalLine: The line number in the original source that
54 // corresponds to this chunk of generated code,
55 // originalColumn: The column number in the original source that
56 // corresponds to this chunk of generated code,
57 // name: The name of the original symbol which generated this chunk of
61 // All properties except for `generatedLine` and `generatedColumn` can be
64 // `_generatedMappings` is ordered by the generated positions.
66 // `_originalMappings` is ordered by the original positions.
68 SourceMapConsumer.prototype.__generatedMappings = null;
69 Object.defineProperty(SourceMapConsumer.prototype, '_generatedMappings', {
71 if (!this.__generatedMappings) {
72 this._parseMappings(this._mappings, this.sourceRoot);
75 return this.__generatedMappings;
79 SourceMapConsumer.prototype.__originalMappings = null;
80 Object.defineProperty(SourceMapConsumer.prototype, '_originalMappings', {
82 if (!this.__originalMappings) {
83 this._parseMappings(this._mappings, this.sourceRoot);
86 return this.__originalMappings;
90 SourceMapConsumer.prototype._charIsMappingSeparator =
91 function SourceMapConsumer_charIsMappingSeparator(aStr, index) {
92 var c = aStr.charAt(index);
93 return c === ";" || c === ",";
97 * Parse the mappings in a string in to a data structure which we can easily
98 * query (the ordered arrays in the `this.__generatedMappings` and
99 * `this.__originalMappings` properties).
101 SourceMapConsumer.prototype._parseMappings =
102 function SourceMapConsumer_parseMappings(aStr, aSourceRoot) {
103 throw new Error("Subclasses must implement _parseMappings");
106 SourceMapConsumer.GENERATED_ORDER = 1;
107 SourceMapConsumer.ORIGINAL_ORDER = 2;
109 SourceMapConsumer.GREATEST_LOWER_BOUND = 1;
110 SourceMapConsumer.LEAST_UPPER_BOUND = 2;
113 * Iterate over each mapping between an original source/line/column and a
114 * generated line/column in this source map.
116 * @param Function aCallback
117 * The function that is called with each mapping.
118 * @param Object aContext
119 * Optional. If specified, this object will be the value of `this` every
120 * time that `aCallback` is called.
122 * Either `SourceMapConsumer.GENERATED_ORDER` or
123 * `SourceMapConsumer.ORIGINAL_ORDER`. Specifies whether you want to
124 * iterate over the mappings sorted by the generated file's line/column
125 * order or the original's source/line/column order, respectively. Defaults to
126 * `SourceMapConsumer.GENERATED_ORDER`.
128 SourceMapConsumer.prototype.eachMapping =
129 function SourceMapConsumer_eachMapping(aCallback, aContext, aOrder) {
130 var context = aContext || null;
131 var order = aOrder || SourceMapConsumer.GENERATED_ORDER;
135 case SourceMapConsumer.GENERATED_ORDER:
136 mappings = this._generatedMappings;
138 case SourceMapConsumer.ORIGINAL_ORDER:
139 mappings = this._originalMappings;
142 throw new Error("Unknown order of iteration.");
145 var sourceRoot = this.sourceRoot;
146 mappings.map(function (mapping) {
147 var source = mapping.source === null ? null : this._sources.at(mapping.source);
148 if (source != null && sourceRoot != null) {
149 source = util.join(sourceRoot, source);
153 generatedLine: mapping.generatedLine,
154 generatedColumn: mapping.generatedColumn,
155 originalLine: mapping.originalLine,
156 originalColumn: mapping.originalColumn,
157 name: mapping.name === null ? null : this._names.at(mapping.name)
159 }, this).forEach(aCallback, context);
163 * Returns all generated line and column information for the original source,
164 * line, and column provided. If no column is provided, returns all mappings
165 * corresponding to a either the line we are searching for or the next
166 * closest line that has any mappings. Otherwise, returns all mappings
167 * corresponding to the given line and either the column we are searching for
168 * or the next closest column that has any offsets.
170 * The only argument is an object with the following properties:
172 * - source: The filename of the original source.
173 * - line: The line number in the original source.
174 * - column: Optional. the column number in the original source.
176 * and an array of objects is returned, each with the following properties:
178 * - line: The line number in the generated source, or null.
179 * - column: The column number in the generated source, or null.
181 SourceMapConsumer.prototype.allGeneratedPositionsFor =
182 function SourceMapConsumer_allGeneratedPositionsFor(aArgs) {
183 var line = util.getArg(aArgs, 'line');
185 // When there is no exact match, BasicSourceMapConsumer.prototype._findMapping
186 // returns the index of the closest mapping less than the needle. By
187 // setting needle.originalColumn to 0, we thus find the last mapping for
188 // the given line, provided such a mapping exists.
190 source: util.getArg(aArgs, 'source'),
192 originalColumn: util.getArg(aArgs, 'column', 0)
195 if (this.sourceRoot != null) {
196 needle.source = util.relative(this.sourceRoot, needle.source);
198 if (!this._sources.has(needle.source)) {
201 needle.source = this._sources.indexOf(needle.source);
205 var index = this._findMapping(needle,
206 this._originalMappings,
209 util.compareByOriginalPositions,
210 binarySearch.LEAST_UPPER_BOUND);
212 var mapping = this._originalMappings[index];
214 if (aArgs.column === undefined) {
215 var originalLine = mapping.originalLine;
217 // Iterate until either we run out of mappings, or we run into
218 // a mapping for a different line than the one we found. Since
219 // mappings are sorted, this is guaranteed to find all mappings for
220 // the line we found.
221 while (mapping && mapping.originalLine === originalLine) {
223 line: util.getArg(mapping, 'generatedLine', null),
224 column: util.getArg(mapping, 'generatedColumn', null),
225 lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null)
228 mapping = this._originalMappings[++index];
231 var originalColumn = mapping.originalColumn;
233 // Iterate until either we run out of mappings, or we run into
234 // a mapping for a different line than the one we were searching for.
235 // Since mappings are sorted, this is guaranteed to find all mappings for
236 // the line we are searching for.
238 mapping.originalLine === line &&
239 mapping.originalColumn == originalColumn) {
241 line: util.getArg(mapping, 'generatedLine', null),
242 column: util.getArg(mapping, 'generatedColumn', null),
243 lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null)
246 mapping = this._originalMappings[++index];
254 exports.SourceMapConsumer = SourceMapConsumer;
257 * A BasicSourceMapConsumer instance represents a parsed source map which we can
258 * query for information about the original file positions by giving it a file
259 * position in the generated source.
261 * The only parameter is the raw source map (either as a JSON string, or
262 * already parsed to an object). According to the spec, source maps have the
263 * following attributes:
265 * - version: Which version of the source map spec this map is following.
266 * - sources: An array of URLs to the original source files.
267 * - names: An array of identifiers which can be referrenced by individual mappings.
268 * - sourceRoot: Optional. The URL root from which all sources are relative.
269 * - sourcesContent: Optional. An array of contents of the original source files.
270 * - mappings: A string of base64 VLQs which contain the actual mappings.
271 * - file: Optional. The generated file this source map is associated with.
273 * Here is an example source map, taken from the source map spec[0]:
279 * sources: ["foo.js", "bar.js"],
280 * names: ["src", "maps", "are", "fun"],
281 * mappings: "AA,AB;;ABCDE;"
284 * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit?pli=1#
286 function BasicSourceMapConsumer(aSourceMap) {
287 var sourceMap = aSourceMap;
288 if (typeof aSourceMap === 'string') {
289 sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, ''));
292 var version = util.getArg(sourceMap, 'version');
293 var sources = util.getArg(sourceMap, 'sources');
294 // Sass 3.3 leaves out the 'names' array, so we deviate from the spec (which
295 // requires the array) to play nice here.
296 var names = util.getArg(sourceMap, 'names', []);
297 var sourceRoot = util.getArg(sourceMap, 'sourceRoot', null);
298 var sourcesContent = util.getArg(sourceMap, 'sourcesContent', null);
299 var mappings = util.getArg(sourceMap, 'mappings');
300 var file = util.getArg(sourceMap, 'file', null);
302 // Once again, Sass deviates from the spec and supplies the version as a
303 // string rather than a number, so we use loose equality checking here.
304 if (version != this._version) {
305 throw new Error('Unsupported version: ' + version);
308 // Some source maps produce relative source paths like "./foo.js" instead of
309 // "foo.js". Normalize these first so that future comparisons will succeed.
310 // See bugzil.la/1090768.
311 sources = sources.map(util.normalize);
313 // Pass `true` below to allow duplicate names and sources. While source maps
314 // are intended to be compressed and deduplicated, the TypeScript compiler
315 // sometimes generates source maps with duplicates in them. See Github issue
316 // #72 and bugzil.la/889492.
317 this._names = ArraySet.fromArray(names, true);
318 this._sources = ArraySet.fromArray(sources, true);
320 this.sourceRoot = sourceRoot;
321 this.sourcesContent = sourcesContent;
322 this._mappings = mappings;
326 BasicSourceMapConsumer.prototype = Object.create(SourceMapConsumer.prototype);
327 BasicSourceMapConsumer.prototype.consumer = SourceMapConsumer;
330 * Create a BasicSourceMapConsumer from a SourceMapGenerator.
332 * @param SourceMapGenerator aSourceMap
333 * The source map that will be consumed.
334 * @returns BasicSourceMapConsumer
336 BasicSourceMapConsumer.fromSourceMap =
337 function SourceMapConsumer_fromSourceMap(aSourceMap) {
338 var smc = Object.create(BasicSourceMapConsumer.prototype);
340 var names = smc._names = ArraySet.fromArray(aSourceMap._names.toArray(), true);
341 var sources = smc._sources = ArraySet.fromArray(aSourceMap._sources.toArray(), true);
342 smc.sourceRoot = aSourceMap._sourceRoot;
343 smc.sourcesContent = aSourceMap._generateSourcesContent(smc._sources.toArray(),
345 smc.file = aSourceMap._file;
347 // Because we are modifying the entries (by converting string sources and
348 // names to indices into the sources and names ArraySets), we have to make
349 // a copy of the entry or else bad things happen. Shared mutable state
350 // strikes again! See github issue #191.
352 var generatedMappings = aSourceMap._mappings.toArray().slice();
353 var destGeneratedMappings = smc.__generatedMappings = [];
354 var destOriginalMappings = smc.__originalMappings = [];
356 for (var i = 0, length = generatedMappings.length; i < length; i++) {
357 var srcMapping = generatedMappings[i];
358 var destMapping = new Mapping;
359 destMapping.generatedLine = srcMapping.generatedLine;
360 destMapping.generatedColumn = srcMapping.generatedColumn;
362 if (srcMapping.source) {
363 destMapping.source = sources.indexOf(srcMapping.source);
364 destMapping.originalLine = srcMapping.originalLine;
365 destMapping.originalColumn = srcMapping.originalColumn;
367 if (srcMapping.name) {
368 destMapping.name = names.indexOf(srcMapping.name);
371 destOriginalMappings.push(destMapping);
374 destGeneratedMappings.push(destMapping);
377 quickSort(smc.__originalMappings, util.compareByOriginalPositions);
383 * The version of the source mapping spec that we are consuming.
385 BasicSourceMapConsumer.prototype._version = 3;
388 * The list of original sources.
390 Object.defineProperty(BasicSourceMapConsumer.prototype, 'sources', {
392 return this._sources.toArray().map(function (s) {
393 return this.sourceRoot != null ? util.join(this.sourceRoot, s) : s;
399 * Provide the JIT with a nice shape / hidden class.
402 this.generatedLine = 0;
403 this.generatedColumn = 0;
405 this.originalLine = null;
406 this.originalColumn = null;
411 * Parse the mappings in a string in to a data structure which we can easily
412 * query (the ordered arrays in the `this.__generatedMappings` and
413 * `this.__originalMappings` properties).
415 BasicSourceMapConsumer.prototype._parseMappings =
416 function SourceMapConsumer_parseMappings(aStr, aSourceRoot) {
417 var generatedLine = 1;
418 var previousGeneratedColumn = 0;
419 var previousOriginalLine = 0;
420 var previousOriginalColumn = 0;
421 var previousSource = 0;
422 var previousName = 0;
423 var length = aStr.length;
425 var cachedSegments = {};
427 var originalMappings = [];
428 var generatedMappings = [];
429 var mapping, str, segment, end, value;
431 while (index < length) {
432 if (aStr.charAt(index) === ';') {
435 previousGeneratedColumn = 0;
437 else if (aStr.charAt(index) === ',') {
441 mapping = new Mapping();
442 mapping.generatedLine = generatedLine;
444 // Because each offset is encoded relative to the previous one,
445 // many segments often have the same encoding. We can exploit this
446 // fact by caching the parsed variable length fields of each segment,
447 // allowing us to avoid a second parse if we encounter the same
449 for (end = index; end < length; end++) {
450 if (this._charIsMappingSeparator(aStr, end)) {
454 str = aStr.slice(index, end);
456 segment = cachedSegments[str];
461 while (index < end) {
462 base64VLQ.decode(aStr, index, temp);
468 if (segment.length === 2) {
469 throw new Error('Found a source, but no line and column');
472 if (segment.length === 3) {
473 throw new Error('Found a source and line, but no column');
476 cachedSegments[str] = segment;
480 mapping.generatedColumn = previousGeneratedColumn + segment[0];
481 previousGeneratedColumn = mapping.generatedColumn;
483 if (segment.length > 1) {
485 mapping.source = previousSource + segment[1];
486 previousSource += segment[1];
489 mapping.originalLine = previousOriginalLine + segment[2];
490 previousOriginalLine = mapping.originalLine;
491 // Lines are stored 0-based
492 mapping.originalLine += 1;
495 mapping.originalColumn = previousOriginalColumn + segment[3];
496 previousOriginalColumn = mapping.originalColumn;
498 if (segment.length > 4) {
500 mapping.name = previousName + segment[4];
501 previousName += segment[4];
505 generatedMappings.push(mapping);
506 if (typeof mapping.originalLine === 'number') {
507 originalMappings.push(mapping);
512 quickSort(generatedMappings, util.compareByGeneratedPositionsDeflated);
513 this.__generatedMappings = generatedMappings;
515 quickSort(originalMappings, util.compareByOriginalPositions);
516 this.__originalMappings = originalMappings;
520 * Find the mapping that best matches the hypothetical "needle" mapping that
521 * we are searching for in the given "haystack" of mappings.
523 BasicSourceMapConsumer.prototype._findMapping =
524 function SourceMapConsumer_findMapping(aNeedle, aMappings, aLineName,
525 aColumnName, aComparator, aBias) {
526 // To return the position we are searching for, we must first find the
527 // mapping for the given position and then return the opposite position it
528 // points to. Because the mappings are sorted, we can use binary search to
529 // find the best mapping.
531 if (aNeedle[aLineName] <= 0) {
532 throw new TypeError('Line must be greater than or equal to 1, got '
533 + aNeedle[aLineName]);
535 if (aNeedle[aColumnName] < 0) {
536 throw new TypeError('Column must be greater than or equal to 0, got '
537 + aNeedle[aColumnName]);
540 return binarySearch.search(aNeedle, aMappings, aComparator, aBias);
544 * Compute the last column for each generated mapping. The last column is
547 BasicSourceMapConsumer.prototype.computeColumnSpans =
548 function SourceMapConsumer_computeColumnSpans() {
549 for (var index = 0; index < this._generatedMappings.length; ++index) {
550 var mapping = this._generatedMappings[index];
552 // Mappings do not contain a field for the last generated columnt. We
553 // can come up with an optimistic estimate, however, by assuming that
554 // mappings are contiguous (i.e. given two consecutive mappings, the
555 // first mapping ends where the second one starts).
556 if (index + 1 < this._generatedMappings.length) {
557 var nextMapping = this._generatedMappings[index + 1];
559 if (mapping.generatedLine === nextMapping.generatedLine) {
560 mapping.lastGeneratedColumn = nextMapping.generatedColumn - 1;
565 // The last mapping for each line spans the entire line.
566 mapping.lastGeneratedColumn = Infinity;
571 * Returns the original source, line, and column information for the generated
572 * source's line and column positions provided. The only argument is an object
573 * with the following properties:
575 * - line: The line number in the generated source.
576 * - column: The column number in the generated source.
577 * - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or
578 * 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the
579 * closest element that is smaller than or greater than the one we are
580 * searching for, respectively, if the exact element cannot be found.
581 * Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'.
583 * and an object is returned with the following properties:
585 * - source: The original source file, or null.
586 * - line: The line number in the original source, or null.
587 * - column: The column number in the original source, or null.
588 * - name: The original identifier, or null.
590 BasicSourceMapConsumer.prototype.originalPositionFor =
591 function SourceMapConsumer_originalPositionFor(aArgs) {
593 generatedLine: util.getArg(aArgs, 'line'),
594 generatedColumn: util.getArg(aArgs, 'column')
597 var index = this._findMapping(
599 this._generatedMappings,
602 util.compareByGeneratedPositionsDeflated,
603 util.getArg(aArgs, 'bias', SourceMapConsumer.GREATEST_LOWER_BOUND)
607 var mapping = this._generatedMappings[index];
609 if (mapping.generatedLine === needle.generatedLine) {
610 var source = util.getArg(mapping, 'source', null);
611 if (source !== null) {
612 source = this._sources.at(source);
613 if (this.sourceRoot != null) {
614 source = util.join(this.sourceRoot, source);
617 var name = util.getArg(mapping, 'name', null);
619 name = this._names.at(name);
623 line: util.getArg(mapping, 'originalLine', null),
624 column: util.getArg(mapping, 'originalColumn', null),
639 * Return true if we have the source content for every source in the source
640 * map, false otherwise.
642 BasicSourceMapConsumer.prototype.hasContentsOfAllSources =
643 function BasicSourceMapConsumer_hasContentsOfAllSources() {
644 if (!this.sourcesContent) {
647 return this.sourcesContent.length >= this._sources.size() &&
648 !this.sourcesContent.some(function (sc) { return sc == null; });
652 * Returns the original source content. The only argument is the url of the
653 * original source file. Returns null if no original source content is
656 BasicSourceMapConsumer.prototype.sourceContentFor =
657 function SourceMapConsumer_sourceContentFor(aSource, nullOnMissing) {
658 if (!this.sourcesContent) {
662 if (this.sourceRoot != null) {
663 aSource = util.relative(this.sourceRoot, aSource);
666 if (this._sources.has(aSource)) {
667 return this.sourcesContent[this._sources.indexOf(aSource)];
671 if (this.sourceRoot != null
672 && (url = util.urlParse(this.sourceRoot))) {
673 // XXX: file:// URIs and absolute paths lead to unexpected behavior for
674 // many users. We can help them out when they expect file:// URIs to
675 // behave like it would if they were running a local HTTP server. See
676 // https://bugzilla.mozilla.org/show_bug.cgi?id=885597.
677 var fileUriAbsPath = aSource.replace(/^file:\/\//, "");
678 if (url.scheme == "file"
679 && this._sources.has(fileUriAbsPath)) {
680 return this.sourcesContent[this._sources.indexOf(fileUriAbsPath)]
683 if ((!url.path || url.path == "/")
684 && this._sources.has("/" + aSource)) {
685 return this.sourcesContent[this._sources.indexOf("/" + aSource)];
689 // This function is used recursively from
690 // IndexedSourceMapConsumer.prototype.sourceContentFor. In that case, we
691 // don't want to throw if we can't find the source - we just want to
692 // return null, so we provide a flag to exit gracefully.
697 throw new Error('"' + aSource + '" is not in the SourceMap.');
702 * Returns the generated line and column information for the original source,
703 * line, and column positions provided. The only argument is an object with
704 * the following properties:
706 * - source: The filename of the original source.
707 * - line: The line number in the original source.
708 * - column: The column number in the original source.
709 * - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or
710 * 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the
711 * closest element that is smaller than or greater than the one we are
712 * searching for, respectively, if the exact element cannot be found.
713 * Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'.
715 * and an object is returned with the following properties:
717 * - line: The line number in the generated source, or null.
718 * - column: The column number in the generated source, or null.
720 BasicSourceMapConsumer.prototype.generatedPositionFor =
721 function SourceMapConsumer_generatedPositionFor(aArgs) {
722 var source = util.getArg(aArgs, 'source');
723 if (this.sourceRoot != null) {
724 source = util.relative(this.sourceRoot, source);
726 if (!this._sources.has(source)) {
733 source = this._sources.indexOf(source);
737 originalLine: util.getArg(aArgs, 'line'),
738 originalColumn: util.getArg(aArgs, 'column')
741 var index = this._findMapping(
743 this._originalMappings,
746 util.compareByOriginalPositions,
747 util.getArg(aArgs, 'bias', SourceMapConsumer.GREATEST_LOWER_BOUND)
751 var mapping = this._originalMappings[index];
753 if (mapping.source === needle.source) {
755 line: util.getArg(mapping, 'generatedLine', null),
756 column: util.getArg(mapping, 'generatedColumn', null),
757 lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null)
769 exports.BasicSourceMapConsumer = BasicSourceMapConsumer;
772 * An IndexedSourceMapConsumer instance represents a parsed source map which
773 * we can query for information. It differs from BasicSourceMapConsumer in
774 * that it takes "indexed" source maps (i.e. ones with a "sections" field) as
777 * The only parameter is a raw source map (either as a JSON string, or already
778 * parsed to an object). According to the spec for indexed source maps, they
779 * have the following attributes:
781 * - version: Which version of the source map spec this map is following.
782 * - file: Optional. The generated file this source map is associated with.
783 * - sections: A list of section definitions.
785 * Each value under the "sections" field has two fields:
786 * - offset: The offset into the original specified at which this section
787 * begins to apply, defined as an object with a "line" and "column"
789 * - map: A source map definition. This source map could also be indexed,
790 * but doesn't have to be.
792 * Instead of the "map" field, it's also possible to have a "url" field
793 * specifying a URL to retrieve a source map from, but that's currently
796 * Here's an example source map, taken from the source map spec[0], but
797 * modified to omit a section which uses the "url" field.
803 * offset: {line:100, column:10},
806 * file: "section.js",
807 * sources: ["foo.js", "bar.js"],
808 * names: ["src", "maps", "are", "fun"],
809 * mappings: "AAAA,E;;ABCDE;"
814 * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#heading=h.535es3xeprgt
816 function IndexedSourceMapConsumer(aSourceMap) {
817 var sourceMap = aSourceMap;
818 if (typeof aSourceMap === 'string') {
819 sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, ''));
822 var version = util.getArg(sourceMap, 'version');
823 var sections = util.getArg(sourceMap, 'sections');
825 if (version != this._version) {
826 throw new Error('Unsupported version: ' + version);
829 this._sources = new ArraySet();
830 this._names = new ArraySet();
836 this._sections = sections.map(function (s) {
838 // The url field will require support for asynchronicity.
839 // See https://github.com/mozilla/source-map/issues/16
840 throw new Error('Support for url field in sections not implemented.');
842 var offset = util.getArg(s, 'offset');
843 var offsetLine = util.getArg(offset, 'line');
844 var offsetColumn = util.getArg(offset, 'column');
846 if (offsetLine < lastOffset.line ||
847 (offsetLine === lastOffset.line && offsetColumn < lastOffset.column)) {
848 throw new Error('Section offsets must be ordered and non-overlapping.');
854 // The offset fields are 0-based, but we use 1-based indices when
855 // encoding/decoding from VLQ.
856 generatedLine: offsetLine + 1,
857 generatedColumn: offsetColumn + 1
859 consumer: new SourceMapConsumer(util.getArg(s, 'map'))
864 IndexedSourceMapConsumer.prototype = Object.create(SourceMapConsumer.prototype);
865 IndexedSourceMapConsumer.prototype.constructor = SourceMapConsumer;
868 * The version of the source mapping spec that we are consuming.
870 IndexedSourceMapConsumer.prototype._version = 3;
873 * The list of original sources.
875 Object.defineProperty(IndexedSourceMapConsumer.prototype, 'sources', {
878 for (var i = 0; i < this._sections.length; i++) {
879 for (var j = 0; j < this._sections[i].consumer.sources.length; j++) {
880 sources.push(this._sections[i].consumer.sources[j]);
888 * Returns the original source, line, and column information for the generated
889 * source's line and column positions provided. The only argument is an object
890 * with the following properties:
892 * - line: The line number in the generated source.
893 * - column: The column number in the generated source.
895 * and an object is returned with the following properties:
897 * - source: The original source file, or null.
898 * - line: The line number in the original source, or null.
899 * - column: The column number in the original source, or null.
900 * - name: The original identifier, or null.
902 IndexedSourceMapConsumer.prototype.originalPositionFor =
903 function IndexedSourceMapConsumer_originalPositionFor(aArgs) {
905 generatedLine: util.getArg(aArgs, 'line'),
906 generatedColumn: util.getArg(aArgs, 'column')
909 // Find the section containing the generated position we're trying to map
910 // to an original position.
911 var sectionIndex = binarySearch.search(needle, this._sections,
912 function(needle, section) {
913 var cmp = needle.generatedLine - section.generatedOffset.generatedLine;
918 return (needle.generatedColumn -
919 section.generatedOffset.generatedColumn);
921 var section = this._sections[sectionIndex];
932 return section.consumer.originalPositionFor({
933 line: needle.generatedLine -
934 (section.generatedOffset.generatedLine - 1),
935 column: needle.generatedColumn -
936 (section.generatedOffset.generatedLine === needle.generatedLine
937 ? section.generatedOffset.generatedColumn - 1
944 * Return true if we have the source content for every source in the source
945 * map, false otherwise.
947 IndexedSourceMapConsumer.prototype.hasContentsOfAllSources =
948 function IndexedSourceMapConsumer_hasContentsOfAllSources() {
949 return this._sections.every(function (s) {
950 return s.consumer.hasContentsOfAllSources();
955 * Returns the original source content. The only argument is the url of the
956 * original source file. Returns null if no original source content is
959 IndexedSourceMapConsumer.prototype.sourceContentFor =
960 function IndexedSourceMapConsumer_sourceContentFor(aSource, nullOnMissing) {
961 for (var i = 0; i < this._sections.length; i++) {
962 var section = this._sections[i];
964 var content = section.consumer.sourceContentFor(aSource, true);
973 throw new Error('"' + aSource + '" is not in the SourceMap.');
978 * Returns the generated line and column information for the original source,
979 * line, and column positions provided. The only argument is an object with
980 * the following properties:
982 * - source: The filename of the original source.
983 * - line: The line number in the original source.
984 * - column: The column number in the original source.
986 * and an object is returned with the following properties:
988 * - line: The line number in the generated source, or null.
989 * - column: The column number in the generated source, or null.
991 IndexedSourceMapConsumer.prototype.generatedPositionFor =
992 function IndexedSourceMapConsumer_generatedPositionFor(aArgs) {
993 for (var i = 0; i < this._sections.length; i++) {
994 var section = this._sections[i];
996 // Only consider this section if the requested source is in the list of
997 // sources of the consumer.
998 if (section.consumer.sources.indexOf(util.getArg(aArgs, 'source')) === -1) {
1001 var generatedPosition = section.consumer.generatedPositionFor(aArgs);
1002 if (generatedPosition) {
1004 line: generatedPosition.line +
1005 (section.generatedOffset.generatedLine - 1),
1006 column: generatedPosition.column +
1007 (section.generatedOffset.generatedLine === generatedPosition.line
1008 ? section.generatedOffset.generatedColumn - 1
1022 * Parse the mappings in a string in to a data structure which we can easily
1023 * query (the ordered arrays in the `this.__generatedMappings` and
1024 * `this.__originalMappings` properties).
1026 IndexedSourceMapConsumer.prototype._parseMappings =
1027 function IndexedSourceMapConsumer_parseMappings(aStr, aSourceRoot) {
1028 this.__generatedMappings = [];
1029 this.__originalMappings = [];
1030 for (var i = 0; i < this._sections.length; i++) {
1031 var section = this._sections[i];
1032 var sectionMappings = section.consumer._generatedMappings;
1033 for (var j = 0; j < sectionMappings.length; j++) {
1034 var mapping = sectionMappings[i];
1036 var source = section.consumer._sources.at(mapping.source);
1037 if (section.consumer.sourceRoot !== null) {
1038 source = util.join(section.consumer.sourceRoot, source);
1040 this._sources.add(source);
1041 source = this._sources.indexOf(source);
1043 var name = section.consumer._names.at(mapping.name);
1044 this._names.add(name);
1045 name = this._names.indexOf(name);
1047 // The mappings coming from the consumer for the section have
1048 // generated positions relative to the start of the section, so we
1049 // need to offset them to be relative to the start of the concatenated
1051 var adjustedMapping = {
1053 generatedLine: mapping.generatedLine +
1054 (section.generatedOffset.generatedLine - 1),
1055 generatedColumn: mapping.column +
1056 (section.generatedOffset.generatedLine === mapping.generatedLine)
1057 ? section.generatedOffset.generatedColumn - 1
1059 originalLine: mapping.originalLine,
1060 originalColumn: mapping.originalColumn,
1064 this.__generatedMappings.push(adjustedMapping);
1065 if (typeof adjustedMapping.originalLine === 'number') {
1066 this.__originalMappings.push(adjustedMapping);
1071 quickSort(this.__generatedMappings, util.compareByGeneratedPositionsDeflated);
1072 quickSort(this.__originalMappings, util.compareByOriginalPositions);
1075 exports.IndexedSourceMapConsumer = IndexedSourceMapConsumer;