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