1 /* -*- Mode: js; js-indent-level: 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
7 if (typeof define !== 'function') {
8 var define = require('amdefine')(module, require);
10 define(function (require, exports, module) {
12 var util = require('./util');
13 var binarySearch = require('./binary-search');
14 var SourceMapConsumer = require('./source-map-consumer').SourceMapConsumer;
15 var BasicSourceMapConsumer = require('./basic-source-map-consumer').BasicSourceMapConsumer;
18 * An IndexedSourceMapConsumer instance represents a parsed source map which
19 * we can query for information. It differs from BasicSourceMapConsumer in
20 * that it takes "indexed" source maps (i.e. ones with a "sections" field) as
23 * The only parameter is a raw source map (either as a JSON string, or already
24 * parsed to an object). According to the spec for indexed source maps, they
25 * have the following attributes:
27 * - version: Which version of the source map spec this map is following.
28 * - file: Optional. The generated file this source map is associated with.
29 * - sections: A list of section definitions.
31 * Each value under the "sections" field has two fields:
32 * - offset: The offset into the original specified at which this section
33 * begins to apply, defined as an object with a "line" and "column"
35 * - map: A source map definition. This source map could also be indexed,
36 * but doesn't have to be.
38 * Instead of the "map" field, it's also possible to have a "url" field
39 * specifying a URL to retrieve a source map from, but that's currently
42 * Here's an example source map, taken from the source map spec[0], but
43 * modified to omit a section which uses the "url" field.
49 * offset: {line:100, column:10},
53 * sources: ["foo.js", "bar.js"],
54 * names: ["src", "maps", "are", "fun"],
55 * mappings: "AAAA,E;;ABCDE;"
60 * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#heading=h.535es3xeprgt
62 function IndexedSourceMapConsumer(aSourceMap) {
63 var sourceMap = aSourceMap;
64 if (typeof aSourceMap === 'string') {
65 sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, ''));
68 var version = util.getArg(sourceMap, 'version');
69 var sections = util.getArg(sourceMap, 'sections');
71 if (version != this._version) {
72 throw new Error('Unsupported version: ' + version);
79 this._sections = sections.map(function (s) {
81 // The url field will require support for asynchronicity.
82 // See https://github.com/mozilla/source-map/issues/16
83 throw new Error('Support for url field in sections not implemented.');
85 var offset = util.getArg(s, 'offset');
86 var offsetLine = util.getArg(offset, 'line');
87 var offsetColumn = util.getArg(offset, 'column');
89 if (offsetLine < lastOffset.line ||
90 (offsetLine === lastOffset.line && offsetColumn < lastOffset.column)) {
91 throw new Error('Section offsets must be ordered and non-overlapping.');
97 // The offset fields are 0-based, but we use 1-based indices when
98 // encoding/decoding from VLQ.
99 generatedLine: offsetLine + 1,
100 generatedColumn: offsetColumn + 1
102 consumer: new SourceMapConsumer(util.getArg(s, 'map'))
107 IndexedSourceMapConsumer.prototype = Object.create(SourceMapConsumer.prototype);
108 IndexedSourceMapConsumer.prototype.constructor = SourceMapConsumer;
111 * The version of the source mapping spec that we are consuming.
113 IndexedSourceMapConsumer.prototype._version = 3;
116 * The list of original sources.
118 Object.defineProperty(IndexedSourceMapConsumer.prototype, 'sources', {
121 for (var i = 0; i < this._sections.length; i++) {
122 for (var j = 0; j < this._sections[i].consumer.sources.length; j++) {
123 sources.push(this._sections[i].consumer.sources[j]);
131 * Returns the original source, line, and column information for the generated
132 * source's line and column positions provided. The only argument is an object
133 * with the following properties:
135 * - line: The line number in the generated source.
136 * - column: The column number in the generated source.
138 * and an object is returned with the following properties:
140 * - source: The original source file, or null.
141 * - line: The line number in the original source, or null.
142 * - column: The column number in the original source, or null.
143 * - name: The original identifier, or null.
145 IndexedSourceMapConsumer.prototype.originalPositionFor =
146 function IndexedSourceMapConsumer_originalPositionFor(aArgs) {
148 generatedLine: util.getArg(aArgs, 'line'),
149 generatedColumn: util.getArg(aArgs, 'column')
152 // Find the section containing the generated position we're trying to map
153 // to an original position.
154 var sectionIndex = binarySearch.search(needle, this._sections,
155 function(needle, section) {
156 var cmp = needle.generatedLine - section.generatedOffset.generatedLine;
161 return (needle.generatedColumn -
162 section.generatedOffset.generatedColumn);
164 var section = this._sections[sectionIndex];
175 return section.consumer.originalPositionFor({
176 line: needle.generatedLine -
177 (section.generatedOffset.generatedLine - 1),
178 column: needle.generatedColumn -
179 (section.generatedOffset.generatedLine === needle.generatedLine
180 ? section.generatedOffset.generatedColumn - 1
186 * Returns the original source content. The only argument is the url of the
187 * original source file. Returns null if no original source content is
190 IndexedSourceMapConsumer.prototype.sourceContentFor =
191 function IndexedSourceMapConsumer_sourceContentFor(aSource, nullOnMissing) {
192 for (var i = 0; i < this._sections.length; i++) {
193 var section = this._sections[i];
195 var content = section.consumer.sourceContentFor(aSource, true);
204 throw new Error('"' + aSource + '" is not in the SourceMap.');
209 * Returns the generated line and column information for the original source,
210 * line, and column positions provided. The only argument is an object with
211 * the following properties:
213 * - source: The filename of the original source.
214 * - line: The line number in the original source.
215 * - column: The column number in the original source.
217 * and an object is returned with the following properties:
219 * - line: The line number in the generated source, or null.
220 * - column: The column number in the generated source, or null.
222 IndexedSourceMapConsumer.prototype.generatedPositionFor =
223 function IndexedSourceMapConsumer_generatedPositionFor(aArgs) {
224 for (var i = 0; i < this._sections.length; i++) {
225 var section = this._sections[i];
227 // Only consider this section if the requested source is in the list of
228 // sources of the consumer.
229 if (section.consumer.sources.indexOf(util.getArg(aArgs, 'source')) === -1) {
232 var generatedPosition = section.consumer.generatedPositionFor(aArgs);
233 if (generatedPosition) {
235 line: generatedPosition.line +
236 (section.generatedOffset.generatedLine - 1),
237 column: generatedPosition.column +
238 (section.generatedOffset.generatedLine === generatedPosition.line
239 ? section.generatedOffset.generatedColumn - 1
253 * Parse the mappings in a string in to a data structure which we can easily
254 * query (the ordered arrays in the `this.__generatedMappings` and
255 * `this.__originalMappings` properties).
257 IndexedSourceMapConsumer.prototype._parseMappings =
258 function IndexedSourceMapConsumer_parseMappings(aStr, aSourceRoot) {
259 this.__generatedMappings = [];
260 this.__originalMappings = [];
261 for (var i = 0; i < this._sections.length; i++) {
262 var section = this._sections[i];
263 var sectionMappings = section.consumer._generatedMappings;
264 for (var j = 0; j < sectionMappings.length; j++) {
265 var mapping = sectionMappings[i];
267 var source = mapping.source;
268 var sourceRoot = section.consumer.sourceRoot;
270 if (source != null && sourceRoot != null) {
271 source = util.join(sourceRoot, source);
274 // The mappings coming from the consumer for the section have
275 // generated positions relative to the start of the section, so we
276 // need to offset them to be relative to the start of the concatenated
278 var adjustedMapping = {
280 generatedLine: mapping.generatedLine +
281 (section.generatedOffset.generatedLine - 1),
282 generatedColumn: mapping.column +
283 (section.generatedOffset.generatedLine === mapping.generatedLine)
284 ? section.generatedOffset.generatedColumn - 1
286 originalLine: mapping.originalLine,
287 originalColumn: mapping.originalColumn,
291 this.__generatedMappings.push(adjustedMapping);
292 if (typeof adjustedMapping.originalLine === 'number') {
293 this.__originalMappings.push(adjustedMapping);
298 this.__generatedMappings.sort(util.compareByGeneratedPositions);
299 this.__originalMappings.sort(util.compareByOriginalPositions);
302 exports.IndexedSourceMapConsumer = IndexedSourceMapConsumer;