source: trip-planner-front/node_modules/source-map/lib/source-map-consumer.js@ 59329aa

Last change on this file since 59329aa was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 41.3 KB
Line 
1/* -*- Mode: js; js-indent-level: 2; -*- */
2/*
3 * Copyright 2011 Mozilla Foundation and contributors
4 * Licensed under the New BSD license. See LICENSE or:
5 * http://opensource.org/licenses/BSD-3-Clause
6 */
7
8const util = require("./util");
9const binarySearch = require("./binary-search");
10const ArraySet = require("./array-set").ArraySet;
11const base64VLQ = require("./base64-vlq"); // eslint-disable-line no-unused-vars
12const readWasm = require("../lib/read-wasm");
13const wasm = require("./wasm");
14
15const INTERNAL = Symbol("smcInternal");
16
17class SourceMapConsumer {
18 constructor(aSourceMap, aSourceMapURL) {
19 // If the constructor was called by super(), just return Promise<this>.
20 // Yes, this is a hack to retain the pre-existing API of the base-class
21 // constructor also being an async factory function.
22 if (aSourceMap == INTERNAL) {
23 return Promise.resolve(this);
24 }
25
26 return _factory(aSourceMap, aSourceMapURL);
27 }
28
29 static initialize(opts) {
30 readWasm.initialize(opts["lib/mappings.wasm"]);
31 }
32
33 static fromSourceMap(aSourceMap, aSourceMapURL) {
34 return _factoryBSM(aSourceMap, aSourceMapURL);
35 }
36
37 /**
38 * Construct a new `SourceMapConsumer` from `rawSourceMap` and `sourceMapUrl`
39 * (see the `SourceMapConsumer` constructor for details. Then, invoke the `async
40 * function f(SourceMapConsumer) -> T` with the newly constructed consumer, wait
41 * for `f` to complete, call `destroy` on the consumer, and return `f`'s return
42 * value.
43 *
44 * You must not use the consumer after `f` completes!
45 *
46 * By using `with`, you do not have to remember to manually call `destroy` on
47 * the consumer, since it will be called automatically once `f` completes.
48 *
49 * ```js
50 * const xSquared = await SourceMapConsumer.with(
51 * myRawSourceMap,
52 * null,
53 * async function (consumer) {
54 * // Use `consumer` inside here and don't worry about remembering
55 * // to call `destroy`.
56 *
57 * const x = await whatever(consumer);
58 * return x * x;
59 * }
60 * );
61 *
62 * // You may not use that `consumer` anymore out here; it has
63 * // been destroyed. But you can use `xSquared`.
64 * console.log(xSquared);
65 * ```
66 */
67 static with(rawSourceMap, sourceMapUrl, f) {
68 // Note: The `acorn` version that `webpack` currently depends on doesn't
69 // support `async` functions, and the nodes that we support don't all have
70 // `.finally`. Therefore, this is written a bit more convolutedly than it
71 // should really be.
72
73 let consumer = null;
74 const promise = new SourceMapConsumer(rawSourceMap, sourceMapUrl);
75 return promise
76 .then(c => {
77 consumer = c;
78 return f(c);
79 })
80 .then(x => {
81 if (consumer) {
82 consumer.destroy();
83 }
84 return x;
85 }, e => {
86 if (consumer) {
87 consumer.destroy();
88 }
89 throw e;
90 });
91 }
92
93 /**
94 * Parse the mappings in a string in to a data structure which we can easily
95 * query (the ordered arrays in the `this.__generatedMappings` and
96 * `this.__originalMappings` properties).
97 */
98 _parseMappings(aStr, aSourceRoot) {
99 throw new Error("Subclasses must implement _parseMappings");
100 }
101
102 /**
103 * Iterate over each mapping between an original source/line/column and a
104 * generated line/column in this source map.
105 *
106 * @param Function aCallback
107 * The function that is called with each mapping.
108 * @param Object aContext
109 * Optional. If specified, this object will be the value of `this` every
110 * time that `aCallback` is called.
111 * @param aOrder
112 * Either `SourceMapConsumer.GENERATED_ORDER` or
113 * `SourceMapConsumer.ORIGINAL_ORDER`. Specifies whether you want to
114 * iterate over the mappings sorted by the generated file's line/column
115 * order or the original's source/line/column order, respectively. Defaults to
116 * `SourceMapConsumer.GENERATED_ORDER`.
117 */
118 eachMapping(aCallback, aContext, aOrder) {
119 throw new Error("Subclasses must implement eachMapping");
120 }
121
122 /**
123 * Returns all generated line and column information for the original source,
124 * line, and column provided. If no column is provided, returns all mappings
125 * corresponding to a either the line we are searching for or the next
126 * closest line that has any mappings. Otherwise, returns all mappings
127 * corresponding to the given line and either the column we are searching for
128 * or the next closest column that has any offsets.
129 *
130 * The only argument is an object with the following properties:
131 *
132 * - source: The filename of the original source.
133 * - line: The line number in the original source. The line number is 1-based.
134 * - column: Optional. the column number in the original source.
135 * The column number is 0-based.
136 *
137 * and an array of objects is returned, each with the following properties:
138 *
139 * - line: The line number in the generated source, or null. The
140 * line number is 1-based.
141 * - column: The column number in the generated source, or null.
142 * The column number is 0-based.
143 */
144 allGeneratedPositionsFor(aArgs) {
145 throw new Error("Subclasses must implement allGeneratedPositionsFor");
146 }
147
148 destroy() {
149 throw new Error("Subclasses must implement destroy");
150 }
151}
152
153/**
154 * The version of the source mapping spec that we are consuming.
155 */
156SourceMapConsumer.prototype._version = 3;
157SourceMapConsumer.GENERATED_ORDER = 1;
158SourceMapConsumer.ORIGINAL_ORDER = 2;
159
160SourceMapConsumer.GREATEST_LOWER_BOUND = 1;
161SourceMapConsumer.LEAST_UPPER_BOUND = 2;
162
163exports.SourceMapConsumer = SourceMapConsumer;
164
165/**
166 * A BasicSourceMapConsumer instance represents a parsed source map which we can
167 * query for information about the original file positions by giving it a file
168 * position in the generated source.
169 *
170 * The first parameter is the raw source map (either as a JSON string, or
171 * already parsed to an object). According to the spec, source maps have the
172 * following attributes:
173 *
174 * - version: Which version of the source map spec this map is following.
175 * - sources: An array of URLs to the original source files.
176 * - names: An array of identifiers which can be referenced by individual mappings.
177 * - sourceRoot: Optional. The URL root from which all sources are relative.
178 * - sourcesContent: Optional. An array of contents of the original source files.
179 * - mappings: A string of base64 VLQs which contain the actual mappings.
180 * - file: Optional. The generated file this source map is associated with.
181 *
182 * Here is an example source map, taken from the source map spec[0]:
183 *
184 * {
185 * version : 3,
186 * file: "out.js",
187 * sourceRoot : "",
188 * sources: ["foo.js", "bar.js"],
189 * names: ["src", "maps", "are", "fun"],
190 * mappings: "AA,AB;;ABCDE;"
191 * }
192 *
193 * The second parameter, if given, is a string whose value is the URL
194 * at which the source map was found. This URL is used to compute the
195 * sources array.
196 *
197 * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit?pli=1#
198 */
199class BasicSourceMapConsumer extends SourceMapConsumer {
200 constructor(aSourceMap, aSourceMapURL) {
201 return super(INTERNAL).then(that => {
202 let sourceMap = aSourceMap;
203 if (typeof aSourceMap === "string") {
204 sourceMap = util.parseSourceMapInput(aSourceMap);
205 }
206
207 const version = util.getArg(sourceMap, "version");
208 let sources = util.getArg(sourceMap, "sources");
209 // Sass 3.3 leaves out the 'names' array, so we deviate from the spec (which
210 // requires the array) to play nice here.
211 const names = util.getArg(sourceMap, "names", []);
212 let sourceRoot = util.getArg(sourceMap, "sourceRoot", null);
213 const sourcesContent = util.getArg(sourceMap, "sourcesContent", null);
214 const mappings = util.getArg(sourceMap, "mappings");
215 const file = util.getArg(sourceMap, "file", null);
216
217 // Once again, Sass deviates from the spec and supplies the version as a
218 // string rather than a number, so we use loose equality checking here.
219 if (version != that._version) {
220 throw new Error("Unsupported version: " + version);
221 }
222
223 if (sourceRoot) {
224 sourceRoot = util.normalize(sourceRoot);
225 }
226
227 sources = sources
228 .map(String)
229 // Some source maps produce relative source paths like "./foo.js" instead of
230 // "foo.js". Normalize these first so that future comparisons will succeed.
231 // See bugzil.la/1090768.
232 .map(util.normalize)
233 // Always ensure that absolute sources are internally stored relative to
234 // the source root, if the source root is absolute. Not doing this would
235 // be particularly problematic when the source root is a prefix of the
236 // source (valid, but why??). See github issue #199 and bugzil.la/1188982.
237 .map(function(source) {
238 return sourceRoot && util.isAbsolute(sourceRoot) && util.isAbsolute(source)
239 ? util.relative(sourceRoot, source)
240 : source;
241 });
242
243 // Pass `true` below to allow duplicate names and sources. While source maps
244 // are intended to be compressed and deduplicated, the TypeScript compiler
245 // sometimes generates source maps with duplicates in them. See Github issue
246 // #72 and bugzil.la/889492.
247 that._names = ArraySet.fromArray(names.map(String), true);
248 that._sources = ArraySet.fromArray(sources, true);
249
250 that._absoluteSources = that._sources.toArray().map(function(s) {
251 return util.computeSourceURL(sourceRoot, s, aSourceMapURL);
252 });
253
254 that.sourceRoot = sourceRoot;
255 that.sourcesContent = sourcesContent;
256 that._mappings = mappings;
257 that._sourceMapURL = aSourceMapURL;
258 that.file = file;
259
260 that._computedColumnSpans = false;
261 that._mappingsPtr = 0;
262 that._wasm = null;
263
264 return wasm().then(w => {
265 that._wasm = w;
266 return that;
267 });
268 });
269 }
270
271 /**
272 * Utility function to find the index of a source. Returns -1 if not
273 * found.
274 */
275 _findSourceIndex(aSource) {
276 let relativeSource = aSource;
277 if (this.sourceRoot != null) {
278 relativeSource = util.relative(this.sourceRoot, relativeSource);
279 }
280
281 if (this._sources.has(relativeSource)) {
282 return this._sources.indexOf(relativeSource);
283 }
284
285 // Maybe aSource is an absolute URL as returned by |sources|. In
286 // this case we can't simply undo the transform.
287 for (let i = 0; i < this._absoluteSources.length; ++i) {
288 if (this._absoluteSources[i] == aSource) {
289 return i;
290 }
291 }
292
293 return -1;
294 }
295
296 /**
297 * Create a BasicSourceMapConsumer from a SourceMapGenerator.
298 *
299 * @param SourceMapGenerator aSourceMap
300 * The source map that will be consumed.
301 * @param String aSourceMapURL
302 * The URL at which the source map can be found (optional)
303 * @returns BasicSourceMapConsumer
304 */
305 static fromSourceMap(aSourceMap, aSourceMapURL) {
306 return new BasicSourceMapConsumer(aSourceMap.toString());
307 }
308
309 get sources() {
310 return this._absoluteSources.slice();
311 }
312
313 _getMappingsPtr() {
314 if (this._mappingsPtr === 0) {
315 this._parseMappings(this._mappings, this.sourceRoot);
316 }
317
318 return this._mappingsPtr;
319 }
320
321 /**
322 * Parse the mappings in a string in to a data structure which we can easily
323 * query (the ordered arrays in the `this.__generatedMappings` and
324 * `this.__originalMappings` properties).
325 */
326 _parseMappings(aStr, aSourceRoot) {
327 const size = aStr.length;
328
329 const mappingsBufPtr = this._wasm.exports.allocate_mappings(size);
330 const mappingsBuf = new Uint8Array(this._wasm.exports.memory.buffer, mappingsBufPtr, size);
331 for (let i = 0; i < size; i++) {
332 mappingsBuf[i] = aStr.charCodeAt(i);
333 }
334
335 const mappingsPtr = this._wasm.exports.parse_mappings(mappingsBufPtr);
336
337 if (!mappingsPtr) {
338 const error = this._wasm.exports.get_last_error();
339 let msg = `Error parsing mappings (code ${error}): `;
340
341 // XXX: keep these error codes in sync with `fitzgen/source-map-mappings`.
342 switch (error) {
343 case 1:
344 msg += "the mappings contained a negative line, column, source index, or name index";
345 break;
346 case 2:
347 msg += "the mappings contained a number larger than 2**32";
348 break;
349 case 3:
350 msg += "reached EOF while in the middle of parsing a VLQ";
351 break;
352 case 4:
353 msg += "invalid base 64 character while parsing a VLQ";
354 break;
355 default:
356 msg += "unknown error code";
357 break;
358 }
359
360 throw new Error(msg);
361 }
362
363 this._mappingsPtr = mappingsPtr;
364 }
365
366 eachMapping(aCallback, aContext, aOrder) {
367 const context = aContext || null;
368 const order = aOrder || SourceMapConsumer.GENERATED_ORDER;
369 const sourceRoot = this.sourceRoot;
370
371 this._wasm.withMappingCallback(
372 mapping => {
373 if (mapping.source !== null) {
374 mapping.source = this._sources.at(mapping.source);
375 mapping.source = util.computeSourceURL(sourceRoot, mapping.source, this._sourceMapURL);
376
377 if (mapping.name !== null) {
378 mapping.name = this._names.at(mapping.name);
379 }
380 }
381
382 aCallback.call(context, mapping);
383 },
384 () => {
385 switch (order) {
386 case SourceMapConsumer.GENERATED_ORDER:
387 this._wasm.exports.by_generated_location(this._getMappingsPtr());
388 break;
389 case SourceMapConsumer.ORIGINAL_ORDER:
390 this._wasm.exports.by_original_location(this._getMappingsPtr());
391 break;
392 default:
393 throw new Error("Unknown order of iteration.");
394 }
395 }
396 );
397 }
398
399 allGeneratedPositionsFor(aArgs) {
400 let source = util.getArg(aArgs, "source");
401 const originalLine = util.getArg(aArgs, "line");
402 const originalColumn = aArgs.column || 0;
403
404 source = this._findSourceIndex(source);
405 if (source < 0) {
406 return [];
407 }
408
409 if (originalLine < 1) {
410 throw new Error("Line numbers must be >= 1");
411 }
412
413 if (originalColumn < 0) {
414 throw new Error("Column numbers must be >= 0");
415 }
416
417 const mappings = [];
418
419 this._wasm.withMappingCallback(
420 m => {
421 let lastColumn = m.lastGeneratedColumn;
422 if (this._computedColumnSpans && lastColumn === null) {
423 lastColumn = Infinity;
424 }
425 mappings.push({
426 line: m.generatedLine,
427 column: m.generatedColumn,
428 lastColumn,
429 });
430 }, () => {
431 this._wasm.exports.all_generated_locations_for(
432 this._getMappingsPtr(),
433 source,
434 originalLine - 1,
435 "column" in aArgs,
436 originalColumn
437 );
438 }
439 );
440
441 return mappings;
442 }
443
444 destroy() {
445 if (this._mappingsPtr !== 0) {
446 this._wasm.exports.free_mappings(this._mappingsPtr);
447 this._mappingsPtr = 0;
448 }
449 }
450
451 /**
452 * Compute the last column for each generated mapping. The last column is
453 * inclusive.
454 */
455 computeColumnSpans() {
456 if (this._computedColumnSpans) {
457 return;
458 }
459
460 this._wasm.exports.compute_column_spans(this._getMappingsPtr());
461 this._computedColumnSpans = true;
462 }
463
464 /**
465 * Returns the original source, line, and column information for the generated
466 * source's line and column positions provided. The only argument is an object
467 * with the following properties:
468 *
469 * - line: The line number in the generated source. The line number
470 * is 1-based.
471 * - column: The column number in the generated source. The column
472 * number is 0-based.
473 * - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or
474 * 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the
475 * closest element that is smaller than or greater than the one we are
476 * searching for, respectively, if the exact element cannot be found.
477 * Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'.
478 *
479 * and an object is returned with the following properties:
480 *
481 * - source: The original source file, or null.
482 * - line: The line number in the original source, or null. The
483 * line number is 1-based.
484 * - column: The column number in the original source, or null. The
485 * column number is 0-based.
486 * - name: The original identifier, or null.
487 */
488 originalPositionFor(aArgs) {
489 const needle = {
490 generatedLine: util.getArg(aArgs, "line"),
491 generatedColumn: util.getArg(aArgs, "column")
492 };
493
494 if (needle.generatedLine < 1) {
495 throw new Error("Line numbers must be >= 1");
496 }
497
498 if (needle.generatedColumn < 0) {
499 throw new Error("Column numbers must be >= 0");
500 }
501
502 let bias = util.getArg(aArgs, "bias", SourceMapConsumer.GREATEST_LOWER_BOUND);
503 if (bias == null) {
504 bias = SourceMapConsumer.GREATEST_LOWER_BOUND;
505 }
506
507 let mapping;
508 this._wasm.withMappingCallback(m => mapping = m, () => {
509 this._wasm.exports.original_location_for(
510 this._getMappingsPtr(),
511 needle.generatedLine - 1,
512 needle.generatedColumn,
513 bias
514 );
515 });
516
517 if (mapping) {
518 if (mapping.generatedLine === needle.generatedLine) {
519 let source = util.getArg(mapping, "source", null);
520 if (source !== null) {
521 source = this._sources.at(source);
522 source = util.computeSourceURL(this.sourceRoot, source, this._sourceMapURL);
523 }
524
525 let name = util.getArg(mapping, "name", null);
526 if (name !== null) {
527 name = this._names.at(name);
528 }
529
530 return {
531 source,
532 line: util.getArg(mapping, "originalLine", null),
533 column: util.getArg(mapping, "originalColumn", null),
534 name
535 };
536 }
537 }
538
539 return {
540 source: null,
541 line: null,
542 column: null,
543 name: null
544 };
545 }
546
547 /**
548 * Return true if we have the source content for every source in the source
549 * map, false otherwise.
550 */
551 hasContentsOfAllSources() {
552 if (!this.sourcesContent) {
553 return false;
554 }
555 return this.sourcesContent.length >= this._sources.size() &&
556 !this.sourcesContent.some(function(sc) { return sc == null; });
557 }
558
559 /**
560 * Returns the original source content. The only argument is the url of the
561 * original source file. Returns null if no original source content is
562 * available.
563 */
564 sourceContentFor(aSource, nullOnMissing) {
565 if (!this.sourcesContent) {
566 return null;
567 }
568
569 const index = this._findSourceIndex(aSource);
570 if (index >= 0) {
571 return this.sourcesContent[index];
572 }
573
574 let relativeSource = aSource;
575 if (this.sourceRoot != null) {
576 relativeSource = util.relative(this.sourceRoot, relativeSource);
577 }
578
579 let url;
580 if (this.sourceRoot != null
581 && (url = util.urlParse(this.sourceRoot))) {
582 // XXX: file:// URIs and absolute paths lead to unexpected behavior for
583 // many users. We can help them out when they expect file:// URIs to
584 // behave like it would if they were running a local HTTP server. See
585 // https://bugzilla.mozilla.org/show_bug.cgi?id=885597.
586 const fileUriAbsPath = relativeSource.replace(/^file:\/\//, "");
587 if (url.scheme == "file"
588 && this._sources.has(fileUriAbsPath)) {
589 return this.sourcesContent[this._sources.indexOf(fileUriAbsPath)];
590 }
591
592 if ((!url.path || url.path == "/")
593 && this._sources.has("/" + relativeSource)) {
594 return this.sourcesContent[this._sources.indexOf("/" + relativeSource)];
595 }
596 }
597
598 // This function is used recursively from
599 // IndexedSourceMapConsumer.prototype.sourceContentFor. In that case, we
600 // don't want to throw if we can't find the source - we just want to
601 // return null, so we provide a flag to exit gracefully.
602 if (nullOnMissing) {
603 return null;
604 }
605
606 throw new Error('"' + relativeSource + '" is not in the SourceMap.');
607 }
608
609 /**
610 * Returns the generated line and column information for the original source,
611 * line, and column positions provided. The only argument is an object with
612 * the following properties:
613 *
614 * - source: The filename of the original source.
615 * - line: The line number in the original source. The line number
616 * is 1-based.
617 * - column: The column number in the original source. The column
618 * number is 0-based.
619 * - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or
620 * 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the
621 * closest element that is smaller than or greater than the one we are
622 * searching for, respectively, if the exact element cannot be found.
623 * Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'.
624 *
625 * and an object is returned with the following properties:
626 *
627 * - line: The line number in the generated source, or null. The
628 * line number is 1-based.
629 * - column: The column number in the generated source, or null.
630 * The column number is 0-based.
631 */
632 generatedPositionFor(aArgs) {
633 let source = util.getArg(aArgs, "source");
634 source = this._findSourceIndex(source);
635 if (source < 0) {
636 return {
637 line: null,
638 column: null,
639 lastColumn: null
640 };
641 }
642
643 const needle = {
644 source,
645 originalLine: util.getArg(aArgs, "line"),
646 originalColumn: util.getArg(aArgs, "column")
647 };
648
649 if (needle.originalLine < 1) {
650 throw new Error("Line numbers must be >= 1");
651 }
652
653 if (needle.originalColumn < 0) {
654 throw new Error("Column numbers must be >= 0");
655 }
656
657 let bias = util.getArg(aArgs, "bias", SourceMapConsumer.GREATEST_LOWER_BOUND);
658 if (bias == null) {
659 bias = SourceMapConsumer.GREATEST_LOWER_BOUND;
660 }
661
662 let mapping;
663 this._wasm.withMappingCallback(m => mapping = m, () => {
664 this._wasm.exports.generated_location_for(
665 this._getMappingsPtr(),
666 needle.source,
667 needle.originalLine - 1,
668 needle.originalColumn,
669 bias
670 );
671 });
672
673 if (mapping) {
674 if (mapping.source === needle.source) {
675 let lastColumn = mapping.lastGeneratedColumn;
676 if (this._computedColumnSpans && lastColumn === null) {
677 lastColumn = Infinity;
678 }
679 return {
680 line: util.getArg(mapping, "generatedLine", null),
681 column: util.getArg(mapping, "generatedColumn", null),
682 lastColumn,
683 };
684 }
685 }
686
687 return {
688 line: null,
689 column: null,
690 lastColumn: null
691 };
692 }
693}
694
695BasicSourceMapConsumer.prototype.consumer = SourceMapConsumer;
696exports.BasicSourceMapConsumer = BasicSourceMapConsumer;
697
698/**
699 * An IndexedSourceMapConsumer instance represents a parsed source map which
700 * we can query for information. It differs from BasicSourceMapConsumer in
701 * that it takes "indexed" source maps (i.e. ones with a "sections" field) as
702 * input.
703 *
704 * The first parameter is a raw source map (either as a JSON string, or already
705 * parsed to an object). According to the spec for indexed source maps, they
706 * have the following attributes:
707 *
708 * - version: Which version of the source map spec this map is following.
709 * - file: Optional. The generated file this source map is associated with.
710 * - sections: A list of section definitions.
711 *
712 * Each value under the "sections" field has two fields:
713 * - offset: The offset into the original specified at which this section
714 * begins to apply, defined as an object with a "line" and "column"
715 * field.
716 * - map: A source map definition. This source map could also be indexed,
717 * but doesn't have to be.
718 *
719 * Instead of the "map" field, it's also possible to have a "url" field
720 * specifying a URL to retrieve a source map from, but that's currently
721 * unsupported.
722 *
723 * Here's an example source map, taken from the source map spec[0], but
724 * modified to omit a section which uses the "url" field.
725 *
726 * {
727 * version : 3,
728 * file: "app.js",
729 * sections: [{
730 * offset: {line:100, column:10},
731 * map: {
732 * version : 3,
733 * file: "section.js",
734 * sources: ["foo.js", "bar.js"],
735 * names: ["src", "maps", "are", "fun"],
736 * mappings: "AAAA,E;;ABCDE;"
737 * }
738 * }],
739 * }
740 *
741 * The second parameter, if given, is a string whose value is the URL
742 * at which the source map was found. This URL is used to compute the
743 * sources array.
744 *
745 * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#heading=h.535es3xeprgt
746 */
747class IndexedSourceMapConsumer extends SourceMapConsumer {
748 constructor(aSourceMap, aSourceMapURL) {
749 return super(INTERNAL).then(that => {
750 let sourceMap = aSourceMap;
751 if (typeof aSourceMap === "string") {
752 sourceMap = util.parseSourceMapInput(aSourceMap);
753 }
754
755 const version = util.getArg(sourceMap, "version");
756 const sections = util.getArg(sourceMap, "sections");
757
758 if (version != that._version) {
759 throw new Error("Unsupported version: " + version);
760 }
761
762 that._sources = new ArraySet();
763 that._names = new ArraySet();
764 that.__generatedMappings = null;
765 that.__originalMappings = null;
766 that.__generatedMappingsUnsorted = null;
767 that.__originalMappingsUnsorted = null;
768
769 let lastOffset = {
770 line: -1,
771 column: 0
772 };
773 return Promise.all(sections.map(s => {
774 if (s.url) {
775 // The url field will require support for asynchronicity.
776 // See https://github.com/mozilla/source-map/issues/16
777 throw new Error("Support for url field in sections not implemented.");
778 }
779 const offset = util.getArg(s, "offset");
780 const offsetLine = util.getArg(offset, "line");
781 const offsetColumn = util.getArg(offset, "column");
782
783 if (offsetLine < lastOffset.line ||
784 (offsetLine === lastOffset.line && offsetColumn < lastOffset.column)) {
785 throw new Error("Section offsets must be ordered and non-overlapping.");
786 }
787 lastOffset = offset;
788
789 const cons = new SourceMapConsumer(util.getArg(s, "map"), aSourceMapURL);
790 return cons.then(consumer => {
791 return {
792 generatedOffset: {
793 // The offset fields are 0-based, but we use 1-based indices when
794 // encoding/decoding from VLQ.
795 generatedLine: offsetLine + 1,
796 generatedColumn: offsetColumn + 1
797 },
798 consumer
799 };
800 });
801 })).then(s => {
802 that._sections = s;
803 return that;
804 });
805 });
806 }
807
808 // `__generatedMappings` and `__originalMappings` are arrays that hold the
809 // parsed mapping coordinates from the source map's "mappings" attribute. They
810 // are lazily instantiated, accessed via the `_generatedMappings` and
811 // `_originalMappings` getters respectively, and we only parse the mappings
812 // and create these arrays once queried for a source location. We jump through
813 // these hoops because there can be many thousands of mappings, and parsing
814 // them is expensive, so we only want to do it if we must.
815 //
816 // Each object in the arrays is of the form:
817 //
818 // {
819 // generatedLine: The line number in the generated code,
820 // generatedColumn: The column number in the generated code,
821 // source: The path to the original source file that generated this
822 // chunk of code,
823 // originalLine: The line number in the original source that
824 // corresponds to this chunk of generated code,
825 // originalColumn: The column number in the original source that
826 // corresponds to this chunk of generated code,
827 // name: The name of the original symbol which generated this chunk of
828 // code.
829 // }
830 //
831 // All properties except for `generatedLine` and `generatedColumn` can be
832 // `null`.
833 //
834 // `_generatedMappings` is ordered by the generated positions.
835 //
836 // `_originalMappings` is ordered by the original positions.
837 get _generatedMappings() {
838 if (!this.__generatedMappings) {
839 this._sortGeneratedMappings();
840 }
841
842 return this.__generatedMappings;
843 }
844
845 get _originalMappings() {
846 if (!this.__originalMappings) {
847 this._sortOriginalMappings();
848 }
849
850 return this.__originalMappings;
851 }
852
853 get _generatedMappingsUnsorted() {
854 if (!this.__generatedMappingsUnsorted) {
855 this._parseMappings(this._mappings, this.sourceRoot);
856 }
857
858 return this.__generatedMappingsUnsorted;
859 }
860
861 get _originalMappingsUnsorted() {
862 if (!this.__originalMappingsUnsorted) {
863 this._parseMappings(this._mappings, this.sourceRoot);
864 }
865
866 return this.__originalMappingsUnsorted;
867 }
868
869 _sortGeneratedMappings() {
870 const mappings = this._generatedMappingsUnsorted;
871 mappings.sort(util.compareByGeneratedPositionsDeflated);
872 this.__generatedMappings = mappings;
873 }
874
875 _sortOriginalMappings() {
876 const mappings = this._originalMappingsUnsorted;
877 mappings.sort(util.compareByOriginalPositions);
878 this.__originalMappings = mappings;
879 }
880
881 /**
882 * The list of original sources.
883 */
884 get sources() {
885 const sources = [];
886 for (let i = 0; i < this._sections.length; i++) {
887 for (let j = 0; j < this._sections[i].consumer.sources.length; j++) {
888 sources.push(this._sections[i].consumer.sources[j]);
889 }
890 }
891 return sources;
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. The line number
900 * is 1-based.
901 * - column: The column number in the generated source. The column
902 * number is 0-based.
903 *
904 * and an object is returned with the following properties:
905 *
906 * - source: The original source file, or null.
907 * - line: The line number in the original source, or null. The
908 * line number is 1-based.
909 * - column: The column number in the original source, or null. The
910 * column number is 0-based.
911 * - name: The original identifier, or null.
912 */
913 originalPositionFor(aArgs) {
914 const needle = {
915 generatedLine: util.getArg(aArgs, "line"),
916 generatedColumn: util.getArg(aArgs, "column")
917 };
918
919 // Find the section containing the generated position we're trying to map
920 // to an original position.
921 const sectionIndex = binarySearch.search(needle, this._sections,
922 function(aNeedle, section) {
923 const cmp = aNeedle.generatedLine - section.generatedOffset.generatedLine;
924 if (cmp) {
925 return cmp;
926 }
927
928 return (aNeedle.generatedColumn -
929 section.generatedOffset.generatedColumn);
930 });
931 const section = this._sections[sectionIndex];
932
933 if (!section) {
934 return {
935 source: null,
936 line: null,
937 column: null,
938 name: null
939 };
940 }
941
942 return section.consumer.originalPositionFor({
943 line: needle.generatedLine -
944 (section.generatedOffset.generatedLine - 1),
945 column: needle.generatedColumn -
946 (section.generatedOffset.generatedLine === needle.generatedLine
947 ? section.generatedOffset.generatedColumn - 1
948 : 0),
949 bias: aArgs.bias
950 });
951 }
952
953 /**
954 * Return true if we have the source content for every source in the source
955 * map, false otherwise.
956 */
957 hasContentsOfAllSources() {
958 return this._sections.every(function(s) {
959 return s.consumer.hasContentsOfAllSources();
960 });
961 }
962
963 /**
964 * Returns the original source content. The only argument is the url of the
965 * original source file. Returns null if no original source content is
966 * available.
967 */
968 sourceContentFor(aSource, nullOnMissing) {
969 for (let i = 0; i < this._sections.length; i++) {
970 const section = this._sections[i];
971
972 const content = section.consumer.sourceContentFor(aSource, true);
973 if (content) {
974 return content;
975 }
976 }
977 if (nullOnMissing) {
978 return null;
979 }
980 throw new Error('"' + aSource + '" is not in the SourceMap.');
981 }
982
983 /**
984 * Returns the generated line and column information for the original source,
985 * line, and column positions provided. The only argument is an object with
986 * the following properties:
987 *
988 * - source: The filename of the original source.
989 * - line: The line number in the original source. The line number
990 * is 1-based.
991 * - column: The column number in the original source. The column
992 * number is 0-based.
993 *
994 * and an object is returned with the following properties:
995 *
996 * - line: The line number in the generated source, or null. The
997 * line number is 1-based.
998 * - column: The column number in the generated source, or null.
999 * The column number is 0-based.
1000 */
1001 generatedPositionFor(aArgs) {
1002 for (let i = 0; i < this._sections.length; i++) {
1003 const section = this._sections[i];
1004
1005 // Only consider this section if the requested source is in the list of
1006 // sources of the consumer.
1007 if (section.consumer._findSourceIndex(util.getArg(aArgs, "source")) === -1) {
1008 continue;
1009 }
1010 const generatedPosition = section.consumer.generatedPositionFor(aArgs);
1011 if (generatedPosition) {
1012 const ret = {
1013 line: generatedPosition.line +
1014 (section.generatedOffset.generatedLine - 1),
1015 column: generatedPosition.column +
1016 (section.generatedOffset.generatedLine === generatedPosition.line
1017 ? section.generatedOffset.generatedColumn - 1
1018 : 0)
1019 };
1020 return ret;
1021 }
1022 }
1023
1024 return {
1025 line: null,
1026 column: null
1027 };
1028 }
1029
1030 /**
1031 * Parse the mappings in a string in to a data structure which we can easily
1032 * query (the ordered arrays in the `this.__generatedMappings` and
1033 * `this.__originalMappings` properties).
1034 */
1035 _parseMappings(aStr, aSourceRoot) {
1036 const generatedMappings = this.__generatedMappingsUnsorted = [];
1037 const originalMappings = this.__originalMappingsUnsorted = [];
1038 for (let i = 0; i < this._sections.length; i++) {
1039 const section = this._sections[i];
1040
1041 const sectionMappings = [];
1042 section.consumer.eachMapping(m => sectionMappings.push(m));
1043
1044 for (let j = 0; j < sectionMappings.length; j++) {
1045 const mapping = sectionMappings[j];
1046
1047 // TODO: test if null is correct here. The original code used
1048 // `source`, which would actually have gotten used as null because
1049 // var's get hoisted.
1050 // See: https://github.com/mozilla/source-map/issues/333
1051 let source = util.computeSourceURL(section.consumer.sourceRoot, null, this._sourceMapURL);
1052 this._sources.add(source);
1053 source = this._sources.indexOf(source);
1054
1055 let name = null;
1056 if (mapping.name) {
1057 this._names.add(mapping.name);
1058 name = this._names.indexOf(mapping.name);
1059 }
1060
1061 // The mappings coming from the consumer for the section have
1062 // generated positions relative to the start of the section, so we
1063 // need to offset them to be relative to the start of the concatenated
1064 // generated file.
1065 const adjustedMapping = {
1066 source,
1067 generatedLine: mapping.generatedLine +
1068 (section.generatedOffset.generatedLine - 1),
1069 generatedColumn: mapping.generatedColumn +
1070 (section.generatedOffset.generatedLine === mapping.generatedLine
1071 ? section.generatedOffset.generatedColumn - 1
1072 : 0),
1073 originalLine: mapping.originalLine,
1074 originalColumn: mapping.originalColumn,
1075 name
1076 };
1077
1078 generatedMappings.push(adjustedMapping);
1079 if (typeof adjustedMapping.originalLine === "number") {
1080 originalMappings.push(adjustedMapping);
1081 }
1082 }
1083 }
1084 }
1085
1086 eachMapping(aCallback, aContext, aOrder) {
1087 const context = aContext || null;
1088 const order = aOrder || SourceMapConsumer.GENERATED_ORDER;
1089
1090 let mappings;
1091 switch (order) {
1092 case SourceMapConsumer.GENERATED_ORDER:
1093 mappings = this._generatedMappings;
1094 break;
1095 case SourceMapConsumer.ORIGINAL_ORDER:
1096 mappings = this._originalMappings;
1097 break;
1098 default:
1099 throw new Error("Unknown order of iteration.");
1100 }
1101
1102 const sourceRoot = this.sourceRoot;
1103 mappings.map(function(mapping) {
1104 let source = null;
1105 if (mapping.source !== null) {
1106 source = this._sources.at(mapping.source);
1107 source = util.computeSourceURL(sourceRoot, source, this._sourceMapURL);
1108 }
1109 return {
1110 source,
1111 generatedLine: mapping.generatedLine,
1112 generatedColumn: mapping.generatedColumn,
1113 originalLine: mapping.originalLine,
1114 originalColumn: mapping.originalColumn,
1115 name: mapping.name === null ? null : this._names.at(mapping.name)
1116 };
1117 }, this).forEach(aCallback, context);
1118 }
1119
1120 /**
1121 * Find the mapping that best matches the hypothetical "needle" mapping that
1122 * we are searching for in the given "haystack" of mappings.
1123 */
1124 _findMapping(aNeedle, aMappings, aLineName,
1125 aColumnName, aComparator, aBias) {
1126 // To return the position we are searching for, we must first find the
1127 // mapping for the given position and then return the opposite position it
1128 // points to. Because the mappings are sorted, we can use binary search to
1129 // find the best mapping.
1130
1131 if (aNeedle[aLineName] <= 0) {
1132 throw new TypeError("Line must be greater than or equal to 1, got "
1133 + aNeedle[aLineName]);
1134 }
1135 if (aNeedle[aColumnName] < 0) {
1136 throw new TypeError("Column must be greater than or equal to 0, got "
1137 + aNeedle[aColumnName]);
1138 }
1139
1140 return binarySearch.search(aNeedle, aMappings, aComparator, aBias);
1141 }
1142
1143 allGeneratedPositionsFor(aArgs) {
1144 const line = util.getArg(aArgs, "line");
1145
1146 // When there is no exact match, BasicSourceMapConsumer.prototype._findMapping
1147 // returns the index of the closest mapping less than the needle. By
1148 // setting needle.originalColumn to 0, we thus find the last mapping for
1149 // the given line, provided such a mapping exists.
1150 const needle = {
1151 source: util.getArg(aArgs, "source"),
1152 originalLine: line,
1153 originalColumn: util.getArg(aArgs, "column", 0)
1154 };
1155
1156 needle.source = this._findSourceIndex(needle.source);
1157 if (needle.source < 0) {
1158 return [];
1159 }
1160
1161 if (needle.originalLine < 1) {
1162 throw new Error("Line numbers must be >= 1");
1163 }
1164
1165 if (needle.originalColumn < 0) {
1166 throw new Error("Column numbers must be >= 0");
1167 }
1168
1169 const mappings = [];
1170
1171 let index = this._findMapping(needle,
1172 this._originalMappings,
1173 "originalLine",
1174 "originalColumn",
1175 util.compareByOriginalPositions,
1176 binarySearch.LEAST_UPPER_BOUND);
1177 if (index >= 0) {
1178 let mapping = this._originalMappings[index];
1179
1180 if (aArgs.column === undefined) {
1181 const originalLine = mapping.originalLine;
1182
1183 // Iterate until either we run out of mappings, or we run into
1184 // a mapping for a different line than the one we found. Since
1185 // mappings are sorted, this is guaranteed to find all mappings for
1186 // the line we found.
1187 while (mapping && mapping.originalLine === originalLine) {
1188 let lastColumn = mapping.lastGeneratedColumn;
1189 if (this._computedColumnSpans && lastColumn === null) {
1190 lastColumn = Infinity;
1191 }
1192 mappings.push({
1193 line: util.getArg(mapping, "generatedLine", null),
1194 column: util.getArg(mapping, "generatedColumn", null),
1195 lastColumn,
1196 });
1197
1198 mapping = this._originalMappings[++index];
1199 }
1200 } else {
1201 const originalColumn = mapping.originalColumn;
1202
1203 // Iterate until either we run out of mappings, or we run into
1204 // a mapping for a different line than the one we were searching for.
1205 // Since mappings are sorted, this is guaranteed to find all mappings for
1206 // the line we are searching for.
1207 while (mapping &&
1208 mapping.originalLine === line &&
1209 mapping.originalColumn == originalColumn) {
1210 let lastColumn = mapping.lastGeneratedColumn;
1211 if (this._computedColumnSpans && lastColumn === null) {
1212 lastColumn = Infinity;
1213 }
1214 mappings.push({
1215 line: util.getArg(mapping, "generatedLine", null),
1216 column: util.getArg(mapping, "generatedColumn", null),
1217 lastColumn,
1218 });
1219
1220 mapping = this._originalMappings[++index];
1221 }
1222 }
1223 }
1224
1225 return mappings;
1226 }
1227
1228 destroy() {
1229 for (let i = 0; i < this._sections.length; i++) {
1230 this._sections[i].consumer.destroy();
1231 }
1232 }
1233}
1234exports.IndexedSourceMapConsumer = IndexedSourceMapConsumer;
1235
1236/*
1237 * Cheat to get around inter-twingled classes. `factory()` can be at the end
1238 * where it has access to non-hoisted classes, but it gets hoisted itself.
1239 */
1240function _factory(aSourceMap, aSourceMapURL) {
1241 let sourceMap = aSourceMap;
1242 if (typeof aSourceMap === "string") {
1243 sourceMap = util.parseSourceMapInput(aSourceMap);
1244 }
1245
1246 const consumer = sourceMap.sections != null
1247 ? new IndexedSourceMapConsumer(sourceMap, aSourceMapURL)
1248 : new BasicSourceMapConsumer(sourceMap, aSourceMapURL);
1249 return Promise.resolve(consumer);
1250}
1251
1252function _factoryBSM(aSourceMap, aSourceMapURL) {
1253 return BasicSourceMapConsumer.fromSourceMap(aSourceMap, aSourceMapURL);
1254}
Note: See TracBrowser for help on using the repository browser.