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