| /* -*- 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 |
| */ |
| if (typeof define !== 'function') { |
| var define = require('amdefine')(module, require); |
| } |
| define(function (require, exports, module) { |
| |
| var util = require('./util'); |
| var binarySearch = require('./binary-search'); |
| var SourceMapConsumer = require('./source-map-consumer').SourceMapConsumer; |
| var BasicSourceMapConsumer = require('./basic-source-map-consumer').BasicSourceMapConsumer; |
| |
| /** |
| * An IndexedSourceMapConsumer instance represents a parsed source map which |
| * we can query for information. It differs from BasicSourceMapConsumer in |
| * that it takes "indexed" source maps (i.e. ones with a "sections" field) as |
| * input. |
| * |
| * The only parameter is a raw source map (either as a JSON string, or already |
| * parsed to an object). According to the spec for indexed source maps, they |
| * have the following attributes: |
| * |
| * - version: Which version of the source map spec this map is following. |
| * - file: Optional. The generated file this source map is associated with. |
| * - sections: A list of section definitions. |
| * |
| * Each value under the "sections" field has two fields: |
| * - offset: The offset into the original specified at which this section |
| * begins to apply, defined as an object with a "line" and "column" |
| * field. |
| * - map: A source map definition. This source map could also be indexed, |
| * but doesn't have to be. |
| * |
| * Instead of the "map" field, it's also possible to have a "url" field |
| * specifying a URL to retrieve a source map from, but that's currently |
| * unsupported. |
| * |
| * Here's an example source map, taken from the source map spec[0], but |
| * modified to omit a section which uses the "url" field. |
| * |
| * { |
| * version : 3, |
| * file: "app.js", |
| * sections: [{ |
| * offset: {line:100, column:10}, |
| * map: { |
| * version : 3, |
| * file: "section.js", |
| * sources: ["foo.js", "bar.js"], |
| * names: ["src", "maps", "are", "fun"], |
| * mappings: "AAAA,E;;ABCDE;" |
| * } |
| * }], |
| * } |
| * |
| * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#heading=h.535es3xeprgt |
| */ |
| function IndexedSourceMapConsumer(aSourceMap) { |
| var sourceMap = aSourceMap; |
| if (typeof aSourceMap === 'string') { |
| sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, '')); |
| } |
| |
| var version = util.getArg(sourceMap, 'version'); |
| var sections = util.getArg(sourceMap, 'sections'); |
| |
| if (version != this._version) { |
| throw new Error('Unsupported version: ' + version); |
| } |
| |
| var lastOffset = { |
| line: -1, |
| column: 0 |
| }; |
| this._sections = sections.map(function (s) { |
| if (s.url) { |
| // The url field will require support for asynchronicity. |
| // See https://github.com/mozilla/source-map/issues/16 |
| throw new Error('Support for url field in sections not implemented.'); |
| } |
| var offset = util.getArg(s, 'offset'); |
| var offsetLine = util.getArg(offset, 'line'); |
| var offsetColumn = util.getArg(offset, 'column'); |
| |
| if (offsetLine < lastOffset.line || |
| (offsetLine === lastOffset.line && offsetColumn < lastOffset.column)) { |
| throw new Error('Section offsets must be ordered and non-overlapping.'); |
| } |
| lastOffset = offset; |
| |
| return { |
| generatedOffset: { |
| // The offset fields are 0-based, but we use 1-based indices when |
| // encoding/decoding from VLQ. |
| generatedLine: offsetLine + 1, |
| generatedColumn: offsetColumn + 1 |
| }, |
| consumer: new SourceMapConsumer(util.getArg(s, 'map')) |
| } |
| }); |
| } |
| |
| IndexedSourceMapConsumer.prototype = Object.create(SourceMapConsumer.prototype); |
| IndexedSourceMapConsumer.prototype.constructor = SourceMapConsumer; |
| |
| /** |
| * The version of the source mapping spec that we are consuming. |
| */ |
| IndexedSourceMapConsumer.prototype._version = 3; |
| |
| /** |
| * The list of original sources. |
| */ |
| Object.defineProperty(IndexedSourceMapConsumer.prototype, 'sources', { |
| get: function () { |
| var sources = []; |
| for (var i = 0; i < this._sections.length; i++) { |
| for (var j = 0; j < this._sections[i].consumer.sources.length; j++) { |
| sources.push(this._sections[i].consumer.sources[j]); |
| } |
| }; |
| return sources; |
| } |
| }); |
| |
| /** |
| * Returns the original source, line, and column information for the generated |
| * source's line and column positions provided. The only argument is an object |
| * with the following properties: |
| * |
| * - line: The line number in the generated source. |
| * - column: The column number in the generated source. |
| * |
| * and an object is returned with the following properties: |
| * |
| * - source: The original source file, or null. |
| * - line: The line number in the original source, or null. |
| * - column: The column number in the original source, or null. |
| * - name: The original identifier, or null. |
| */ |
| IndexedSourceMapConsumer.prototype.originalPositionFor = |
| function IndexedSourceMapConsumer_originalPositionFor(aArgs) { |
| var needle = { |
| generatedLine: util.getArg(aArgs, 'line'), |
| generatedColumn: util.getArg(aArgs, 'column') |
| }; |
| |
| // Find the section containing the generated position we're trying to map |
| // to an original position. |
| var sectionIndex = binarySearch.search(needle, this._sections, |
| function(needle, section) { |
| var cmp = needle.generatedLine - section.generatedOffset.generatedLine; |
| if (cmp) { |
| return cmp; |
| } |
| |
| return (needle.generatedColumn - |
| section.generatedOffset.generatedColumn); |
| }); |
| var section = this._sections[sectionIndex]; |
| |
| if (!section) { |
| return { |
| source: null, |
| line: null, |
| column: null, |
| name: null |
| }; |
| } |
| |
| return section.consumer.originalPositionFor({ |
| line: needle.generatedLine - |
| (section.generatedOffset.generatedLine - 1), |
| column: needle.generatedColumn - |
| (section.generatedOffset.generatedLine === needle.generatedLine |
| ? section.generatedOffset.generatedColumn - 1 |
| : 0) |
| }); |
| }; |
| |
| /** |
| * Returns the original source content. The only argument is the url of the |
| * original source file. Returns null if no original source content is |
| * available. |
| */ |
| IndexedSourceMapConsumer.prototype.sourceContentFor = |
| function IndexedSourceMapConsumer_sourceContentFor(aSource, nullOnMissing) { |
| for (var i = 0; i < this._sections.length; i++) { |
| var section = this._sections[i]; |
| |
| var content = section.consumer.sourceContentFor(aSource, true); |
| if (content) { |
| return content; |
| } |
| } |
| if (nullOnMissing) { |
| return null; |
| } |
| else { |
| throw new Error('"' + aSource + '" is not in the SourceMap.'); |
| } |
| }; |
| |
| /** |
| * Returns the generated line and column information for the original source, |
| * line, and column positions provided. The only argument is an object with |
| * the following properties: |
| * |
| * - source: The filename of the original source. |
| * - line: The line number in the original source. |
| * - column: The column number in the original source. |
| * |
| * and an object is returned with the following properties: |
| * |
| * - line: The line number in the generated source, or null. |
| * - column: The column number in the generated source, or null. |
| */ |
| IndexedSourceMapConsumer.prototype.generatedPositionFor = |
| function IndexedSourceMapConsumer_generatedPositionFor(aArgs) { |
| for (var i = 0; i < this._sections.length; i++) { |
| var section = this._sections[i]; |
| |
| // Only consider this section if the requested source is in the list of |
| // sources of the consumer. |
| if (section.consumer.sources.indexOf(util.getArg(aArgs, 'source')) === -1) { |
| continue; |
| } |
| var generatedPosition = section.consumer.generatedPositionFor(aArgs); |
| if (generatedPosition) { |
| var ret = { |
| line: generatedPosition.line + |
| (section.generatedOffset.generatedLine - 1), |
| column: generatedPosition.column + |
| (section.generatedOffset.generatedLine === generatedPosition.line |
| ? section.generatedOffset.generatedColumn - 1 |
| : 0) |
| }; |
| return ret; |
| } |
| } |
| |
| return { |
| line: null, |
| column: null |
| }; |
| }; |
| |
| /** |
| * Parse the mappings in a string in to a data structure which we can easily |
| * query (the ordered arrays in the `this.__generatedMappings` and |
| * `this.__originalMappings` properties). |
| */ |
| IndexedSourceMapConsumer.prototype._parseMappings = |
| function IndexedSourceMapConsumer_parseMappings(aStr, aSourceRoot) { |
| this.__generatedMappings = []; |
| this.__originalMappings = []; |
| for (var i = 0; i < this._sections.length; i++) { |
| var section = this._sections[i]; |
| var sectionMappings = section.consumer._generatedMappings; |
| for (var j = 0; j < sectionMappings.length; j++) { |
| var mapping = sectionMappings[i]; |
| |
| var source = mapping.source; |
| var sourceRoot = section.consumer.sourceRoot; |
| |
| if (source != null && sourceRoot != null) { |
| source = util.join(sourceRoot, source); |
| } |
| |
| // The mappings coming from the consumer for the section have |
| // generated positions relative to the start of the section, so we |
| // need to offset them to be relative to the start of the concatenated |
| // generated file. |
| var adjustedMapping = { |
| source: source, |
| generatedLine: mapping.generatedLine + |
| (section.generatedOffset.generatedLine - 1), |
| generatedColumn: mapping.column + |
| (section.generatedOffset.generatedLine === mapping.generatedLine) |
| ? section.generatedOffset.generatedColumn - 1 |
| : 0, |
| originalLine: mapping.originalLine, |
| originalColumn: mapping.originalColumn, |
| name: mapping.name |
| }; |
| |
| this.__generatedMappings.push(adjustedMapping); |
| if (typeof adjustedMapping.originalLine === 'number') { |
| this.__originalMappings.push(adjustedMapping); |
| } |
| }; |
| }; |
| |
| this.__generatedMappings.sort(util.compareByGeneratedPositions); |
| this.__originalMappings.sort(util.compareByOriginalPositions); |
| }; |
| |
| exports.IndexedSourceMapConsumer = IndexedSourceMapConsumer; |
| }); |