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