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
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;
14 function SourceMapConsumer(aSourceMap) {
15 var sourceMap = aSourceMap;
16 if (typeof aSourceMap === 'string') {
17 sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, ''));
20 return sourceMap.sections != null
21 ? new IndexedSourceMapConsumer(sourceMap)
22 : new BasicSourceMapConsumer(sourceMap);
25 SourceMapConsumer.fromSourceMap = function(aSourceMap) {
26 return BasicSourceMapConsumer.fromSourceMap(aSourceMap);
30 * The version of the source mapping spec that we are consuming.
32 SourceMapConsumer.prototype._version = 3;
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.
42 // Each object in the arrays is of the form:
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
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
57 // All properties except for `generatedLine` and `generatedColumn` can be
60 // `_generatedMappings` is ordered by the generated positions.
62 // `_originalMappings` is ordered by the original positions.
64 SourceMapConsumer.prototype.__generatedMappings = null;
65 Object.defineProperty(SourceMapConsumer.prototype, '_generatedMappings', {
67 if (!this.__generatedMappings) {
68 this._parseMappings(this._mappings, this.sourceRoot);
71 return this.__generatedMappings;
75 SourceMapConsumer.prototype.__originalMappings = null;
76 Object.defineProperty(SourceMapConsumer.prototype, '_originalMappings', {
78 if (!this.__originalMappings) {
79 this._parseMappings(this._mappings, this.sourceRoot);
82 return this.__originalMappings;
86 SourceMapConsumer.prototype._charIsMappingSeparator =
87 function SourceMapConsumer_charIsMappingSeparator(aStr, index) {
88 var c = aStr.charAt(index);
89 return c === ";" || c === ",";
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).
97 SourceMapConsumer.prototype._parseMappings =
98 function SourceMapConsumer_parseMappings(aStr, aSourceRoot) {
99 throw new Error("Subclasses must implement _parseMappings");
102 SourceMapConsumer.GENERATED_ORDER = 1;
103 SourceMapConsumer.ORIGINAL_ORDER = 2;
105 SourceMapConsumer.GREATEST_LOWER_BOUND = 1;
106 SourceMapConsumer.LEAST_UPPER_BOUND = 2;
109 * Iterate over each mapping between an original source/line/column and a
110 * generated line/column in this source map.
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.
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`.
124 SourceMapConsumer.prototype.eachMapping =
125 function SourceMapConsumer_eachMapping(aCallback, aContext, aOrder) {
126 var context = aContext || null;
127 var order = aOrder || SourceMapConsumer.GENERATED_ORDER;
131 case SourceMapConsumer.GENERATED_ORDER:
132 mappings = this._generatedMappings;
134 case SourceMapConsumer.ORIGINAL_ORDER:
135 mappings = this._originalMappings;
138 throw new Error("Unknown order of iteration.");
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);
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)
155 }, this).forEach(aCallback, context);
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.
166 * The only argument is an object with the following properties:
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.
172 * and an array of objects is returned, each with the following properties:
174 * - line: The line number in the generated source, or null.
175 * - column: The column number in the generated source, or null.
177 SourceMapConsumer.prototype.allGeneratedPositionsFor =
178 function SourceMapConsumer_allGeneratedPositionsFor(aArgs) {
179 var line = util.getArg(aArgs, 'line');
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.
186 source: util.getArg(aArgs, 'source'),
188 originalColumn: util.getArg(aArgs, 'column', 0)
191 if (this.sourceRoot != null) {
192 needle.source = util.relative(this.sourceRoot, needle.source);
194 if (!this._sources.has(needle.source)) {
197 needle.source = this._sources.indexOf(needle.source);
201 var index = this._findMapping(needle,
202 this._originalMappings,
205 util.compareByOriginalPositions,
206 binarySearch.LEAST_UPPER_BOUND);
208 var mapping = this._originalMappings[index];
210 if (aArgs.column === undefined) {
211 var originalLine = mapping.originalLine;
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) {
219 line: util.getArg(mapping, 'generatedLine', null),
220 column: util.getArg(mapping, 'generatedColumn', null),
221 lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null)
224 mapping = this._originalMappings[++index];
227 var originalColumn = mapping.originalColumn;
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.
234 mapping.originalLine === line &&
235 mapping.originalColumn == originalColumn) {
237 line: util.getArg(mapping, 'generatedLine', null),
238 column: util.getArg(mapping, 'generatedColumn', null),
239 lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null)
242 mapping = this._originalMappings[++index];
250 exports.SourceMapConsumer = SourceMapConsumer;
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.
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:
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.
269 * Here is an example source map, taken from the source map spec[0]:
275 * sources: ["foo.js", "bar.js"],
276 * names: ["src", "maps", "are", "fun"],
277 * mappings: "AA,AB;;ABCDE;"
280 * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit?pli=1#
282 function BasicSourceMapConsumer(aSourceMap) {
283 var sourceMap = aSourceMap;
284 if (typeof aSourceMap === 'string') {
285 sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, ''));
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);
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);
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.
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)
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);
327 this.sourceRoot = sourceRoot;
328 this.sourcesContent = sourcesContent;
329 this._mappings = mappings;
333 BasicSourceMapConsumer.prototype = Object.create(SourceMapConsumer.prototype);
334 BasicSourceMapConsumer.prototype.consumer = SourceMapConsumer;
337 * Create a BasicSourceMapConsumer from a SourceMapGenerator.
339 * @param SourceMapGenerator aSourceMap
340 * The source map that will be consumed.
341 * @returns BasicSourceMapConsumer
343 BasicSourceMapConsumer.fromSourceMap =
344 function SourceMapConsumer_fromSourceMap(aSourceMap) {
345 var smc = Object.create(BasicSourceMapConsumer.prototype);
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(),
352 smc.file = aSourceMap._file;
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.
359 var generatedMappings = aSourceMap._mappings.toArray().slice();
360 var destGeneratedMappings = smc.__generatedMappings = [];
361 var destOriginalMappings = smc.__originalMappings = [];
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;
369 if (srcMapping.source) {
370 destMapping.source = sources.indexOf(srcMapping.source);
371 destMapping.originalLine = srcMapping.originalLine;
372 destMapping.originalColumn = srcMapping.originalColumn;
374 if (srcMapping.name) {
375 destMapping.name = names.indexOf(srcMapping.name);
378 destOriginalMappings.push(destMapping);
381 destGeneratedMappings.push(destMapping);
384 quickSort(smc.__originalMappings, util.compareByOriginalPositions);
390 * The version of the source mapping spec that we are consuming.
392 BasicSourceMapConsumer.prototype._version = 3;
395 * The list of original sources.
397 Object.defineProperty(BasicSourceMapConsumer.prototype, 'sources', {
399 return this._sources.toArray().map(function (s) {
400 return this.sourceRoot != null ? util.join(this.sourceRoot, s) : s;
406 * Provide the JIT with a nice shape / hidden class.
409 this.generatedLine = 0;
410 this.generatedColumn = 0;
412 this.originalLine = null;
413 this.originalColumn = null;
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).
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;
432 var cachedSegments = {};
434 var originalMappings = [];
435 var generatedMappings = [];
436 var mapping, str, segment, end, value;
438 while (index < length) {
439 if (aStr.charAt(index) === ';') {
442 previousGeneratedColumn = 0;
444 else if (aStr.charAt(index) === ',') {
448 mapping = new Mapping();
449 mapping.generatedLine = generatedLine;
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
456 for (end = index; end < length; end++) {
457 if (this._charIsMappingSeparator(aStr, end)) {
461 str = aStr.slice(index, end);
463 segment = cachedSegments[str];
468 while (index < end) {
469 base64VLQ.decode(aStr, index, temp);
475 if (segment.length === 2) {
476 throw new Error('Found a source, but no line and column');
479 if (segment.length === 3) {
480 throw new Error('Found a source and line, but no column');
483 cachedSegments[str] = segment;
487 mapping.generatedColumn = previousGeneratedColumn + segment[0];
488 previousGeneratedColumn = mapping.generatedColumn;
490 if (segment.length > 1) {
492 mapping.source = previousSource + segment[1];
493 previousSource += segment[1];
496 mapping.originalLine = previousOriginalLine + segment[2];
497 previousOriginalLine = mapping.originalLine;
498 // Lines are stored 0-based
499 mapping.originalLine += 1;
502 mapping.originalColumn = previousOriginalColumn + segment[3];
503 previousOriginalColumn = mapping.originalColumn;
505 if (segment.length > 4) {
507 mapping.name = previousName + segment[4];
508 previousName += segment[4];
512 generatedMappings.push(mapping);
513 if (typeof mapping.originalLine === 'number') {
514 originalMappings.push(mapping);
519 quickSort(generatedMappings, util.compareByGeneratedPositionsDeflated);
520 this.__generatedMappings = generatedMappings;
522 quickSort(originalMappings, util.compareByOriginalPositions);
523 this.__originalMappings = originalMappings;
527 * Find the mapping that best matches the hypothetical "needle" mapping that
528 * we are searching for in the given "haystack" of mappings.
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.
538 if (aNeedle[aLineName] <= 0) {
539 throw new TypeError('Line must be greater than or equal to 1, got '
540 + aNeedle[aLineName]);
542 if (aNeedle[aColumnName] < 0) {
543 throw new TypeError('Column must be greater than or equal to 0, got '
544 + aNeedle[aColumnName]);
547 return binarySearch.search(aNeedle, aMappings, aComparator, aBias);
551 * Compute the last column for each generated mapping. The last column is
554 BasicSourceMapConsumer.prototype.computeColumnSpans =
555 function SourceMapConsumer_computeColumnSpans() {
556 for (var index = 0; index < this._generatedMappings.length; ++index) {
557 var mapping = this._generatedMappings[index];
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];
566 if (mapping.generatedLine === nextMapping.generatedLine) {
567 mapping.lastGeneratedColumn = nextMapping.generatedColumn - 1;
572 // The last mapping for each line spans the entire line.
573 mapping.lastGeneratedColumn = Infinity;
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:
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'.
590 * and an object is returned with the following properties:
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.
597 BasicSourceMapConsumer.prototype.originalPositionFor =
598 function SourceMapConsumer_originalPositionFor(aArgs) {
600 generatedLine: util.getArg(aArgs, 'line'),
601 generatedColumn: util.getArg(aArgs, 'column')
604 var index = this._findMapping(
606 this._generatedMappings,
609 util.compareByGeneratedPositionsDeflated,
610 util.getArg(aArgs, 'bias', SourceMapConsumer.GREATEST_LOWER_BOUND)
614 var mapping = this._generatedMappings[index];
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);
624 var name = util.getArg(mapping, 'name', null);
626 name = this._names.at(name);
630 line: util.getArg(mapping, 'originalLine', null),
631 column: util.getArg(mapping, 'originalColumn', null),
646 * Return true if we have the source content for every source in the source
647 * map, false otherwise.
649 BasicSourceMapConsumer.prototype.hasContentsOfAllSources =
650 function BasicSourceMapConsumer_hasContentsOfAllSources() {
651 if (!this.sourcesContent) {
654 return this.sourcesContent.length >= this._sources.size() &&
655 !this.sourcesContent.some(function (sc) { return sc == null; });
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
663 BasicSourceMapConsumer.prototype.sourceContentFor =
664 function SourceMapConsumer_sourceContentFor(aSource, nullOnMissing) {
665 if (!this.sourcesContent) {
669 if (this.sourceRoot != null) {
670 aSource = util.relative(this.sourceRoot, aSource);
673 if (this._sources.has(aSource)) {
674 return this.sourcesContent[this._sources.indexOf(aSource)];
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)]
690 if ((!url.path || url.path == "/")
691 && this._sources.has("/" + aSource)) {
692 return this.sourcesContent[this._sources.indexOf("/" + aSource)];
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.
704 throw new Error('"' + aSource + '" is not in the SourceMap.');
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:
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'.
722 * and an object is returned with the following properties:
724 * - line: The line number in the generated source, or null.
725 * - column: The column number in the generated source, or null.
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);
733 if (!this._sources.has(source)) {
740 source = this._sources.indexOf(source);
744 originalLine: util.getArg(aArgs, 'line'),
745 originalColumn: util.getArg(aArgs, 'column')
748 var index = this._findMapping(
750 this._originalMappings,
753 util.compareByOriginalPositions,
754 util.getArg(aArgs, 'bias', SourceMapConsumer.GREATEST_LOWER_BOUND)
758 var mapping = this._originalMappings[index];
760 if (mapping.source === needle.source) {
762 line: util.getArg(mapping, 'generatedLine', null),
763 column: util.getArg(mapping, 'generatedColumn', null),
764 lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null)
776 exports.BasicSourceMapConsumer = BasicSourceMapConsumer;
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
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:
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.
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"
796 * - map: A source map definition. This source map could also be indexed,
797 * but doesn't have to be.
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
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.
810 * offset: {line:100, column:10},
813 * file: "section.js",
814 * sources: ["foo.js", "bar.js"],
815 * names: ["src", "maps", "are", "fun"],
816 * mappings: "AAAA,E;;ABCDE;"
821 * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#heading=h.535es3xeprgt
823 function IndexedSourceMapConsumer(aSourceMap) {
824 var sourceMap = aSourceMap;
825 if (typeof aSourceMap === 'string') {
826 sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, ''));
829 var version = util.getArg(sourceMap, 'version');
830 var sections = util.getArg(sourceMap, 'sections');
832 if (version != this._version) {
833 throw new Error('Unsupported version: ' + version);
836 this._sources = new ArraySet();
837 this._names = new ArraySet();
843 this._sections = sections.map(function (s) {
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.');
849 var offset = util.getArg(s, 'offset');
850 var offsetLine = util.getArg(offset, 'line');
851 var offsetColumn = util.getArg(offset, 'column');
853 if (offsetLine < lastOffset.line ||
854 (offsetLine === lastOffset.line && offsetColumn < lastOffset.column)) {
855 throw new Error('Section offsets must be ordered and non-overlapping.');
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
866 consumer: new SourceMapConsumer(util.getArg(s, 'map'))
871 IndexedSourceMapConsumer.prototype = Object.create(SourceMapConsumer.prototype);
872 IndexedSourceMapConsumer.prototype.constructor = SourceMapConsumer;
875 * The version of the source mapping spec that we are consuming.
877 IndexedSourceMapConsumer.prototype._version = 3;
880 * The list of original sources.
882 Object.defineProperty(IndexedSourceMapConsumer.prototype, '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]);
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:
899 * - line: The line number in the generated source.
900 * - column: The column number in the generated source.
902 * and an object is returned with the following properties:
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.
909 IndexedSourceMapConsumer.prototype.originalPositionFor =
910 function IndexedSourceMapConsumer_originalPositionFor(aArgs) {
912 generatedLine: util.getArg(aArgs, 'line'),
913 generatedColumn: util.getArg(aArgs, 'column')
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;
925 return (needle.generatedColumn -
926 section.generatedOffset.generatedColumn);
928 var section = this._sections[sectionIndex];
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
951 * Return true if we have the source content for every source in the source
952 * map, false otherwise.
954 IndexedSourceMapConsumer.prototype.hasContentsOfAllSources =
955 function IndexedSourceMapConsumer_hasContentsOfAllSources() {
956 return this._sections.every(function (s) {
957 return s.consumer.hasContentsOfAllSources();
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
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];
971 var content = section.consumer.sourceContentFor(aSource, true);
980 throw new Error('"' + aSource + '" is not in the SourceMap.');
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:
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.
993 * and an object is returned with the following properties:
995 * - line: The line number in the generated source, or null.
996 * - column: The column number in the generated source, or null.
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];
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) {
1008 var generatedPosition = section.consumer.generatedPositionFor(aArgs);
1009 if (generatedPosition) {
1011 line: generatedPosition.line +
1012 (section.generatedOffset.generatedLine - 1),
1013 column: generatedPosition.column +
1014 (section.generatedOffset.generatedLine === generatedPosition.line
1015 ? section.generatedOffset.generatedColumn - 1
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).
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];
1043 var source = section.consumer._sources.at(mapping.source);
1044 if (section.consumer.sourceRoot !== null) {
1045 source = util.join(section.consumer.sourceRoot, source);
1047 this._sources.add(source);
1048 source = this._sources.indexOf(source);
1050 var name = section.consumer._names.at(mapping.name);
1051 this._names.add(name);
1052 name = this._names.indexOf(name);
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
1058 var adjustedMapping = {
1060 generatedLine: mapping.generatedLine +
1061 (section.generatedOffset.generatedLine - 1),
1062 generatedColumn: mapping.generatedColumn +
1063 (section.generatedOffset.generatedLine === mapping.generatedLine
1064 ? section.generatedOffset.generatedColumn - 1
1066 originalLine: mapping.originalLine,
1067 originalColumn: mapping.originalColumn,
1071 this.__generatedMappings.push(adjustedMapping);
1072 if (typeof adjustedMapping.originalLine === 'number') {
1073 this.__originalMappings.push(adjustedMapping);
1078 quickSort(this.__generatedMappings, util.compareByGeneratedPositionsDeflated);
1079 quickSort(this.__originalMappings, util.compareByOriginalPositions);
1082 exports.IndexedSourceMapConsumer = IndexedSourceMapConsumer;