/* -*- 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 
 | 
 */ 
 | 
  
 | 
const base64VLQ = require("./base64-vlq"); 
 | 
const util = require("./util"); 
 | 
const ArraySet = require("./array-set").ArraySet; 
 | 
const 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. 
 | 
 */ 
 | 
class SourceMapGenerator { 
 | 
  constructor(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; 
 | 
  } 
 | 
  
 | 
  /** 
 | 
   * Creates a new SourceMapGenerator based on a SourceMapConsumer 
 | 
   * 
 | 
   * @param aSourceMapConsumer The SourceMap. 
 | 
   */ 
 | 
  static fromSourceMap(aSourceMapConsumer) { 
 | 
    const sourceRoot = aSourceMapConsumer.sourceRoot; 
 | 
    const generator = new SourceMapGenerator({ 
 | 
      file: aSourceMapConsumer.file, 
 | 
      sourceRoot 
 | 
    }); 
 | 
    aSourceMapConsumer.eachMapping(function(mapping) { 
 | 
      const 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) { 
 | 
      let sourceRelative = sourceFile; 
 | 
      if (sourceRoot !== null) { 
 | 
        sourceRelative = util.relative(sourceRoot, sourceFile); 
 | 
      } 
 | 
  
 | 
      if (!generator._sources.has(sourceRelative)) { 
 | 
        generator._sources.add(sourceRelative); 
 | 
      } 
 | 
  
 | 
      const 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. 
 | 
   */ 
 | 
  addMapping(aArgs) { 
 | 
    const generated = util.getArg(aArgs, "generated"); 
 | 
    const original = util.getArg(aArgs, "original", null); 
 | 
    let source = util.getArg(aArgs, "source", null); 
 | 
    let 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, 
 | 
      name 
 | 
    }); 
 | 
  } 
 | 
  
 | 
  /** 
 | 
   * Set the source content for a source file. 
 | 
   */ 
 | 
  setSourceContent(aSourceFile, aSourceContent) { 
 | 
    let 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. 
 | 
   */ 
 | 
  applySourceMap(aSourceMapConsumer, aSourceFile, aSourceMapPath) { 
 | 
    let 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; 
 | 
    } 
 | 
    const 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. 
 | 
    const newSources = this._mappings.toArray().length > 0 
 | 
      ? new ArraySet() 
 | 
      : this._sources; 
 | 
    const 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. 
 | 
        const 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; 
 | 
          } 
 | 
        } 
 | 
      } 
 | 
  
 | 
      const source = mapping.source; 
 | 
      if (source != null && !newSources.has(source)) { 
 | 
        newSources.add(source); 
 | 
      } 
 | 
  
 | 
      const 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(srcFile) { 
 | 
      const content = aSourceMapConsumer.sourceContentFor(srcFile); 
 | 
      if (content != null) { 
 | 
        if (aSourceMapPath != null) { 
 | 
          srcFile = util.join(aSourceMapPath, srcFile); 
 | 
        } 
 | 
        if (sourceRoot != null) { 
 | 
          srcFile = util.relative(sourceRoot, srcFile); 
 | 
        } 
 | 
        this.setSourceContent(srcFile, 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. 
 | 
   */ 
 | 
  _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. 
 | 
  
 | 
    } 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. 
 | 
  
 | 
    } 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. 
 | 
   */ 
 | 
  _serializeMappings() { 
 | 
    let previousGeneratedColumn = 0; 
 | 
    let previousGeneratedLine = 1; 
 | 
    let previousOriginalColumn = 0; 
 | 
    let previousOriginalLine = 0; 
 | 
    let previousName = 0; 
 | 
    let previousSource = 0; 
 | 
    let result = ""; 
 | 
    let next; 
 | 
    let mapping; 
 | 
    let nameIdx; 
 | 
    let sourceIdx; 
 | 
  
 | 
    const mappings = this._mappings.toArray(); 
 | 
    for (let 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; 
 | 
  } 
 | 
  
 | 
  _generateSourcesContent(aSources, aSourceRoot) { 
 | 
    return aSources.map(function(source) { 
 | 
      if (!this._sourcesContents) { 
 | 
        return null; 
 | 
      } 
 | 
      if (aSourceRoot != null) { 
 | 
        source = util.relative(aSourceRoot, source); 
 | 
      } 
 | 
      const key = util.toSetString(source); 
 | 
      return Object.prototype.hasOwnProperty.call(this._sourcesContents, key) 
 | 
        ? this._sourcesContents[key] 
 | 
        : null; 
 | 
    }, this); 
 | 
  } 
 | 
  
 | 
  /** 
 | 
   * Externalize the source map. 
 | 
   */ 
 | 
  toJSON() { 
 | 
    const 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. 
 | 
   */ 
 | 
  toString() { 
 | 
    return JSON.stringify(this.toJSON()); 
 | 
  } 
 | 
} 
 | 
  
 | 
SourceMapGenerator.prototype._version = 3; 
 | 
exports.SourceMapGenerator = SourceMapGenerator; 
 |