/ cloudformation-templates / node_modules / source-map / lib / source-map-consumer.js
source-map-consumer.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 util = require('./util');
   9  var binarySearch = require('./binary-search');
  10  var ArraySet = require('./array-set').ArraySet;
  11  var base64VLQ = require('./base64-vlq');
  12  var quickSort = require('./quick-sort').quickSort;
  13  
  14  function SourceMapConsumer(aSourceMap, aSourceMapURL) {
  15    var sourceMap = aSourceMap;
  16    if (typeof aSourceMap === 'string') {
  17      sourceMap = util.parseSourceMapInput(aSourceMap);
  18    }
  19  
  20    return sourceMap.sections != null
  21      ? new IndexedSourceMapConsumer(sourceMap, aSourceMapURL)
  22      : new BasicSourceMapConsumer(sourceMap, aSourceMapURL);
  23  }
  24  
  25  SourceMapConsumer.fromSourceMap = function(aSourceMap, aSourceMapURL) {
  26    return BasicSourceMapConsumer.fromSourceMap(aSourceMap, aSourceMapURL);
  27  }
  28  
  29  /**
  30   * The version of the source mapping spec that we are consuming.
  31   */
  32  SourceMapConsumer.prototype._version = 3;
  33  
  34  // `__generatedMappings` and `__originalMappings` are arrays that hold the
  35  // parsed mapping coordinates from the source map's "mappings" attribute. They
  36  // are lazily instantiated, accessed via the `_generatedMappings` and
  37  // `_originalMappings` getters respectively, and we only parse the mappings
  38  // and create these arrays once queried for a source location. We jump through
  39  // these hoops because there can be many thousands of mappings, and parsing
  40  // them is expensive, so we only want to do it if we must.
  41  //
  42  // Each object in the arrays is of the form:
  43  //
  44  //     {
  45  //       generatedLine: The line number in the generated code,
  46  //       generatedColumn: The column number in the generated code,
  47  //       source: The path to the original source file that generated this
  48  //               chunk of code,
  49  //       originalLine: The line number in the original source that
  50  //                     corresponds to this chunk of generated code,
  51  //       originalColumn: The column number in the original source that
  52  //                       corresponds to this chunk of generated code,
  53  //       name: The name of the original symbol which generated this chunk of
  54  //             code.
  55  //     }
  56  //
  57  // All properties except for `generatedLine` and `generatedColumn` can be
  58  // `null`.
  59  //
  60  // `_generatedMappings` is ordered by the generated positions.
  61  //
  62  // `_originalMappings` is ordered by the original positions.
  63  
  64  SourceMapConsumer.prototype.__generatedMappings = null;
  65  Object.defineProperty(SourceMapConsumer.prototype, '_generatedMappings', {
  66    configurable: true,
  67    enumerable: true,
  68    get: function () {
  69      if (!this.__generatedMappings) {
  70        this._parseMappings(this._mappings, this.sourceRoot);
  71      }
  72  
  73      return this.__generatedMappings;
  74    }
  75  });
  76  
  77  SourceMapConsumer.prototype.__originalMappings = null;
  78  Object.defineProperty(SourceMapConsumer.prototype, '_originalMappings', {
  79    configurable: true,
  80    enumerable: true,
  81    get: function () {
  82      if (!this.__originalMappings) {
  83        this._parseMappings(this._mappings, this.sourceRoot);
  84      }
  85  
  86      return this.__originalMappings;
  87    }
  88  });
  89  
  90  SourceMapConsumer.prototype._charIsMappingSeparator =
  91    function SourceMapConsumer_charIsMappingSeparator(aStr, index) {
  92      var c = aStr.charAt(index);
  93      return c === ";" || c === ",";
  94    };
  95  
  96  /**
  97   * Parse the mappings in a string in to a data structure which we can easily
  98   * query (the ordered arrays in the `this.__generatedMappings` and
  99   * `this.__originalMappings` properties).
 100   */
 101  SourceMapConsumer.prototype._parseMappings =
 102    function SourceMapConsumer_parseMappings(aStr, aSourceRoot) {
 103      throw new Error("Subclasses must implement _parseMappings");
 104    };
 105  
 106  SourceMapConsumer.GENERATED_ORDER = 1;
 107  SourceMapConsumer.ORIGINAL_ORDER = 2;
 108  
 109  SourceMapConsumer.GREATEST_LOWER_BOUND = 1;
 110  SourceMapConsumer.LEAST_UPPER_BOUND = 2;
 111  
 112  /**
 113   * Iterate over each mapping between an original source/line/column and a
 114   * generated line/column in this source map.
 115   *
 116   * @param Function aCallback
 117   *        The function that is called with each mapping.
 118   * @param Object aContext
 119   *        Optional. If specified, this object will be the value of `this` every
 120   *        time that `aCallback` is called.
 121   * @param aOrder
 122   *        Either `SourceMapConsumer.GENERATED_ORDER` or
 123   *        `SourceMapConsumer.ORIGINAL_ORDER`. Specifies whether you want to
 124   *        iterate over the mappings sorted by the generated file's line/column
 125   *        order or the original's source/line/column order, respectively. Defaults to
 126   *        `SourceMapConsumer.GENERATED_ORDER`.
 127   */
 128  SourceMapConsumer.prototype.eachMapping =
 129    function SourceMapConsumer_eachMapping(aCallback, aContext, aOrder) {
 130      var context = aContext || null;
 131      var order = aOrder || SourceMapConsumer.GENERATED_ORDER;
 132  
 133      var mappings;
 134      switch (order) {
 135      case SourceMapConsumer.GENERATED_ORDER:
 136        mappings = this._generatedMappings;
 137        break;
 138      case SourceMapConsumer.ORIGINAL_ORDER:
 139        mappings = this._originalMappings;
 140        break;
 141      default:
 142        throw new Error("Unknown order of iteration.");
 143      }
 144  
 145      var sourceRoot = this.sourceRoot;
 146      mappings.map(function (mapping) {
 147        var source = mapping.source === null ? null : this._sources.at(mapping.source);
 148        source = util.computeSourceURL(sourceRoot, source, this._sourceMapURL);
 149        return {
 150          source: source,
 151          generatedLine: mapping.generatedLine,
 152          generatedColumn: mapping.generatedColumn,
 153          originalLine: mapping.originalLine,
 154          originalColumn: mapping.originalColumn,
 155          name: mapping.name === null ? null : this._names.at(mapping.name)
 156        };
 157      }, this).forEach(aCallback, context);
 158    };
 159  
 160  /**
 161   * Returns all generated line and column information for the original source,
 162   * line, and column provided. If no column is provided, returns all mappings
 163   * corresponding to a either the line we are searching for or the next
 164   * closest line that has any mappings. Otherwise, returns all mappings
 165   * corresponding to the given line and either the column we are searching for
 166   * or the next closest column that has any offsets.
 167   *
 168   * The only argument is an object with the following properties:
 169   *
 170   *   - source: The filename of the original source.
 171   *   - line: The line number in the original source.  The line number is 1-based.
 172   *   - column: Optional. the column number in the original source.
 173   *    The column number is 0-based.
 174   *
 175   * and an array of objects is returned, each with the following properties:
 176   *
 177   *   - line: The line number in the generated source, or null.  The
 178   *    line number is 1-based.
 179   *   - column: The column number in the generated source, or null.
 180   *    The column number is 0-based.
 181   */
 182  SourceMapConsumer.prototype.allGeneratedPositionsFor =
 183    function SourceMapConsumer_allGeneratedPositionsFor(aArgs) {
 184      var line = util.getArg(aArgs, 'line');
 185  
 186      // When there is no exact match, BasicSourceMapConsumer.prototype._findMapping
 187      // returns the index of the closest mapping less than the needle. By
 188      // setting needle.originalColumn to 0, we thus find the last mapping for
 189      // the given line, provided such a mapping exists.
 190      var needle = {
 191        source: util.getArg(aArgs, 'source'),
 192        originalLine: line,
 193        originalColumn: util.getArg(aArgs, 'column', 0)
 194      };
 195  
 196      needle.source = this._findSourceIndex(needle.source);
 197      if (needle.source < 0) {
 198        return [];
 199      }
 200  
 201      var mappings = [];
 202  
 203      var index = this._findMapping(needle,
 204                                    this._originalMappings,
 205                                    "originalLine",
 206                                    "originalColumn",
 207                                    util.compareByOriginalPositions,
 208                                    binarySearch.LEAST_UPPER_BOUND);
 209      if (index >= 0) {
 210        var mapping = this._originalMappings[index];
 211  
 212        if (aArgs.column === undefined) {
 213          var originalLine = mapping.originalLine;
 214  
 215          // Iterate until either we run out of mappings, or we run into
 216          // a mapping for a different line than the one we found. Since
 217          // mappings are sorted, this is guaranteed to find all mappings for
 218          // the line we found.
 219          while (mapping && mapping.originalLine === originalLine) {
 220            mappings.push({
 221              line: util.getArg(mapping, 'generatedLine', null),
 222              column: util.getArg(mapping, 'generatedColumn', null),
 223              lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null)
 224            });
 225  
 226            mapping = this._originalMappings[++index];
 227          }
 228        } else {
 229          var originalColumn = mapping.originalColumn;
 230  
 231          // Iterate until either we run out of mappings, or we run into
 232          // a mapping for a different line than the one we were searching for.
 233          // Since mappings are sorted, this is guaranteed to find all mappings for
 234          // the line we are searching for.
 235          while (mapping &&
 236                 mapping.originalLine === line &&
 237                 mapping.originalColumn == originalColumn) {
 238            mappings.push({
 239              line: util.getArg(mapping, 'generatedLine', null),
 240              column: util.getArg(mapping, 'generatedColumn', null),
 241              lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null)
 242            });
 243  
 244            mapping = this._originalMappings[++index];
 245          }
 246        }
 247      }
 248  
 249      return mappings;
 250    };
 251  
 252  exports.SourceMapConsumer = SourceMapConsumer;
 253  
 254  /**
 255   * A BasicSourceMapConsumer instance represents a parsed source map which we can
 256   * query for information about the original file positions by giving it a file
 257   * position in the generated source.
 258   *
 259   * The first parameter is the raw source map (either as a JSON string, or
 260   * already parsed to an object). According to the spec, source maps have the
 261   * following attributes:
 262   *
 263   *   - version: Which version of the source map spec this map is following.
 264   *   - sources: An array of URLs to the original source files.
 265   *   - names: An array of identifiers which can be referrenced by individual mappings.
 266   *   - sourceRoot: Optional. The URL root from which all sources are relative.
 267   *   - sourcesContent: Optional. An array of contents of the original source files.
 268   *   - mappings: A string of base64 VLQs which contain the actual mappings.
 269   *   - file: Optional. The generated file this source map is associated with.
 270   *
 271   * Here is an example source map, taken from the source map spec[0]:
 272   *
 273   *     {
 274   *       version : 3,
 275   *       file: "out.js",
 276   *       sourceRoot : "",
 277   *       sources: ["foo.js", "bar.js"],
 278   *       names: ["src", "maps", "are", "fun"],
 279   *       mappings: "AA,AB;;ABCDE;"
 280   *     }
 281   *
 282   * The second parameter, if given, is a string whose value is the URL
 283   * at which the source map was found.  This URL is used to compute the
 284   * sources array.
 285   *
 286   * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit?pli=1#
 287   */
 288  function BasicSourceMapConsumer(aSourceMap, aSourceMapURL) {
 289    var sourceMap = aSourceMap;
 290    if (typeof aSourceMap === 'string') {
 291      sourceMap = util.parseSourceMapInput(aSourceMap);
 292    }
 293  
 294    var version = util.getArg(sourceMap, 'version');
 295    var sources = util.getArg(sourceMap, 'sources');
 296    // Sass 3.3 leaves out the 'names' array, so we deviate from the spec (which
 297    // requires the array) to play nice here.
 298    var names = util.getArg(sourceMap, 'names', []);
 299    var sourceRoot = util.getArg(sourceMap, 'sourceRoot', null);
 300    var sourcesContent = util.getArg(sourceMap, 'sourcesContent', null);
 301    var mappings = util.getArg(sourceMap, 'mappings');
 302    var file = util.getArg(sourceMap, 'file', null);
 303  
 304    // Once again, Sass deviates from the spec and supplies the version as a
 305    // string rather than a number, so we use loose equality checking here.
 306    if (version != this._version) {
 307      throw new Error('Unsupported version: ' + version);
 308    }
 309  
 310    if (sourceRoot) {
 311      sourceRoot = util.normalize(sourceRoot);
 312    }
 313  
 314    sources = sources
 315      .map(String)
 316      // Some source maps produce relative source paths like "./foo.js" instead of
 317      // "foo.js".  Normalize these first so that future comparisons will succeed.
 318      // See bugzil.la/1090768.
 319      .map(util.normalize)
 320      // Always ensure that absolute sources are internally stored relative to
 321      // the source root, if the source root is absolute. Not doing this would
 322      // be particularly problematic when the source root is a prefix of the
 323      // source (valid, but why??). See github issue #199 and bugzil.la/1188982.
 324      .map(function (source) {
 325        return sourceRoot && util.isAbsolute(sourceRoot) && util.isAbsolute(source)
 326          ? util.relative(sourceRoot, source)
 327          : source;
 328      });
 329  
 330    // Pass `true` below to allow duplicate names and sources. While source maps
 331    // are intended to be compressed and deduplicated, the TypeScript compiler
 332    // sometimes generates source maps with duplicates in them. See Github issue
 333    // #72 and bugzil.la/889492.
 334    this._names = ArraySet.fromArray(names.map(String), true);
 335    this._sources = ArraySet.fromArray(sources, true);
 336  
 337    this._absoluteSources = this._sources.toArray().map(function (s) {
 338      return util.computeSourceURL(sourceRoot, s, aSourceMapURL);
 339    });
 340  
 341    this.sourceRoot = sourceRoot;
 342    this.sourcesContent = sourcesContent;
 343    this._mappings = mappings;
 344    this._sourceMapURL = aSourceMapURL;
 345    this.file = file;
 346  }
 347  
 348  BasicSourceMapConsumer.prototype = Object.create(SourceMapConsumer.prototype);
 349  BasicSourceMapConsumer.prototype.consumer = SourceMapConsumer;
 350  
 351  /**
 352   * Utility function to find the index of a source.  Returns -1 if not
 353   * found.
 354   */
 355  BasicSourceMapConsumer.prototype._findSourceIndex = function(aSource) {
 356    var relativeSource = aSource;
 357    if (this.sourceRoot != null) {
 358      relativeSource = util.relative(this.sourceRoot, relativeSource);
 359    }
 360  
 361    if (this._sources.has(relativeSource)) {
 362      return this._sources.indexOf(relativeSource);
 363    }
 364  
 365    // Maybe aSource is an absolute URL as returned by |sources|.  In
 366    // this case we can't simply undo the transform.
 367    var i;
 368    for (i = 0; i < this._absoluteSources.length; ++i) {
 369      if (this._absoluteSources[i] == aSource) {
 370        return i;
 371      }
 372    }
 373  
 374    return -1;
 375  };
 376  
 377  /**
 378   * Create a BasicSourceMapConsumer from a SourceMapGenerator.
 379   *
 380   * @param SourceMapGenerator aSourceMap
 381   *        The source map that will be consumed.
 382   * @param String aSourceMapURL
 383   *        The URL at which the source map can be found (optional)
 384   * @returns BasicSourceMapConsumer
 385   */
 386  BasicSourceMapConsumer.fromSourceMap =
 387    function SourceMapConsumer_fromSourceMap(aSourceMap, aSourceMapURL) {
 388      var smc = Object.create(BasicSourceMapConsumer.prototype);
 389  
 390      var names = smc._names = ArraySet.fromArray(aSourceMap._names.toArray(), true);
 391      var sources = smc._sources = ArraySet.fromArray(aSourceMap._sources.toArray(), true);
 392      smc.sourceRoot = aSourceMap._sourceRoot;
 393      smc.sourcesContent = aSourceMap._generateSourcesContent(smc._sources.toArray(),
 394                                                              smc.sourceRoot);
 395      smc.file = aSourceMap._file;
 396      smc._sourceMapURL = aSourceMapURL;
 397      smc._absoluteSources = smc._sources.toArray().map(function (s) {
 398        return util.computeSourceURL(smc.sourceRoot, s, aSourceMapURL);
 399      });
 400  
 401      // Because we are modifying the entries (by converting string sources and
 402      // names to indices into the sources and names ArraySets), we have to make
 403      // a copy of the entry or else bad things happen. Shared mutable state
 404      // strikes again! See github issue #191.
 405  
 406      var generatedMappings = aSourceMap._mappings.toArray().slice();
 407      var destGeneratedMappings = smc.__generatedMappings = [];
 408      var destOriginalMappings = smc.__originalMappings = [];
 409  
 410      for (var i = 0, length = generatedMappings.length; i < length; i++) {
 411        var srcMapping = generatedMappings[i];
 412        var destMapping = new Mapping;
 413        destMapping.generatedLine = srcMapping.generatedLine;
 414        destMapping.generatedColumn = srcMapping.generatedColumn;
 415  
 416        if (srcMapping.source) {
 417          destMapping.source = sources.indexOf(srcMapping.source);
 418          destMapping.originalLine = srcMapping.originalLine;
 419          destMapping.originalColumn = srcMapping.originalColumn;
 420  
 421          if (srcMapping.name) {
 422            destMapping.name = names.indexOf(srcMapping.name);
 423          }
 424  
 425          destOriginalMappings.push(destMapping);
 426        }
 427  
 428        destGeneratedMappings.push(destMapping);
 429      }
 430  
 431      quickSort(smc.__originalMappings, util.compareByOriginalPositions);
 432  
 433      return smc;
 434    };
 435  
 436  /**
 437   * The version of the source mapping spec that we are consuming.
 438   */
 439  BasicSourceMapConsumer.prototype._version = 3;
 440  
 441  /**
 442   * The list of original sources.
 443   */
 444  Object.defineProperty(BasicSourceMapConsumer.prototype, 'sources', {
 445    get: function () {
 446      return this._absoluteSources.slice();
 447    }
 448  });
 449  
 450  /**
 451   * Provide the JIT with a nice shape / hidden class.
 452   */
 453  function Mapping() {
 454    this.generatedLine = 0;
 455    this.generatedColumn = 0;
 456    this.source = null;
 457    this.originalLine = null;
 458    this.originalColumn = null;
 459    this.name = null;
 460  }
 461  
 462  /**
 463   * Parse the mappings in a string in to a data structure which we can easily
 464   * query (the ordered arrays in the `this.__generatedMappings` and
 465   * `this.__originalMappings` properties).
 466   */
 467  BasicSourceMapConsumer.prototype._parseMappings =
 468    function SourceMapConsumer_parseMappings(aStr, aSourceRoot) {
 469      var generatedLine = 1;
 470      var previousGeneratedColumn = 0;
 471      var previousOriginalLine = 0;
 472      var previousOriginalColumn = 0;
 473      var previousSource = 0;
 474      var previousName = 0;
 475      var length = aStr.length;
 476      var index = 0;
 477      var cachedSegments = {};
 478      var temp = {};
 479      var originalMappings = [];
 480      var generatedMappings = [];
 481      var mapping, str, segment, end, value;
 482  
 483      while (index < length) {
 484        if (aStr.charAt(index) === ';') {
 485          generatedLine++;
 486          index++;
 487          previousGeneratedColumn = 0;
 488        }
 489        else if (aStr.charAt(index) === ',') {
 490          index++;
 491        }
 492        else {
 493          mapping = new Mapping();
 494          mapping.generatedLine = generatedLine;
 495  
 496          // Because each offset is encoded relative to the previous one,
 497          // many segments often have the same encoding. We can exploit this
 498          // fact by caching the parsed variable length fields of each segment,
 499          // allowing us to avoid a second parse if we encounter the same
 500          // segment again.
 501          for (end = index; end < length; end++) {
 502            if (this._charIsMappingSeparator(aStr, end)) {
 503              break;
 504            }
 505          }
 506          str = aStr.slice(index, end);
 507  
 508          segment = cachedSegments[str];
 509          if (segment) {
 510            index += str.length;
 511          } else {
 512            segment = [];
 513            while (index < end) {
 514              base64VLQ.decode(aStr, index, temp);
 515              value = temp.value;
 516              index = temp.rest;
 517              segment.push(value);
 518            }
 519  
 520            if (segment.length === 2) {
 521              throw new Error('Found a source, but no line and column');
 522            }
 523  
 524            if (segment.length === 3) {
 525              throw new Error('Found a source and line, but no column');
 526            }
 527  
 528            cachedSegments[str] = segment;
 529          }
 530  
 531          // Generated column.
 532          mapping.generatedColumn = previousGeneratedColumn + segment[0];
 533          previousGeneratedColumn = mapping.generatedColumn;
 534  
 535          if (segment.length > 1) {
 536            // Original source.
 537            mapping.source = previousSource + segment[1];
 538            previousSource += segment[1];
 539  
 540            // Original line.
 541            mapping.originalLine = previousOriginalLine + segment[2];
 542            previousOriginalLine = mapping.originalLine;
 543            // Lines are stored 0-based
 544            mapping.originalLine += 1;
 545  
 546            // Original column.
 547            mapping.originalColumn = previousOriginalColumn + segment[3];
 548            previousOriginalColumn = mapping.originalColumn;
 549  
 550            if (segment.length > 4) {
 551              // Original name.
 552              mapping.name = previousName + segment[4];
 553              previousName += segment[4];
 554            }
 555          }
 556  
 557          generatedMappings.push(mapping);
 558          if (typeof mapping.originalLine === 'number') {
 559            originalMappings.push(mapping);
 560          }
 561        }
 562      }
 563  
 564      quickSort(generatedMappings, util.compareByGeneratedPositionsDeflated);
 565      this.__generatedMappings = generatedMappings;
 566  
 567      quickSort(originalMappings, util.compareByOriginalPositions);
 568      this.__originalMappings = originalMappings;
 569    };
 570  
 571  /**
 572   * Find the mapping that best matches the hypothetical "needle" mapping that
 573   * we are searching for in the given "haystack" of mappings.
 574   */
 575  BasicSourceMapConsumer.prototype._findMapping =
 576    function SourceMapConsumer_findMapping(aNeedle, aMappings, aLineName,
 577                                           aColumnName, aComparator, aBias) {
 578      // To return the position we are searching for, we must first find the
 579      // mapping for the given position and then return the opposite position it
 580      // points to. Because the mappings are sorted, we can use binary search to
 581      // find the best mapping.
 582  
 583      if (aNeedle[aLineName] <= 0) {
 584        throw new TypeError('Line must be greater than or equal to 1, got '
 585                            + aNeedle[aLineName]);
 586      }
 587      if (aNeedle[aColumnName] < 0) {
 588        throw new TypeError('Column must be greater than or equal to 0, got '
 589                            + aNeedle[aColumnName]);
 590      }
 591  
 592      return binarySearch.search(aNeedle, aMappings, aComparator, aBias);
 593    };
 594  
 595  /**
 596   * Compute the last column for each generated mapping. The last column is
 597   * inclusive.
 598   */
 599  BasicSourceMapConsumer.prototype.computeColumnSpans =
 600    function SourceMapConsumer_computeColumnSpans() {
 601      for (var index = 0; index < this._generatedMappings.length; ++index) {
 602        var mapping = this._generatedMappings[index];
 603  
 604        // Mappings do not contain a field for the last generated columnt. We
 605        // can come up with an optimistic estimate, however, by assuming that
 606        // mappings are contiguous (i.e. given two consecutive mappings, the
 607        // first mapping ends where the second one starts).
 608        if (index + 1 < this._generatedMappings.length) {
 609          var nextMapping = this._generatedMappings[index + 1];
 610  
 611          if (mapping.generatedLine === nextMapping.generatedLine) {
 612            mapping.lastGeneratedColumn = nextMapping.generatedColumn - 1;
 613            continue;
 614          }
 615        }
 616  
 617        // The last mapping for each line spans the entire line.
 618        mapping.lastGeneratedColumn = Infinity;
 619      }
 620    };
 621  
 622  /**
 623   * Returns the original source, line, and column information for the generated
 624   * source's line and column positions provided. The only argument is an object
 625   * with the following properties:
 626   *
 627   *   - line: The line number in the generated source.  The line number
 628   *     is 1-based.
 629   *   - column: The column number in the generated source.  The column
 630   *     number is 0-based.
 631   *   - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or
 632   *     'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the
 633   *     closest element that is smaller than or greater than the one we are
 634   *     searching for, respectively, if the exact element cannot be found.
 635   *     Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'.
 636   *
 637   * and an object is returned with the following properties:
 638   *
 639   *   - source: The original source file, or null.
 640   *   - line: The line number in the original source, or null.  The
 641   *     line number is 1-based.
 642   *   - column: The column number in the original source, or null.  The
 643   *     column number is 0-based.
 644   *   - name: The original identifier, or null.
 645   */
 646  BasicSourceMapConsumer.prototype.originalPositionFor =
 647    function SourceMapConsumer_originalPositionFor(aArgs) {
 648      var needle = {
 649        generatedLine: util.getArg(aArgs, 'line'),
 650        generatedColumn: util.getArg(aArgs, 'column')
 651      };
 652  
 653      var index = this._findMapping(
 654        needle,
 655        this._generatedMappings,
 656        "generatedLine",
 657        "generatedColumn",
 658        util.compareByGeneratedPositionsDeflated,
 659        util.getArg(aArgs, 'bias', SourceMapConsumer.GREATEST_LOWER_BOUND)
 660      );
 661  
 662      if (index >= 0) {
 663        var mapping = this._generatedMappings[index];
 664  
 665        if (mapping.generatedLine === needle.generatedLine) {
 666          var source = util.getArg(mapping, 'source', null);
 667          if (source !== null) {
 668            source = this._sources.at(source);
 669            source = util.computeSourceURL(this.sourceRoot, source, this._sourceMapURL);
 670          }
 671          var name = util.getArg(mapping, 'name', null);
 672          if (name !== null) {
 673            name = this._names.at(name);
 674          }
 675          return {
 676            source: source,
 677            line: util.getArg(mapping, 'originalLine', null),
 678            column: util.getArg(mapping, 'originalColumn', null),
 679            name: name
 680          };
 681        }
 682      }
 683  
 684      return {
 685        source: null,
 686        line: null,
 687        column: null,
 688        name: null
 689      };
 690    };
 691  
 692  /**
 693   * Return true if we have the source content for every source in the source
 694   * map, false otherwise.
 695   */
 696  BasicSourceMapConsumer.prototype.hasContentsOfAllSources =
 697    function BasicSourceMapConsumer_hasContentsOfAllSources() {
 698      if (!this.sourcesContent) {
 699        return false;
 700      }
 701      return this.sourcesContent.length >= this._sources.size() &&
 702        !this.sourcesContent.some(function (sc) { return sc == null; });
 703    };
 704  
 705  /**
 706   * Returns the original source content. The only argument is the url of the
 707   * original source file. Returns null if no original source content is
 708   * available.
 709   */
 710  BasicSourceMapConsumer.prototype.sourceContentFor =
 711    function SourceMapConsumer_sourceContentFor(aSource, nullOnMissing) {
 712      if (!this.sourcesContent) {
 713        return null;
 714      }
 715  
 716      var index = this._findSourceIndex(aSource);
 717      if (index >= 0) {
 718        return this.sourcesContent[index];
 719      }
 720  
 721      var relativeSource = aSource;
 722      if (this.sourceRoot != null) {
 723        relativeSource = util.relative(this.sourceRoot, relativeSource);
 724      }
 725  
 726      var url;
 727      if (this.sourceRoot != null
 728          && (url = util.urlParse(this.sourceRoot))) {
 729        // XXX: file:// URIs and absolute paths lead to unexpected behavior for
 730        // many users. We can help them out when they expect file:// URIs to
 731        // behave like it would if they were running a local HTTP server. See
 732        // https://bugzilla.mozilla.org/show_bug.cgi?id=885597.
 733        var fileUriAbsPath = relativeSource.replace(/^file:\/\//, "");
 734        if (url.scheme == "file"
 735            && this._sources.has(fileUriAbsPath)) {
 736          return this.sourcesContent[this._sources.indexOf(fileUriAbsPath)]
 737        }
 738  
 739        if ((!url.path || url.path == "/")
 740            && this._sources.has("/" + relativeSource)) {
 741          return this.sourcesContent[this._sources.indexOf("/" + relativeSource)];
 742        }
 743      }
 744  
 745      // This function is used recursively from
 746      // IndexedSourceMapConsumer.prototype.sourceContentFor. In that case, we
 747      // don't want to throw if we can't find the source - we just want to
 748      // return null, so we provide a flag to exit gracefully.
 749      if (nullOnMissing) {
 750        return null;
 751      }
 752      else {
 753        throw new Error('"' + relativeSource + '" is not in the SourceMap.');
 754      }
 755    };
 756  
 757  /**
 758   * Returns the generated line and column information for the original source,
 759   * line, and column positions provided. The only argument is an object with
 760   * the following properties:
 761   *
 762   *   - source: The filename of the original source.
 763   *   - line: The line number in the original source.  The line number
 764   *     is 1-based.
 765   *   - column: The column number in the original source.  The column
 766   *     number is 0-based.
 767   *   - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or
 768   *     'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the
 769   *     closest element that is smaller than or greater than the one we are
 770   *     searching for, respectively, if the exact element cannot be found.
 771   *     Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'.
 772   *
 773   * and an object is returned with the following properties:
 774   *
 775   *   - line: The line number in the generated source, or null.  The
 776   *     line number is 1-based.
 777   *   - column: The column number in the generated source, or null.
 778   *     The column number is 0-based.
 779   */
 780  BasicSourceMapConsumer.prototype.generatedPositionFor =
 781    function SourceMapConsumer_generatedPositionFor(aArgs) {
 782      var source = util.getArg(aArgs, 'source');
 783      source = this._findSourceIndex(source);
 784      if (source < 0) {
 785        return {
 786          line: null,
 787          column: null,
 788          lastColumn: null
 789        };
 790      }
 791  
 792      var needle = {
 793        source: source,
 794        originalLine: util.getArg(aArgs, 'line'),
 795        originalColumn: util.getArg(aArgs, 'column')
 796      };
 797  
 798      var index = this._findMapping(
 799        needle,
 800        this._originalMappings,
 801        "originalLine",
 802        "originalColumn",
 803        util.compareByOriginalPositions,
 804        util.getArg(aArgs, 'bias', SourceMapConsumer.GREATEST_LOWER_BOUND)
 805      );
 806  
 807      if (index >= 0) {
 808        var mapping = this._originalMappings[index];
 809  
 810        if (mapping.source === needle.source) {
 811          return {
 812            line: util.getArg(mapping, 'generatedLine', null),
 813            column: util.getArg(mapping, 'generatedColumn', null),
 814            lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null)
 815          };
 816        }
 817      }
 818  
 819      return {
 820        line: null,
 821        column: null,
 822        lastColumn: null
 823      };
 824    };
 825  
 826  exports.BasicSourceMapConsumer = BasicSourceMapConsumer;
 827  
 828  /**
 829   * An IndexedSourceMapConsumer instance represents a parsed source map which
 830   * we can query for information. It differs from BasicSourceMapConsumer in
 831   * that it takes "indexed" source maps (i.e. ones with a "sections" field) as
 832   * input.
 833   *
 834   * The first parameter is a raw source map (either as a JSON string, or already
 835   * parsed to an object). According to the spec for indexed source maps, they
 836   * have the following attributes:
 837   *
 838   *   - version: Which version of the source map spec this map is following.
 839   *   - file: Optional. The generated file this source map is associated with.
 840   *   - sections: A list of section definitions.
 841   *
 842   * Each value under the "sections" field has two fields:
 843   *   - offset: The offset into the original specified at which this section
 844   *       begins to apply, defined as an object with a "line" and "column"
 845   *       field.
 846   *   - map: A source map definition. This source map could also be indexed,
 847   *       but doesn't have to be.
 848   *
 849   * Instead of the "map" field, it's also possible to have a "url" field
 850   * specifying a URL to retrieve a source map from, but that's currently
 851   * unsupported.
 852   *
 853   * Here's an example source map, taken from the source map spec[0], but
 854   * modified to omit a section which uses the "url" field.
 855   *
 856   *  {
 857   *    version : 3,
 858   *    file: "app.js",
 859   *    sections: [{
 860   *      offset: {line:100, column:10},
 861   *      map: {
 862   *        version : 3,
 863   *        file: "section.js",
 864   *        sources: ["foo.js", "bar.js"],
 865   *        names: ["src", "maps", "are", "fun"],
 866   *        mappings: "AAAA,E;;ABCDE;"
 867   *      }
 868   *    }],
 869   *  }
 870   *
 871   * The second parameter, if given, is a string whose value is the URL
 872   * at which the source map was found.  This URL is used to compute the
 873   * sources array.
 874   *
 875   * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#heading=h.535es3xeprgt
 876   */
 877  function IndexedSourceMapConsumer(aSourceMap, aSourceMapURL) {
 878    var sourceMap = aSourceMap;
 879    if (typeof aSourceMap === 'string') {
 880      sourceMap = util.parseSourceMapInput(aSourceMap);
 881    }
 882  
 883    var version = util.getArg(sourceMap, 'version');
 884    var sections = util.getArg(sourceMap, 'sections');
 885  
 886    if (version != this._version) {
 887      throw new Error('Unsupported version: ' + version);
 888    }
 889  
 890    this._sources = new ArraySet();
 891    this._names = new ArraySet();
 892  
 893    var lastOffset = {
 894      line: -1,
 895      column: 0
 896    };
 897    this._sections = sections.map(function (s) {
 898      if (s.url) {
 899        // The url field will require support for asynchronicity.
 900        // See https://github.com/mozilla/source-map/issues/16
 901        throw new Error('Support for url field in sections not implemented.');
 902      }
 903      var offset = util.getArg(s, 'offset');
 904      var offsetLine = util.getArg(offset, 'line');
 905      var offsetColumn = util.getArg(offset, 'column');
 906  
 907      if (offsetLine < lastOffset.line ||
 908          (offsetLine === lastOffset.line && offsetColumn < lastOffset.column)) {
 909        throw new Error('Section offsets must be ordered and non-overlapping.');
 910      }
 911      lastOffset = offset;
 912  
 913      return {
 914        generatedOffset: {
 915          // The offset fields are 0-based, but we use 1-based indices when
 916          // encoding/decoding from VLQ.
 917          generatedLine: offsetLine + 1,
 918          generatedColumn: offsetColumn + 1
 919        },
 920        consumer: new SourceMapConsumer(util.getArg(s, 'map'), aSourceMapURL)
 921      }
 922    });
 923  }
 924  
 925  IndexedSourceMapConsumer.prototype = Object.create(SourceMapConsumer.prototype);
 926  IndexedSourceMapConsumer.prototype.constructor = SourceMapConsumer;
 927  
 928  /**
 929   * The version of the source mapping spec that we are consuming.
 930   */
 931  IndexedSourceMapConsumer.prototype._version = 3;
 932  
 933  /**
 934   * The list of original sources.
 935   */
 936  Object.defineProperty(IndexedSourceMapConsumer.prototype, 'sources', {
 937    get: function () {
 938      var sources = [];
 939      for (var i = 0; i < this._sections.length; i++) {
 940        for (var j = 0; j < this._sections[i].consumer.sources.length; j++) {
 941          sources.push(this._sections[i].consumer.sources[j]);
 942        }
 943      }
 944      return sources;
 945    }
 946  });
 947  
 948  /**
 949   * Returns the original source, line, and column information for the generated
 950   * source's line and column positions provided. The only argument is an object
 951   * with the following properties:
 952   *
 953   *   - line: The line number in the generated source.  The line number
 954   *     is 1-based.
 955   *   - column: The column number in the generated source.  The column
 956   *     number is 0-based.
 957   *
 958   * and an object is returned with the following properties:
 959   *
 960   *   - source: The original source file, or null.
 961   *   - line: The line number in the original source, or null.  The
 962   *     line number is 1-based.
 963   *   - column: The column number in the original source, or null.  The
 964   *     column number is 0-based.
 965   *   - name: The original identifier, or null.
 966   */
 967  IndexedSourceMapConsumer.prototype.originalPositionFor =
 968    function IndexedSourceMapConsumer_originalPositionFor(aArgs) {
 969      var needle = {
 970        generatedLine: util.getArg(aArgs, 'line'),
 971        generatedColumn: util.getArg(aArgs, 'column')
 972      };
 973  
 974      // Find the section containing the generated position we're trying to map
 975      // to an original position.
 976      var sectionIndex = binarySearch.search(needle, this._sections,
 977        function(needle, section) {
 978          var cmp = needle.generatedLine - section.generatedOffset.generatedLine;
 979          if (cmp) {
 980            return cmp;
 981          }
 982  
 983          return (needle.generatedColumn -
 984                  section.generatedOffset.generatedColumn);
 985        });
 986      var section = this._sections[sectionIndex];
 987  
 988      if (!section) {
 989        return {
 990          source: null,
 991          line: null,
 992          column: null,
 993          name: null
 994        };
 995      }
 996  
 997      return section.consumer.originalPositionFor({
 998        line: needle.generatedLine -
 999          (section.generatedOffset.generatedLine - 1),
1000        column: needle.generatedColumn -
1001          (section.generatedOffset.generatedLine === needle.generatedLine
1002           ? section.generatedOffset.generatedColumn - 1
1003           : 0),
1004        bias: aArgs.bias
1005      });
1006    };
1007  
1008  /**
1009   * Return true if we have the source content for every source in the source
1010   * map, false otherwise.
1011   */
1012  IndexedSourceMapConsumer.prototype.hasContentsOfAllSources =
1013    function IndexedSourceMapConsumer_hasContentsOfAllSources() {
1014      return this._sections.every(function (s) {
1015        return s.consumer.hasContentsOfAllSources();
1016      });
1017    };
1018  
1019  /**
1020   * Returns the original source content. The only argument is the url of the
1021   * original source file. Returns null if no original source content is
1022   * available.
1023   */
1024  IndexedSourceMapConsumer.prototype.sourceContentFor =
1025    function IndexedSourceMapConsumer_sourceContentFor(aSource, nullOnMissing) {
1026      for (var i = 0; i < this._sections.length; i++) {
1027        var section = this._sections[i];
1028  
1029        var content = section.consumer.sourceContentFor(aSource, true);
1030        if (content) {
1031          return content;
1032        }
1033      }
1034      if (nullOnMissing) {
1035        return null;
1036      }
1037      else {
1038        throw new Error('"' + aSource + '" is not in the SourceMap.');
1039      }
1040    };
1041  
1042  /**
1043   * Returns the generated line and column information for the original source,
1044   * line, and column positions provided. The only argument is an object with
1045   * the following properties:
1046   *
1047   *   - source: The filename of the original source.
1048   *   - line: The line number in the original source.  The line number
1049   *     is 1-based.
1050   *   - column: The column number in the original source.  The column
1051   *     number is 0-based.
1052   *
1053   * and an object is returned with the following properties:
1054   *
1055   *   - line: The line number in the generated source, or null.  The
1056   *     line number is 1-based. 
1057   *   - column: The column number in the generated source, or null.
1058   *     The column number is 0-based.
1059   */
1060  IndexedSourceMapConsumer.prototype.generatedPositionFor =
1061    function IndexedSourceMapConsumer_generatedPositionFor(aArgs) {
1062      for (var i = 0; i < this._sections.length; i++) {
1063        var section = this._sections[i];
1064  
1065        // Only consider this section if the requested source is in the list of
1066        // sources of the consumer.
1067        if (section.consumer._findSourceIndex(util.getArg(aArgs, 'source')) === -1) {
1068          continue;
1069        }
1070        var generatedPosition = section.consumer.generatedPositionFor(aArgs);
1071        if (generatedPosition) {
1072          var ret = {
1073            line: generatedPosition.line +
1074              (section.generatedOffset.generatedLine - 1),
1075            column: generatedPosition.column +
1076              (section.generatedOffset.generatedLine === generatedPosition.line
1077               ? section.generatedOffset.generatedColumn - 1
1078               : 0)
1079          };
1080          return ret;
1081        }
1082      }
1083  
1084      return {
1085        line: null,
1086        column: null
1087      };
1088    };
1089  
1090  /**
1091   * Parse the mappings in a string in to a data structure which we can easily
1092   * query (the ordered arrays in the `this.__generatedMappings` and
1093   * `this.__originalMappings` properties).
1094   */
1095  IndexedSourceMapConsumer.prototype._parseMappings =
1096    function IndexedSourceMapConsumer_parseMappings(aStr, aSourceRoot) {
1097      this.__generatedMappings = [];
1098      this.__originalMappings = [];
1099      for (var i = 0; i < this._sections.length; i++) {
1100        var section = this._sections[i];
1101        var sectionMappings = section.consumer._generatedMappings;
1102        for (var j = 0; j < sectionMappings.length; j++) {
1103          var mapping = sectionMappings[j];
1104  
1105          var source = section.consumer._sources.at(mapping.source);
1106          source = util.computeSourceURL(section.consumer.sourceRoot, source, this._sourceMapURL);
1107          this._sources.add(source);
1108          source = this._sources.indexOf(source);
1109  
1110          var name = null;
1111          if (mapping.name) {
1112            name = section.consumer._names.at(mapping.name);
1113            this._names.add(name);
1114            name = this._names.indexOf(name);
1115          }
1116  
1117          // The mappings coming from the consumer for the section have
1118          // generated positions relative to the start of the section, so we
1119          // need to offset them to be relative to the start of the concatenated
1120          // generated file.
1121          var adjustedMapping = {
1122            source: source,
1123            generatedLine: mapping.generatedLine +
1124              (section.generatedOffset.generatedLine - 1),
1125            generatedColumn: mapping.generatedColumn +
1126              (section.generatedOffset.generatedLine === mapping.generatedLine
1127              ? section.generatedOffset.generatedColumn - 1
1128              : 0),
1129            originalLine: mapping.originalLine,
1130            originalColumn: mapping.originalColumn,
1131            name: name
1132          };
1133  
1134          this.__generatedMappings.push(adjustedMapping);
1135          if (typeof adjustedMapping.originalLine === 'number') {
1136            this.__originalMappings.push(adjustedMapping);
1137          }
1138        }
1139      }
1140  
1141      quickSort(this.__generatedMappings, util.compareByGeneratedPositionsDeflated);
1142      quickSort(this.__originalMappings, util.compareByOriginalPositions);
1143    };
1144  
1145  exports.IndexedSourceMapConsumer = IndexedSourceMapConsumer;