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