Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
80766 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: Optional. 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.__generatedMappings.sort(util.compareByGeneratedPositions);
261
this.__originalMappings.sort(util.compareByOriginalPositions);
262
};
263
264
/**
265
* Find the mapping that best matches the hypothetical "needle" mapping that
266
* we are searching for in the given "haystack" of mappings.
267
*/
268
SourceMapConsumer.prototype._findMapping =
269
function SourceMapConsumer_findMapping(aNeedle, aMappings, aLineName,
270
aColumnName, aComparator) {
271
// To return the position we are searching for, we must first find the
272
// mapping for the given position and then return the opposite position it
273
// points to. Because the mappings are sorted, we can use binary search to
274
// find the best mapping.
275
276
if (aNeedle[aLineName] <= 0) {
277
throw new TypeError('Line must be greater than or equal to 1, got '
278
+ aNeedle[aLineName]);
279
}
280
if (aNeedle[aColumnName] < 0) {
281
throw new TypeError('Column must be greater than or equal to 0, got '
282
+ aNeedle[aColumnName]);
283
}
284
285
return binarySearch.search(aNeedle, aMappings, aComparator);
286
};
287
288
/**
289
* Returns the original source, line, and column information for the generated
290
* source's line and column positions provided. The only argument is an object
291
* with the following properties:
292
*
293
* - line: The line number in the generated source.
294
* - column: The column number in the generated source.
295
*
296
* and an object is returned with the following properties:
297
*
298
* - source: The original source file, or null.
299
* - line: The line number in the original source, or null.
300
* - column: The column number in the original source, or null.
301
* - name: The original identifier, or null.
302
*/
303
SourceMapConsumer.prototype.originalPositionFor =
304
function SourceMapConsumer_originalPositionFor(aArgs) {
305
var needle = {
306
generatedLine: util.getArg(aArgs, 'line'),
307
generatedColumn: util.getArg(aArgs, 'column')
308
};
309
310
var mapping = this._findMapping(needle,
311
this._generatedMappings,
312
"generatedLine",
313
"generatedColumn",
314
util.compareByGeneratedPositions);
315
316
if (mapping && mapping.generatedLine === needle.generatedLine) {
317
var source = util.getArg(mapping, 'source', null);
318
if (source && this.sourceRoot) {
319
source = util.join(this.sourceRoot, source);
320
}
321
return {
322
source: source,
323
line: util.getArg(mapping, 'originalLine', null),
324
column: util.getArg(mapping, 'originalColumn', null),
325
name: util.getArg(mapping, 'name', null)
326
};
327
}
328
329
return {
330
source: null,
331
line: null,
332
column: null,
333
name: null
334
};
335
};
336
337
/**
338
* Returns the original source content. The only argument is the url of the
339
* original source file. Returns null if no original source content is
340
* availible.
341
*/
342
SourceMapConsumer.prototype.sourceContentFor =
343
function SourceMapConsumer_sourceContentFor(aSource) {
344
if (!this.sourcesContent) {
345
return null;
346
}
347
348
if (this.sourceRoot) {
349
aSource = util.relative(this.sourceRoot, aSource);
350
}
351
352
if (this._sources.has(aSource)) {
353
return this.sourcesContent[this._sources.indexOf(aSource)];
354
}
355
356
var url;
357
if (this.sourceRoot
358
&& (url = util.urlParse(this.sourceRoot))) {
359
// XXX: file:// URIs and absolute paths lead to unexpected behavior for
360
// many users. We can help them out when they expect file:// URIs to
361
// behave like it would if they were running a local HTTP server. See
362
// https://bugzilla.mozilla.org/show_bug.cgi?id=885597.
363
var fileUriAbsPath = aSource.replace(/^file:\/\//, "");
364
if (url.scheme == "file"
365
&& this._sources.has(fileUriAbsPath)) {
366
return this.sourcesContent[this._sources.indexOf(fileUriAbsPath)]
367
}
368
369
if ((!url.path || url.path == "/")
370
&& this._sources.has("/" + aSource)) {
371
return this.sourcesContent[this._sources.indexOf("/" + aSource)];
372
}
373
}
374
375
throw new Error('"' + aSource + '" is not in the SourceMap.');
376
};
377
378
/**
379
* Returns the generated line and column information for the original source,
380
* line, and column positions provided. The only argument is an object with
381
* the following properties:
382
*
383
* - source: The filename of the original source.
384
* - line: The line number in the original source.
385
* - column: The column number in the original source.
386
*
387
* and an object is returned with the following properties:
388
*
389
* - line: The line number in the generated source, or null.
390
* - column: The column number in the generated source, or null.
391
*/
392
SourceMapConsumer.prototype.generatedPositionFor =
393
function SourceMapConsumer_generatedPositionFor(aArgs) {
394
var needle = {
395
source: util.getArg(aArgs, 'source'),
396
originalLine: util.getArg(aArgs, 'line'),
397
originalColumn: util.getArg(aArgs, 'column')
398
};
399
400
if (this.sourceRoot) {
401
needle.source = util.relative(this.sourceRoot, needle.source);
402
}
403
404
var mapping = this._findMapping(needle,
405
this._originalMappings,
406
"originalLine",
407
"originalColumn",
408
util.compareByOriginalPositions);
409
410
if (mapping) {
411
return {
412
line: util.getArg(mapping, 'generatedLine', null),
413
column: util.getArg(mapping, 'generatedColumn', null)
414
};
415
}
416
417
return {
418
line: null,
419
column: null
420
};
421
};
422
423
SourceMapConsumer.GENERATED_ORDER = 1;
424
SourceMapConsumer.ORIGINAL_ORDER = 2;
425
426
/**
427
* Iterate over each mapping between an original source/line/column and a
428
* generated line/column in this source map.
429
*
430
* @param Function aCallback
431
* The function that is called with each mapping.
432
* @param Object aContext
433
* Optional. If specified, this object will be the value of `this` every
434
* time that `aCallback` is called.
435
* @param aOrder
436
* Either `SourceMapConsumer.GENERATED_ORDER` or
437
* `SourceMapConsumer.ORIGINAL_ORDER`. Specifies whether you want to
438
* iterate over the mappings sorted by the generated file's line/column
439
* order or the original's source/line/column order, respectively. Defaults to
440
* `SourceMapConsumer.GENERATED_ORDER`.
441
*/
442
SourceMapConsumer.prototype.eachMapping =
443
function SourceMapConsumer_eachMapping(aCallback, aContext, aOrder) {
444
var context = aContext || null;
445
var order = aOrder || SourceMapConsumer.GENERATED_ORDER;
446
447
var mappings;
448
switch (order) {
449
case SourceMapConsumer.GENERATED_ORDER:
450
mappings = this._generatedMappings;
451
break;
452
case SourceMapConsumer.ORIGINAL_ORDER:
453
mappings = this._originalMappings;
454
break;
455
default:
456
throw new Error("Unknown order of iteration.");
457
}
458
459
var sourceRoot = this.sourceRoot;
460
mappings.map(function (mapping) {
461
var source = mapping.source;
462
if (source && sourceRoot) {
463
source = util.join(sourceRoot, source);
464
}
465
return {
466
source: source,
467
generatedLine: mapping.generatedLine,
468
generatedColumn: mapping.generatedColumn,
469
originalLine: mapping.originalLine,
470
originalColumn: mapping.originalColumn,
471
name: mapping.name
472
};
473
}).forEach(aCallback, context);
474
};
475
476
exports.SourceMapConsumer = SourceMapConsumer;
477
478
});
479
480