| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416 | /* -*- Mode: js; js-indent-level: 2; -*- *//* * Copyright 2011 Mozilla Foundation and contributors * Licensed under the New BSD license. See LICENSE or: * http://opensource.org/licenses/BSD-3-Clause */var base64VLQ = require('./base64-vlq');var util = require('./util');var ArraySet = require('./array-set').ArraySet;var MappingList = require('./mapping-list').MappingList;/** * An instance of the SourceMapGenerator represents a source map which is * being built incrementally. You may pass an object with the following * properties: * *   - file: The filename of the generated source. *   - sourceRoot: A root for all relative URLs in this source map. */function SourceMapGenerator(aArgs) {  if (!aArgs) {    aArgs = {};  }  this._file = util.getArg(aArgs, 'file', null);  this._sourceRoot = util.getArg(aArgs, 'sourceRoot', null);  this._skipValidation = util.getArg(aArgs, 'skipValidation', false);  this._sources = new ArraySet();  this._names = new ArraySet();  this._mappings = new MappingList();  this._sourcesContents = null;}SourceMapGenerator.prototype._version = 3;/** * Creates a new SourceMapGenerator based on a SourceMapConsumer * * @param aSourceMapConsumer The SourceMap. */SourceMapGenerator.fromSourceMap =  function SourceMapGenerator_fromSourceMap(aSourceMapConsumer) {    var sourceRoot = aSourceMapConsumer.sourceRoot;    var generator = new SourceMapGenerator({      file: aSourceMapConsumer.file,      sourceRoot: sourceRoot    });    aSourceMapConsumer.eachMapping(function (mapping) {      var newMapping = {        generated: {          line: mapping.generatedLine,          column: mapping.generatedColumn        }      };      if (mapping.source != null) {        newMapping.source = mapping.source;        if (sourceRoot != null) {          newMapping.source = util.relative(sourceRoot, newMapping.source);        }        newMapping.original = {          line: mapping.originalLine,          column: mapping.originalColumn        };        if (mapping.name != null) {          newMapping.name = mapping.name;        }      }      generator.addMapping(newMapping);    });    aSourceMapConsumer.sources.forEach(function (sourceFile) {      var content = aSourceMapConsumer.sourceContentFor(sourceFile);      if (content != null) {        generator.setSourceContent(sourceFile, content);      }    });    return generator;  };/** * Add a single mapping from original source line and column to the generated * source's line and column for this source map being created. The mapping * object should have the following properties: * *   - generated: An object with the generated line and column positions. *   - original: An object with the original line and column positions. *   - source: The original source file (relative to the sourceRoot). *   - name: An optional original token name for this mapping. */SourceMapGenerator.prototype.addMapping =  function SourceMapGenerator_addMapping(aArgs) {    var generated = util.getArg(aArgs, 'generated');    var original = util.getArg(aArgs, 'original', null);    var source = util.getArg(aArgs, 'source', null);    var name = util.getArg(aArgs, 'name', null);    if (!this._skipValidation) {      this._validateMapping(generated, original, source, name);    }    if (source != null) {      source = String(source);      if (!this._sources.has(source)) {        this._sources.add(source);      }    }    if (name != null) {      name = String(name);      if (!this._names.has(name)) {        this._names.add(name);      }    }    this._mappings.add({      generatedLine: generated.line,      generatedColumn: generated.column,      originalLine: original != null && original.line,      originalColumn: original != null && original.column,      source: source,      name: name    });  };/** * Set the source content for a source file. */SourceMapGenerator.prototype.setSourceContent =  function SourceMapGenerator_setSourceContent(aSourceFile, aSourceContent) {    var source = aSourceFile;    if (this._sourceRoot != null) {      source = util.relative(this._sourceRoot, source);    }    if (aSourceContent != null) {      // Add the source content to the _sourcesContents map.      // Create a new _sourcesContents map if the property is null.      if (!this._sourcesContents) {        this._sourcesContents = Object.create(null);      }      this._sourcesContents[util.toSetString(source)] = aSourceContent;    } else if (this._sourcesContents) {      // Remove the source file from the _sourcesContents map.      // If the _sourcesContents map is empty, set the property to null.      delete this._sourcesContents[util.toSetString(source)];      if (Object.keys(this._sourcesContents).length === 0) {        this._sourcesContents = null;      }    }  };/** * Applies the mappings of a sub-source-map for a specific source file to the * source map being generated. Each mapping to the supplied source file is * rewritten using the supplied source map. Note: The resolution for the * resulting mappings is the minimium of this map and the supplied map. * * @param aSourceMapConsumer The source map to be applied. * @param aSourceFile Optional. The filename of the source file. *        If omitted, SourceMapConsumer's file property will be used. * @param aSourceMapPath Optional. The dirname of the path to the source map *        to be applied. If relative, it is relative to the SourceMapConsumer. *        This parameter is needed when the two source maps aren't in the same *        directory, and the source map to be applied contains relative source *        paths. If so, those relative source paths need to be rewritten *        relative to the SourceMapGenerator. */SourceMapGenerator.prototype.applySourceMap =  function SourceMapGenerator_applySourceMap(aSourceMapConsumer, aSourceFile, aSourceMapPath) {    var sourceFile = aSourceFile;    // If aSourceFile is omitted, we will use the file property of the SourceMap    if (aSourceFile == null) {      if (aSourceMapConsumer.file == null) {        throw new Error(          'SourceMapGenerator.prototype.applySourceMap requires either an explicit source file, ' +          'or the source map\'s "file" property. Both were omitted.'        );      }      sourceFile = aSourceMapConsumer.file;    }    var sourceRoot = this._sourceRoot;    // Make "sourceFile" relative if an absolute Url is passed.    if (sourceRoot != null) {      sourceFile = util.relative(sourceRoot, sourceFile);    }    // Applying the SourceMap can add and remove items from the sources and    // the names array.    var newSources = new ArraySet();    var newNames = new ArraySet();    // Find mappings for the "sourceFile"    this._mappings.unsortedForEach(function (mapping) {      if (mapping.source === sourceFile && mapping.originalLine != null) {        // Check if it can be mapped by the source map, then update the mapping.        var original = aSourceMapConsumer.originalPositionFor({          line: mapping.originalLine,          column: mapping.originalColumn        });        if (original.source != null) {          // Copy mapping          mapping.source = original.source;          if (aSourceMapPath != null) {            mapping.source = util.join(aSourceMapPath, mapping.source)          }          if (sourceRoot != null) {            mapping.source = util.relative(sourceRoot, mapping.source);          }          mapping.originalLine = original.line;          mapping.originalColumn = original.column;          if (original.name != null) {            mapping.name = original.name;          }        }      }      var source = mapping.source;      if (source != null && !newSources.has(source)) {        newSources.add(source);      }      var name = mapping.name;      if (name != null && !newNames.has(name)) {        newNames.add(name);      }    }, this);    this._sources = newSources;    this._names = newNames;    // Copy sourcesContents of applied map.    aSourceMapConsumer.sources.forEach(function (sourceFile) {      var content = aSourceMapConsumer.sourceContentFor(sourceFile);      if (content != null) {        if (aSourceMapPath != null) {          sourceFile = util.join(aSourceMapPath, sourceFile);        }        if (sourceRoot != null) {          sourceFile = util.relative(sourceRoot, sourceFile);        }        this.setSourceContent(sourceFile, content);      }    }, this);  };/** * A mapping can have one of the three levels of data: * *   1. Just the generated position. *   2. The Generated position, original position, and original source. *   3. Generated and original position, original source, as well as a name *      token. * * To maintain consistency, we validate that any new mapping being added falls * in to one of these categories. */SourceMapGenerator.prototype._validateMapping =  function SourceMapGenerator_validateMapping(aGenerated, aOriginal, aSource,                                              aName) {    // When aOriginal is truthy but has empty values for .line and .column,    // it is most likely a programmer error. In this case we throw a very    // specific error message to try to guide them the right way.    // For example: https://github.com/Polymer/polymer-bundler/pull/519    if (aOriginal && typeof aOriginal.line !== 'number' && typeof aOriginal.column !== 'number') {        throw new Error(            'original.line and original.column are not numbers -- you probably meant to omit ' +            'the original mapping entirely and only map the generated position. If so, pass ' +            'null for the original mapping instead of an object with empty or null values.'        );    }    if (aGenerated && 'line' in aGenerated && 'column' in aGenerated        && aGenerated.line > 0 && aGenerated.column >= 0        && !aOriginal && !aSource && !aName) {      // Case 1.      return;    }    else if (aGenerated && 'line' in aGenerated && 'column' in aGenerated             && aOriginal && 'line' in aOriginal && 'column' in aOriginal             && aGenerated.line > 0 && aGenerated.column >= 0             && aOriginal.line > 0 && aOriginal.column >= 0             && aSource) {      // Cases 2 and 3.      return;    }    else {      throw new Error('Invalid mapping: ' + JSON.stringify({        generated: aGenerated,        source: aSource,        original: aOriginal,        name: aName      }));    }  };/** * Serialize the accumulated mappings in to the stream of base 64 VLQs * specified by the source map format. */SourceMapGenerator.prototype._serializeMappings =  function SourceMapGenerator_serializeMappings() {    var previousGeneratedColumn = 0;    var previousGeneratedLine = 1;    var previousOriginalColumn = 0;    var previousOriginalLine = 0;    var previousName = 0;    var previousSource = 0;    var result = '';    var next;    var mapping;    var nameIdx;    var sourceIdx;    var mappings = this._mappings.toArray();    for (var i = 0, len = mappings.length; i < len; i++) {      mapping = mappings[i];      next = ''      if (mapping.generatedLine !== previousGeneratedLine) {        previousGeneratedColumn = 0;        while (mapping.generatedLine !== previousGeneratedLine) {          next += ';';          previousGeneratedLine++;        }      }      else {        if (i > 0) {          if (!util.compareByGeneratedPositionsInflated(mapping, mappings[i - 1])) {            continue;          }          next += ',';        }      }      next += base64VLQ.encode(mapping.generatedColumn                                 - previousGeneratedColumn);      previousGeneratedColumn = mapping.generatedColumn;      if (mapping.source != null) {        sourceIdx = this._sources.indexOf(mapping.source);        next += base64VLQ.encode(sourceIdx - previousSource);        previousSource = sourceIdx;        // lines are stored 0-based in SourceMap spec version 3        next += base64VLQ.encode(mapping.originalLine - 1                                   - previousOriginalLine);        previousOriginalLine = mapping.originalLine - 1;        next += base64VLQ.encode(mapping.originalColumn                                   - previousOriginalColumn);        previousOriginalColumn = mapping.originalColumn;        if (mapping.name != null) {          nameIdx = this._names.indexOf(mapping.name);          next += base64VLQ.encode(nameIdx - previousName);          previousName = nameIdx;        }      }      result += next;    }    return result;  };SourceMapGenerator.prototype._generateSourcesContent =  function SourceMapGenerator_generateSourcesContent(aSources, aSourceRoot) {    return aSources.map(function (source) {      if (!this._sourcesContents) {        return null;      }      if (aSourceRoot != null) {        source = util.relative(aSourceRoot, source);      }      var key = util.toSetString(source);      return Object.prototype.hasOwnProperty.call(this._sourcesContents, key)        ? this._sourcesContents[key]        : null;    }, this);  };/** * Externalize the source map. */SourceMapGenerator.prototype.toJSON =  function SourceMapGenerator_toJSON() {    var map = {      version: this._version,      sources: this._sources.toArray(),      names: this._names.toArray(),      mappings: this._serializeMappings()    };    if (this._file != null) {      map.file = this._file;    }    if (this._sourceRoot != null) {      map.sourceRoot = this._sourceRoot;    }    if (this._sourcesContents) {      map.sourcesContent = this._generateSourcesContent(map.sources, map.sourceRoot);    }    return map;  };/** * Render the source map being generated to a string. */SourceMapGenerator.prototype.toString =  function SourceMapGenerator_toString() {    return JSON.stringify(this.toJSON());  };exports.SourceMapGenerator = SourceMapGenerator;
 |