blob: 6abcc280eea1603086852633e89ec60bacf81098 [file] [log] [blame]
Leo Repp58b9f112021-11-22 11:57:47 +01001/* -*- 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
8var util = require('./util');
9var binarySearch = require('./binary-search');
10var ArraySet = require('./array-set').ArraySet;
11var base64VLQ = require('./base64-vlq');
12var quickSort = require('./quick-sort').quickSort;
13
14function 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
25SourceMapConsumer.fromSourceMap = function(aSourceMap) {
26 return BasicSourceMapConsumer.fromSourceMap(aSourceMap);
27}
28
29/**
30 * The version of the source mapping spec that we are consuming.
31 */
32SourceMapConsumer.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
64SourceMapConsumer.prototype.__generatedMappings = null;
65Object.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
75SourceMapConsumer.prototype.__originalMappings = null;
76Object.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
86SourceMapConsumer.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 */
97SourceMapConsumer.prototype._parseMappings =
98 function SourceMapConsumer_parseMappings(aStr, aSourceRoot) {
99 throw new Error("Subclasses must implement _parseMappings");
100 };
101
102SourceMapConsumer.GENERATED_ORDER = 1;
103SourceMapConsumer.ORIGINAL_ORDER = 2;
104
105SourceMapConsumer.GREATEST_LOWER_BOUND = 1;
106SourceMapConsumer.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 */
124SourceMapConsumer.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 */
177SourceMapConsumer.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
250exports.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 */
282function 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
333BasicSourceMapConsumer.prototype = Object.create(SourceMapConsumer.prototype);
334BasicSourceMapConsumer.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 */
343BasicSourceMapConsumer.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 */
392BasicSourceMapConsumer.prototype._version = 3;
393
394/**
395 * The list of original sources.
396 */
397Object.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 */
408function 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 */
422BasicSourceMapConsumer.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 */
530BasicSourceMapConsumer.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 */
554BasicSourceMapConsumer.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 */
597BasicSourceMapConsumer.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 */
649BasicSourceMapConsumer.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 */
663BasicSourceMapConsumer.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 */
727BasicSourceMapConsumer.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
776exports.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 */
823function 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
871IndexedSourceMapConsumer.prototype = Object.create(SourceMapConsumer.prototype);
872IndexedSourceMapConsumer.prototype.constructor = SourceMapConsumer;
873
874/**
875 * The version of the source mapping spec that we are consuming.
876 */
877IndexedSourceMapConsumer.prototype._version = 3;
878
879/**
880 * The list of original sources.
881 */
882Object.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 */
909IndexedSourceMapConsumer.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 */
954IndexedSourceMapConsumer.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 */
966IndexedSourceMapConsumer.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 */
998IndexedSourceMapConsumer.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 */
1033IndexedSourceMapConsumer.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
1082exports.IndexedSourceMapConsumer = IndexedSourceMapConsumer;