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 base64VLQ = require('./base64-vlq');
13
var util = require('./util');
14
var ArraySet = require('./array-set').ArraySet;
15
16
/**
17
* An instance of the SourceMapGenerator represents a source map which is
18
* being built incrementally. To create a new one, you must pass an object
19
* with the following properties:
20
*
21
* - file: The filename of the generated source.
22
* - sourceRoot: An optional root for all URLs in this source map.
23
*/
24
function SourceMapGenerator(aArgs) {
25
this._file = util.getArg(aArgs, 'file');
26
this._sourceRoot = util.getArg(aArgs, 'sourceRoot', null);
27
this._sources = new ArraySet();
28
this._names = new ArraySet();
29
this._mappings = [];
30
this._sourcesContents = null;
31
}
32
33
SourceMapGenerator.prototype._version = 3;
34
35
/**
36
* Creates a new SourceMapGenerator based on a SourceMapConsumer
37
*
38
* @param aSourceMapConsumer The SourceMap.
39
*/
40
SourceMapGenerator.fromSourceMap =
41
function SourceMapGenerator_fromSourceMap(aSourceMapConsumer) {
42
var sourceRoot = aSourceMapConsumer.sourceRoot;
43
var generator = new SourceMapGenerator({
44
file: aSourceMapConsumer.file,
45
sourceRoot: sourceRoot
46
});
47
aSourceMapConsumer.eachMapping(function (mapping) {
48
var newMapping = {
49
generated: {
50
line: mapping.generatedLine,
51
column: mapping.generatedColumn
52
}
53
};
54
55
if (mapping.source) {
56
newMapping.source = mapping.source;
57
if (sourceRoot) {
58
newMapping.source = util.relative(sourceRoot, newMapping.source);
59
}
60
61
newMapping.original = {
62
line: mapping.originalLine,
63
column: mapping.originalColumn
64
};
65
66
if (mapping.name) {
67
newMapping.name = mapping.name;
68
}
69
}
70
71
generator.addMapping(newMapping);
72
});
73
aSourceMapConsumer.sources.forEach(function (sourceFile) {
74
var content = aSourceMapConsumer.sourceContentFor(sourceFile);
75
if (content) {
76
generator.setSourceContent(sourceFile, content);
77
}
78
});
79
return generator;
80
};
81
82
/**
83
* Add a single mapping from original source line and column to the generated
84
* source's line and column for this source map being created. The mapping
85
* object should have the following properties:
86
*
87
* - generated: An object with the generated line and column positions.
88
* - original: An object with the original line and column positions.
89
* - source: The original source file (relative to the sourceRoot).
90
* - name: An optional original token name for this mapping.
91
*/
92
SourceMapGenerator.prototype.addMapping =
93
function SourceMapGenerator_addMapping(aArgs) {
94
var generated = util.getArg(aArgs, 'generated');
95
var original = util.getArg(aArgs, 'original', null);
96
var source = util.getArg(aArgs, 'source', null);
97
var name = util.getArg(aArgs, 'name', null);
98
99
this._validateMapping(generated, original, source, name);
100
101
if (source && !this._sources.has(source)) {
102
this._sources.add(source);
103
}
104
105
if (name && !this._names.has(name)) {
106
this._names.add(name);
107
}
108
109
this._mappings.push({
110
generatedLine: generated.line,
111
generatedColumn: generated.column,
112
originalLine: original != null && original.line,
113
originalColumn: original != null && original.column,
114
source: source,
115
name: name
116
});
117
};
118
119
/**
120
* Set the source content for a source file.
121
*/
122
SourceMapGenerator.prototype.setSourceContent =
123
function SourceMapGenerator_setSourceContent(aSourceFile, aSourceContent) {
124
var source = aSourceFile;
125
if (this._sourceRoot) {
126
source = util.relative(this._sourceRoot, source);
127
}
128
129
if (aSourceContent !== null) {
130
// Add the source content to the _sourcesContents map.
131
// Create a new _sourcesContents map if the property is null.
132
if (!this._sourcesContents) {
133
this._sourcesContents = {};
134
}
135
this._sourcesContents[util.toSetString(source)] = aSourceContent;
136
} else {
137
// Remove the source file from the _sourcesContents map.
138
// If the _sourcesContents map is empty, set the property to null.
139
delete this._sourcesContents[util.toSetString(source)];
140
if (Object.keys(this._sourcesContents).length === 0) {
141
this._sourcesContents = null;
142
}
143
}
144
};
145
146
/**
147
* Applies the mappings of a sub-source-map for a specific source file to the
148
* source map being generated. Each mapping to the supplied source file is
149
* rewritten using the supplied source map. Note: The resolution for the
150
* resulting mappings is the minimium of this map and the supplied map.
151
*
152
* @param aSourceMapConsumer The source map to be applied.
153
* @param aSourceFile Optional. The filename of the source file.
154
* If omitted, SourceMapConsumer's file property will be used.
155
*/
156
SourceMapGenerator.prototype.applySourceMap =
157
function SourceMapGenerator_applySourceMap(aSourceMapConsumer, aSourceFile) {
158
// If aSourceFile is omitted, we will use the file property of the SourceMap
159
if (!aSourceFile) {
160
aSourceFile = aSourceMapConsumer.file;
161
}
162
var sourceRoot = this._sourceRoot;
163
// Make "aSourceFile" relative if an absolute Url is passed.
164
if (sourceRoot) {
165
aSourceFile = util.relative(sourceRoot, aSourceFile);
166
}
167
// Applying the SourceMap can add and remove items from the sources and
168
// the names array.
169
var newSources = new ArraySet();
170
var newNames = new ArraySet();
171
172
// Find mappings for the "aSourceFile"
173
this._mappings.forEach(function (mapping) {
174
if (mapping.source === aSourceFile && mapping.originalLine) {
175
// Check if it can be mapped by the source map, then update the mapping.
176
var original = aSourceMapConsumer.originalPositionFor({
177
line: mapping.originalLine,
178
column: mapping.originalColumn
179
});
180
if (original.source !== null) {
181
// Copy mapping
182
if (sourceRoot) {
183
mapping.source = util.relative(sourceRoot, original.source);
184
} else {
185
mapping.source = original.source;
186
}
187
mapping.originalLine = original.line;
188
mapping.originalColumn = original.column;
189
if (original.name !== null && mapping.name !== null) {
190
// Only use the identifier name if it's an identifier
191
// in both SourceMaps
192
mapping.name = original.name;
193
}
194
}
195
}
196
197
var source = mapping.source;
198
if (source && !newSources.has(source)) {
199
newSources.add(source);
200
}
201
202
var name = mapping.name;
203
if (name && !newNames.has(name)) {
204
newNames.add(name);
205
}
206
207
}, this);
208
this._sources = newSources;
209
this._names = newNames;
210
211
// Copy sourcesContents of applied map.
212
aSourceMapConsumer.sources.forEach(function (sourceFile) {
213
var content = aSourceMapConsumer.sourceContentFor(sourceFile);
214
if (content) {
215
if (sourceRoot) {
216
sourceFile = util.relative(sourceRoot, sourceFile);
217
}
218
this.setSourceContent(sourceFile, content);
219
}
220
}, this);
221
};
222
223
/**
224
* A mapping can have one of the three levels of data:
225
*
226
* 1. Just the generated position.
227
* 2. The Generated position, original position, and original source.
228
* 3. Generated and original position, original source, as well as a name
229
* token.
230
*
231
* To maintain consistency, we validate that any new mapping being added falls
232
* in to one of these categories.
233
*/
234
SourceMapGenerator.prototype._validateMapping =
235
function SourceMapGenerator_validateMapping(aGenerated, aOriginal, aSource,
236
aName) {
237
if (aGenerated && 'line' in aGenerated && 'column' in aGenerated
238
&& aGenerated.line > 0 && aGenerated.column >= 0
239
&& !aOriginal && !aSource && !aName) {
240
// Case 1.
241
return;
242
}
243
else if (aGenerated && 'line' in aGenerated && 'column' in aGenerated
244
&& aOriginal && 'line' in aOriginal && 'column' in aOriginal
245
&& aGenerated.line > 0 && aGenerated.column >= 0
246
&& aOriginal.line > 0 && aOriginal.column >= 0
247
&& aSource) {
248
// Cases 2 and 3.
249
return;
250
}
251
else {
252
throw new Error('Invalid mapping: ' + JSON.stringify({
253
generated: aGenerated,
254
source: aSource,
255
orginal: aOriginal,
256
name: aName
257
}));
258
}
259
};
260
261
/**
262
* Serialize the accumulated mappings in to the stream of base 64 VLQs
263
* specified by the source map format.
264
*/
265
SourceMapGenerator.prototype._serializeMappings =
266
function SourceMapGenerator_serializeMappings() {
267
var previousGeneratedColumn = 0;
268
var previousGeneratedLine = 1;
269
var previousOriginalColumn = 0;
270
var previousOriginalLine = 0;
271
var previousName = 0;
272
var previousSource = 0;
273
var result = '';
274
var mapping;
275
276
// The mappings must be guaranteed to be in sorted order before we start
277
// serializing them or else the generated line numbers (which are defined
278
// via the ';' separators) will be all messed up. Note: it might be more
279
// performant to maintain the sorting as we insert them, rather than as we
280
// serialize them, but the big O is the same either way.
281
this._mappings.sort(util.compareByGeneratedPositions);
282
283
for (var i = 0, len = this._mappings.length; i < len; i++) {
284
mapping = this._mappings[i];
285
286
if (mapping.generatedLine !== previousGeneratedLine) {
287
previousGeneratedColumn = 0;
288
while (mapping.generatedLine !== previousGeneratedLine) {
289
result += ';';
290
previousGeneratedLine++;
291
}
292
}
293
else {
294
if (i > 0) {
295
if (!util.compareByGeneratedPositions(mapping, this._mappings[i - 1])) {
296
continue;
297
}
298
result += ',';
299
}
300
}
301
302
result += base64VLQ.encode(mapping.generatedColumn
303
- previousGeneratedColumn);
304
previousGeneratedColumn = mapping.generatedColumn;
305
306
if (mapping.source) {
307
result += base64VLQ.encode(this._sources.indexOf(mapping.source)
308
- previousSource);
309
previousSource = this._sources.indexOf(mapping.source);
310
311
// lines are stored 0-based in SourceMap spec version 3
312
result += base64VLQ.encode(mapping.originalLine - 1
313
- previousOriginalLine);
314
previousOriginalLine = mapping.originalLine - 1;
315
316
result += base64VLQ.encode(mapping.originalColumn
317
- previousOriginalColumn);
318
previousOriginalColumn = mapping.originalColumn;
319
320
if (mapping.name) {
321
result += base64VLQ.encode(this._names.indexOf(mapping.name)
322
- previousName);
323
previousName = this._names.indexOf(mapping.name);
324
}
325
}
326
}
327
328
return result;
329
};
330
331
SourceMapGenerator.prototype._generateSourcesContent =
332
function SourceMapGenerator_generateSourcesContent(aSources, aSourceRoot) {
333
return aSources.map(function (source) {
334
if (!this._sourcesContents) {
335
return null;
336
}
337
if (aSourceRoot) {
338
source = util.relative(aSourceRoot, source);
339
}
340
var key = util.toSetString(source);
341
return Object.prototype.hasOwnProperty.call(this._sourcesContents,
342
key)
343
? this._sourcesContents[key]
344
: null;
345
}, this);
346
};
347
348
/**
349
* Externalize the source map.
350
*/
351
SourceMapGenerator.prototype.toJSON =
352
function SourceMapGenerator_toJSON() {
353
var map = {
354
version: this._version,
355
file: this._file,
356
sources: this._sources.toArray(),
357
names: this._names.toArray(),
358
mappings: this._serializeMappings()
359
};
360
if (this._sourceRoot) {
361
map.sourceRoot = this._sourceRoot;
362
}
363
if (this._sourcesContents) {
364
map.sourcesContent = this._generateSourcesContent(map.sources, map.sourceRoot);
365
}
366
367
return map;
368
};
369
370
/**
371
* Render the source map being generated to a string.
372
*/
373
SourceMapGenerator.prototype.toString =
374
function SourceMapGenerator_toString() {
375
return JSON.stringify(this);
376
};
377
378
exports.SourceMapGenerator = SourceMapGenerator;
379
380
});
381
382