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
/**
18
* A SourceMapConsumer instance represents a parsed source map which we can
19
* query for information about the original file positions by giving it a file
20
* position in the generated source.
21
*
22
* The only parameter is the raw source map (either as a JSON string, or
23
* already parsed to an object). According to the spec, source maps have the
24
* following attributes:
25
*
26
* - version: Which version of the source map spec this map is following.
27
* - sources: An array of URLs to the original source files.
28
* - names: An array of identifiers which can be referrenced by individual mappings.
29
* - sourceRoot: Optional. The URL root from which all sources are relative.
30
* - sourcesContent: Optional. An array of contents of the original source files.
31
* - mappings: A string of base64 VLQs which contain the actual mappings.
32
* - file: The generated file this source map is associated with.
33
*
34
* Here is an example source map, taken from the source map spec[0]:
35
*
36
* {
37
* version : 3,
38
* file: "out.js",
39
* sourceRoot : "",
40
* sources: ["foo.js", "bar.js"],
41
* names: ["src", "maps", "are", "fun"],
42
* mappings: "AA,AB;;ABCDE;"
43
* }
44
*
45
* [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit?pli=1#
46
*/
47
function SourceMapConsumer(aSourceMap) {
48
var sourceMap = aSourceMap;
49
if (typeof aSourceMap === 'string') {
50
sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, ''));
51
}
52
53
var version = util.getArg(sourceMap, 'version');
54
var sources = util.getArg(sourceMap, 'sources');
55
// Sass 3.3 leaves out the 'names' array, so we deviate from the spec (which
56
// requires the array) to play nice here.
57
var names = util.getArg(sourceMap, 'names', []);
58
var sourceRoot = util.getArg(sourceMap, 'sourceRoot', null);
59
var sourcesContent = util.getArg(sourceMap, 'sourcesContent', null);
60
var mappings = util.getArg(sourceMap, 'mappings');
61
var file = util.getArg(sourceMap, 'file', null);
62
63
// Once again, Sass deviates from the spec and supplies the version as a
64
// string rather than a number, so we use loose equality checking here.
65
if (version != this._version) {
66
throw new Error('Unsupported version: ' + version);
67
}
68
69
// Pass `true` below to allow duplicate names and sources. While source maps
70
// are intended to be compressed and deduplicated, the TypeScript compiler
71
// sometimes generates source maps with duplicates in them. See Github issue
72
// #72 and bugzil.la/889492.
73
this._names = ArraySet.fromArray(names, true);
74
this._sources = ArraySet.fromArray(sources, true);
75
76
this.sourceRoot = sourceRoot;
77
this.sourcesContent = sourcesContent;
78
this._mappings = mappings;
79
this.file = file;
80
}
81
82
/**
83
* Create a SourceMapConsumer from a SourceMapGenerator.
84
*
85
* @param SourceMapGenerator aSourceMap
86
* The source map that will be consumed.
87
* @returns SourceMapConsumer
88
*/
89
SourceMapConsumer.fromSourceMap =
90
function SourceMapConsumer_fromSourceMap(aSourceMap) {
91
var smc = Object.create(SourceMapConsumer.prototype);
92
93
smc._names = ArraySet.fromArray(aSourceMap._names.toArray(), true);
94
smc._sources = ArraySet.fromArray(aSourceMap._sources.toArray(), true);
95
smc.sourceRoot = aSourceMap._sourceRoot;
96
smc.sourcesContent = aSourceMap._generateSourcesContent(smc._sources.toArray(),
97
smc.sourceRoot);
98
smc.file = aSourceMap._file;
99
100
smc.__generatedMappings = aSourceMap._mappings.slice()
101
.sort(util.compareByGeneratedPositions);
102
smc.__originalMappings = aSourceMap._mappings.slice()
103
.sort(util.compareByOriginalPositions);
104
105
return smc;
106
};
107
108
/**
109
* The version of the source mapping spec that we are consuming.
110
*/
111
SourceMapConsumer.prototype._version = 3;
112
113
/**
114
* The list of original sources.
115
*/
116
Object.defineProperty(SourceMapConsumer.prototype, 'sources', {
117
get: function () {
118
return this._sources.toArray().map(function (s) {
119
return this.sourceRoot ? util.join(this.sourceRoot, s) : s;
120
}, this);
121
}
122
});
123
124
// `__generatedMappings` and `__originalMappings` are arrays that hold the
125
// parsed mapping coordinates from the source map's "mappings" attribute. They
126
// are lazily instantiated, accessed via the `_generatedMappings` and
127
// `_originalMappings` getters respectively, and we only parse the mappings
128
// and create these arrays once queried for a source location. We jump through
129
// these hoops because there can be many thousands of mappings, and parsing
130
// them is expensive, so we only want to do it if we must.
131
//
132
// Each object in the arrays is of the form:
133
//
134
// {
135
// generatedLine: The line number in the generated code,
136
// generatedColumn: The column number in the generated code,
137
// source: The path to the original source file that generated this
138
// chunk of code,
139
// originalLine: The line number in the original source that
140
// corresponds to this chunk of generated code,
141
// originalColumn: The column number in the original source that
142
// corresponds to this chunk of generated code,
143
// name: The name of the original symbol which generated this chunk of
144
// code.
145
// }
146
//
147
// All properties except for `generatedLine` and `generatedColumn` can be
148
// `null`.
149
//
150
// `_generatedMappings` is ordered by the generated positions.
151
//
152
// `_originalMappings` is ordered by the original positions.
153
154
SourceMapConsumer.prototype.__generatedMappings = null;
155
Object.defineProperty(SourceMapConsumer.prototype, '_generatedMappings', {
156
get: function () {
157
if (!this.__generatedMappings) {
158
this.__generatedMappings = [];
159
this.__originalMappings = [];
160
this._parseMappings(this._mappings, this.sourceRoot);
161
}
162
163
return this.__generatedMappings;
164
}
165
});
166
167
SourceMapConsumer.prototype.__originalMappings = null;
168
Object.defineProperty(SourceMapConsumer.prototype, '_originalMappings', {
169
get: function () {
170
if (!this.__originalMappings) {
171
this.__generatedMappings = [];
172
this.__originalMappings = [];
173
this._parseMappings(this._mappings, this.sourceRoot);
174
}
175
176
return this.__originalMappings;
177
}
178
});
179
180
/**
181
* Parse the mappings in a string in to a data structure which we can easily
182
* query (the ordered arrays in the `this.__generatedMappings` and
183
* `this.__originalMappings` properties).
184
*/
185
SourceMapConsumer.prototype._parseMappings =
186
function SourceMapConsumer_parseMappings(aStr, aSourceRoot) {
187
var generatedLine = 1;
188
var previousGeneratedColumn = 0;
189
var previousOriginalLine = 0;
190
var previousOriginalColumn = 0;
191
var previousSource = 0;
192
var previousName = 0;
193
var mappingSeparator = /^[,;]/;
194
var str = aStr;
195
var mapping;
196
var temp;
197
198
while (str.length > 0) {
199
if (str.charAt(0) === ';') {
200
generatedLine++;
201
str = str.slice(1);
202
previousGeneratedColumn = 0;
203
}
204
else if (str.charAt(0) === ',') {
205
str = str.slice(1);
206
}
207
else {
208
mapping = {};
209
mapping.generatedLine = generatedLine;
210
211
// Generated column.
212
temp = base64VLQ.decode(str);
213
mapping.generatedColumn = previousGeneratedColumn + temp.value;
214
previousGeneratedColumn = mapping.generatedColumn;
215
str = temp.rest;
216
217
if (str.length > 0 && !mappingSeparator.test(str.charAt(0))) {
218
// Original source.
219
temp = base64VLQ.decode(str);
220
mapping.source = this._sources.at(previousSource + temp.value);
221
previousSource += temp.value;
222
str = temp.rest;
223
if (str.length === 0 || mappingSeparator.test(str.charAt(0))) {
224
throw new Error('Found a source, but no line and column');
225
}
226
227
// Original line.
228
temp = base64VLQ.decode(str);
229
mapping.originalLine = previousOriginalLine + temp.value;
230
previousOriginalLine = mapping.originalLine;
231
// Lines are stored 0-based
232
mapping.originalLine += 1;
233
str = temp.rest;
234
if (str.length === 0 || mappingSeparator.test(str.charAt(0))) {
235
throw new Error('Found a source and line, but no column');
236
}
237
238
// Original column.
239
temp = base64VLQ.decode(str);
240
mapping.originalColumn = previousOriginalColumn + temp.value;
241
previousOriginalColumn = mapping.originalColumn;
242
str = temp.rest;
243
244
if (str.length > 0 && !mappingSeparator.test(str.charAt(0))) {
245
// Original name.
246
temp = base64VLQ.decode(str);
247
mapping.name = this._names.at(previousName + temp.value);
248
previousName += temp.value;
249
str = temp.rest;
250
}
251
}
252
253
this.__generatedMappings.push(mapping);
254
if (typeof mapping.originalLine === 'number') {
255
this.__originalMappings.push(mapping);
256
}
257
}
258
}
259
260
this.__originalMappings.sort(util.compareByOriginalPositions);
261
};
262
263
/**
264
* Find the mapping that best matches the hypothetical "needle" mapping that
265
* we are searching for in the given "haystack" of mappings.
266
*/
267
SourceMapConsumer.prototype._findMapping =
268
function SourceMapConsumer_findMapping(aNeedle, aMappings, aLineName,
269
aColumnName, aComparator) {
270
// To return the position we are searching for, we must first find the
271
// mapping for the given position and then return the opposite position it
272
// points to. Because the mappings are sorted, we can use binary search to
273
// find the best mapping.
274
275
if (aNeedle[aLineName] <= 0) {
276
throw new TypeError('Line must be greater than or equal to 1, got '
277
+ aNeedle[aLineName]);
278
}
279
if (aNeedle[aColumnName] < 0) {
280
throw new TypeError('Column must be greater than or equal to 0, got '
281
+ aNeedle[aColumnName]);
282
}
283
284
return binarySearch.search(aNeedle, aMappings, aComparator);
285
};
286
287
/**
288
* Returns the original source, line, and column information for the generated
289
* source's line and column positions provided. The only argument is an object
290
* with the following properties:
291
*
292
* - line: The line number in the generated source.
293
* - column: The column number in the generated source.
294
*
295
* and an object is returned with the following properties:
296
*
297
* - source: The original source file, or null.
298
* - line: The line number in the original source, or null.
299
* - column: The column number in the original source, or null.
300
* - name: The original identifier, or null.
301
*/
302
SourceMapConsumer.prototype.originalPositionFor =
303
function SourceMapConsumer_originalPositionFor(aArgs) {
304
var needle = {
305
generatedLine: util.getArg(aArgs, 'line'),
306
generatedColumn: util.getArg(aArgs, 'column')
307
};
308
309
var mapping = this._findMapping(needle,
310
this._generatedMappings,
311
"generatedLine",
312
"generatedColumn",
313
util.compareByGeneratedPositions);
314
315
if (mapping) {
316
var source = util.getArg(mapping, 'source', null);
317
if (source && this.sourceRoot) {
318
source = util.join(this.sourceRoot, source);
319
}
320
return {
321
source: source,
322
line: util.getArg(mapping, 'originalLine', null),
323
column: util.getArg(mapping, 'originalColumn', null),
324
name: util.getArg(mapping, 'name', null)
325
};
326
}
327
328
return {
329
source: null,
330
line: null,
331
column: null,
332
name: null
333
};
334
};
335
336
/**
337
* Returns the original source content. The only argument is the url of the
338
* original source file. Returns null if no original source content is
339
* availible.
340
*/
341
SourceMapConsumer.prototype.sourceContentFor =
342
function SourceMapConsumer_sourceContentFor(aSource) {
343
if (!this.sourcesContent) {
344
return null;
345
}
346
347
if (this.sourceRoot) {
348
aSource = util.relative(this.sourceRoot, aSource);
349
}
350
351
if (this._sources.has(aSource)) {
352
return this.sourcesContent[this._sources.indexOf(aSource)];
353
}
354
355
var url;
356
if (this.sourceRoot
357
&& (url = util.urlParse(this.sourceRoot))) {
358
// XXX: file:// URIs and absolute paths lead to unexpected behavior for
359
// many users. We can help them out when they expect file:// URIs to
360
// behave like it would if they were running a local HTTP server. See
361
// https://bugzilla.mozilla.org/show_bug.cgi?id=885597.
362
var fileUriAbsPath = aSource.replace(/^file:\/\//, "");
363
if (url.scheme == "file"
364
&& this._sources.has(fileUriAbsPath)) {
365
return this.sourcesContent[this._sources.indexOf(fileUriAbsPath)]
366
}
367
368
if ((!url.path || url.path == "/")
369
&& this._sources.has("/" + aSource)) {
370
return this.sourcesContent[this._sources.indexOf("/" + aSource)];
371
}
372
}
373
374
throw new Error('"' + aSource + '" is not in the SourceMap.');
375
};
376
377
/**
378
* Returns the generated line and column information for the original source,
379
* line, and column positions provided. The only argument is an object with
380
* the following properties:
381
*
382
* - source: The filename of the original source.
383
* - line: The line number in the original source.
384
* - column: The column number in the original source.
385
*
386
* and an object is returned with the following properties:
387
*
388
* - line: The line number in the generated source, or null.
389
* - column: The column number in the generated source, or null.
390
*/
391
SourceMapConsumer.prototype.generatedPositionFor =
392
function SourceMapConsumer_generatedPositionFor(aArgs) {
393
var needle = {
394
source: util.getArg(aArgs, 'source'),
395
originalLine: util.getArg(aArgs, 'line'),
396
originalColumn: util.getArg(aArgs, 'column')
397
};
398
399
if (this.sourceRoot) {
400
needle.source = util.relative(this.sourceRoot, needle.source);
401
}
402
403
var mapping = this._findMapping(needle,
404
this._originalMappings,
405
"originalLine",
406
"originalColumn",
407
util.compareByOriginalPositions);
408
409
if (mapping) {
410
return {
411
line: util.getArg(mapping, 'generatedLine', null),
412
column: util.getArg(mapping, 'generatedColumn', null)
413
};
414
}
415
416
return {
417
line: null,
418
column: null
419
};
420
};
421
422
SourceMapConsumer.GENERATED_ORDER = 1;
423
SourceMapConsumer.ORIGINAL_ORDER = 2;
424
425
/**
426
* Iterate over each mapping between an original source/line/column and a
427
* generated line/column in this source map.
428
*
429
* @param Function aCallback
430
* The function that is called with each mapping.
431
* @param Object aContext
432
* Optional. If specified, this object will be the value of `this` every
433
* time that `aCallback` is called.
434
* @param aOrder
435
* Either `SourceMapConsumer.GENERATED_ORDER` or
436
* `SourceMapConsumer.ORIGINAL_ORDER`. Specifies whether you want to
437
* iterate over the mappings sorted by the generated file's line/column
438
* order or the original's source/line/column order, respectively. Defaults to
439
* `SourceMapConsumer.GENERATED_ORDER`.
440
*/
441
SourceMapConsumer.prototype.eachMapping =
442
function SourceMapConsumer_eachMapping(aCallback, aContext, aOrder) {
443
var context = aContext || null;
444
var order = aOrder || SourceMapConsumer.GENERATED_ORDER;
445
446
var mappings;
447
switch (order) {
448
case SourceMapConsumer.GENERATED_ORDER:
449
mappings = this._generatedMappings;
450
break;
451
case SourceMapConsumer.ORIGINAL_ORDER:
452
mappings = this._originalMappings;
453
break;
454
default:
455
throw new Error("Unknown order of iteration.");
456
}
457
458
var sourceRoot = this.sourceRoot;
459
mappings.map(function (mapping) {
460
var source = mapping.source;
461
if (source && sourceRoot) {
462
source = util.join(sourceRoot, source);
463
}
464
return {
465
source: source,
466
generatedLine: mapping.generatedLine,
467
generatedColumn: mapping.generatedColumn,
468
originalLine: mapping.originalLine,
469
originalColumn: mapping.originalColumn,
470
name: mapping.name
471
};
472
}).forEach(aCallback, context);
473
};
474
475
exports.SourceMapConsumer = SourceMapConsumer;
476
477
});
478
479