stackdriver-errors-js
Version:
Experimental client-side JavaScript library to report errors to Stackdriver Error Reporting
1,311 lines (1,174 loc) • 114 kB
JavaScript
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.StackdriverErrorReporter = f()}})(function(){var define,module,exports;
var createModuleFactory = function createModuleFactory(t){var e;return function(r){return e||t(e={exports:{},parent:r},e.exports),e.exports}};
var _$stacktraceGps_11 = createModuleFactory(function (module, exports) {
(function(root, factory) {
'use strict';
// Universal Module Definition (UMD) to support AMD, CommonJS/Node.js, Rhino, and browsers.
/* istanbul ignore next */
if (typeof define === 'function' && define.amd) {
define('stacktrace-gps', ['source-map', 'stackframe'], factory);
} else if (typeof exports === 'object') {
module.exports = factory(_$sourceMapConsumer_7({}), _$stackframe_10({}));
} else {
root.StackTraceGPS = factory(root.SourceMap || root.sourceMap, root.StackFrame);
}
}(this, function(SourceMap, StackFrame) {
'use strict';
/**
* Make a X-Domain request to url and callback.
*
* @param {String} url
* @returns {Promise} with response text if fulfilled
*/
function _xdr(url) {
return new Promise(function(resolve, reject) {
var req = new XMLHttpRequest();
req.open('get', url);
req.onerror = reject;
req.onreadystatechange = function onreadystatechange() {
if (req.readyState === 4) {
if ((req.status >= 200 && req.status < 300) ||
(url.substr(0, 7) === 'file://' && req.responseText)) {
resolve(req.responseText);
} else {
reject(new Error('HTTP status: ' + req.status + ' retrieving ' + url));
}
}
};
req.send();
});
}
/**
* Convert a Base64-encoded string into its original representation.
* Used for inline sourcemaps.
*
* @param {String} b64str Base-64 encoded string
* @returns {String} original representation of the base64-encoded string.
*/
function _atob(b64str) {
if (typeof window !== 'undefined' && window.atob) {
return window.atob(b64str);
} else {
throw new Error('You must supply a polyfill for window.atob in this environment');
}
}
function _parseJson(string) {
if (typeof JSON !== 'undefined' && JSON.parse) {
return JSON.parse(string);
} else {
throw new Error('You must supply a polyfill for JSON.parse in this environment');
}
}
function _findFunctionName(source, lineNumber/*, columnNumber*/) {
var syntaxes = [
// {name} = function ({args}) TODO args capture
/['"]?([$_A-Za-z][$_A-Za-z0-9]*)['"]?\s*[:=]\s*function\b/,
// function {name}({args}) m[1]=name m[2]=args
/function\s+([^('"`]*?)\s*\(([^)]*)\)/,
// {name} = eval()
/['"]?([$_A-Za-z][$_A-Za-z0-9]*)['"]?\s*[:=]\s*(?:eval|new Function)\b/,
// fn_name() {
/\b(?!(?:if|for|switch|while|with|catch)\b)(?:(?:static)\s+)?(\S+)\s*\(.*?\)\s*\{/,
// {name} = () => {
/['"]?([$_A-Za-z][$_A-Za-z0-9]*)['"]?\s*[:=]\s*\(.*?\)\s*=>/
];
var lines = source.split('\n');
// Walk backwards in the source lines until we find the line which matches one of the patterns above
var code = '';
var maxLines = Math.min(lineNumber, 20);
for (var i = 0; i < maxLines; ++i) {
// lineNo is 1-based, source[] is 0-based
var line = lines[lineNumber - i - 1];
var commentPos = line.indexOf('//');
if (commentPos >= 0) {
line = line.substr(0, commentPos);
}
if (line) {
code = line + code;
var len = syntaxes.length;
for (var index = 0; index < len; index++) {
var m = syntaxes[index].exec(code);
if (m && m[1]) {
return m[1];
}
}
}
}
return undefined;
}
function _ensureSupportedEnvironment() {
if (typeof Object.defineProperty !== 'function' || typeof Object.create !== 'function') {
throw new Error('Unable to consume source maps in older browsers');
}
}
function _ensureStackFrameIsLegit(stackframe) {
if (typeof stackframe !== 'object') {
throw new TypeError('Given StackFrame is not an object');
} else if (typeof stackframe.fileName !== 'string') {
throw new TypeError('Given file name is not a String');
} else if (typeof stackframe.lineNumber !== 'number' ||
stackframe.lineNumber % 1 !== 0 ||
stackframe.lineNumber < 1) {
throw new TypeError('Given line number must be a positive integer');
} else if (typeof stackframe.columnNumber !== 'number' ||
stackframe.columnNumber % 1 !== 0 ||
stackframe.columnNumber < 0) {
throw new TypeError('Given column number must be a non-negative integer');
}
return true;
}
function _findSourceMappingURL(source) {
var sourceMappingUrlRegExp = /\/\/[#@] ?sourceMappingURL=([^\s'"]+)\s*$/mg;
var lastSourceMappingUrl;
var matchSourceMappingUrl;
// eslint-disable-next-line no-cond-assign
while (matchSourceMappingUrl = sourceMappingUrlRegExp.exec(source)) {
lastSourceMappingUrl = matchSourceMappingUrl[1];
}
if (lastSourceMappingUrl) {
return lastSourceMappingUrl;
} else {
throw new Error('sourceMappingURL not found');
}
}
function _extractLocationInfoFromSourceMapSource(stackframe, sourceMapConsumer, sourceCache) {
return new Promise(function(resolve, reject) {
var loc = sourceMapConsumer.originalPositionFor({
line: stackframe.lineNumber,
column: stackframe.columnNumber
});
if (loc.source) {
// cache mapped sources
var mappedSource = sourceMapConsumer.sourceContentFor(loc.source);
if (mappedSource) {
sourceCache[loc.source] = mappedSource;
}
resolve(
// given stackframe and source location, update stackframe
new StackFrame({
functionName: loc.name || stackframe.functionName,
args: stackframe.args,
fileName: loc.source,
lineNumber: loc.line,
columnNumber: loc.column
}));
} else {
reject(new Error('Could not get original source for given stackframe and source map'));
}
});
}
/**
* @constructor
* @param {Object} opts
* opts.sourceCache = {url: "Source String"} => preload source cache
* opts.sourceMapConsumerCache = {/path/file.js.map: SourceMapConsumer}
* opts.offline = True to prevent network requests.
* Best effort without sources or source maps.
* opts.ajax = Promise returning function to make X-Domain requests
*/
return function StackTraceGPS(opts) {
if (!(this instanceof StackTraceGPS)) {
return new StackTraceGPS(opts);
}
opts = opts || {};
this.sourceCache = opts.sourceCache || {};
this.sourceMapConsumerCache = opts.sourceMapConsumerCache || {};
this.ajax = opts.ajax || _xdr;
this._atob = opts.atob || _atob;
this._get = function _get(location) {
return new Promise(function(resolve, reject) {
var isDataUrl = location.substr(0, 5) === 'data:';
if (this.sourceCache[location]) {
resolve(this.sourceCache[location]);
} else if (opts.offline && !isDataUrl) {
reject(new Error('Cannot make network requests in offline mode'));
} else {
if (isDataUrl) {
// data URLs can have parameters.
// see http://tools.ietf.org/html/rfc2397
var supportedEncodingRegexp =
/^data:application\/json;([\w=:"-]+;)*base64,/;
var match = location.match(supportedEncodingRegexp);
if (match) {
var sourceMapStart = match[0].length;
var encodedSource = location.substr(sourceMapStart);
var source = this._atob(encodedSource);
this.sourceCache[location] = source;
resolve(source);
} else {
reject(new Error('The encoding of the inline sourcemap is not supported'));
}
} else {
var xhrPromise = this.ajax(location, {method: 'get'});
// Cache the Promise to prevent duplicate in-flight requests
this.sourceCache[location] = xhrPromise;
xhrPromise.then(resolve, reject);
}
}
}.bind(this));
};
/**
* Creating SourceMapConsumers is expensive, so this wraps the creation of a
* SourceMapConsumer in a per-instance cache.
*
* @param {String} sourceMappingURL = URL to fetch source map from
* @param {String} defaultSourceRoot = Default source root for source map if undefined
* @returns {Promise} that resolves a SourceMapConsumer
*/
this._getSourceMapConsumer = function _getSourceMapConsumer(sourceMappingURL, defaultSourceRoot) {
return new Promise(function(resolve) {
if (this.sourceMapConsumerCache[sourceMappingURL]) {
resolve(this.sourceMapConsumerCache[sourceMappingURL]);
} else {
var sourceMapConsumerPromise = new Promise(function(resolve, reject) {
return this._get(sourceMappingURL).then(function(sourceMapSource) {
if (typeof sourceMapSource === 'string') {
sourceMapSource = _parseJson(sourceMapSource.replace(/^\)\]\}'/, ''));
}
if (typeof sourceMapSource.sourceRoot === 'undefined') {
sourceMapSource.sourceRoot = defaultSourceRoot;
}
resolve(new SourceMap.SourceMapConsumer(sourceMapSource));
}, reject);
}.bind(this));
this.sourceMapConsumerCache[sourceMappingURL] = sourceMapConsumerPromise;
resolve(sourceMapConsumerPromise);
}
}.bind(this));
};
/**
* Given a StackFrame, enhance function name and use source maps for a
* better StackFrame.
*
* @param {StackFrame} stackframe object
* @returns {Promise} that resolves with with source-mapped StackFrame
*/
this.pinpoint = function StackTraceGPS$$pinpoint(stackframe) {
return new Promise(function(resolve, reject) {
this.getMappedLocation(stackframe).then(function(mappedStackFrame) {
function resolveMappedStackFrame() {
resolve(mappedStackFrame);
}
this.findFunctionName(mappedStackFrame)
.then(resolve, resolveMappedStackFrame)
// eslint-disable-next-line no-unexpected-multiline
['catch'](resolveMappedStackFrame);
}.bind(this), reject);
}.bind(this));
};
/**
* Given a StackFrame, guess function name from location information.
*
* @param {StackFrame} stackframe
* @returns {Promise} that resolves with enhanced StackFrame.
*/
this.findFunctionName = function StackTraceGPS$$findFunctionName(stackframe) {
return new Promise(function(resolve, reject) {
_ensureStackFrameIsLegit(stackframe);
this._get(stackframe.fileName).then(function getSourceCallback(source) {
var lineNumber = stackframe.lineNumber;
var columnNumber = stackframe.columnNumber;
var guessedFunctionName = _findFunctionName(source, lineNumber, columnNumber);
// Only replace functionName if we found something
if (guessedFunctionName) {
resolve(new StackFrame({
functionName: guessedFunctionName,
args: stackframe.args,
fileName: stackframe.fileName,
lineNumber: lineNumber,
columnNumber: columnNumber
}));
} else {
resolve(stackframe);
}
}, reject)['catch'](reject);
}.bind(this));
};
/**
* Given a StackFrame, seek source-mapped location and return new enhanced StackFrame.
*
* @param {StackFrame} stackframe
* @returns {Promise} that resolves with enhanced StackFrame.
*/
this.getMappedLocation = function StackTraceGPS$$getMappedLocation(stackframe) {
return new Promise(function(resolve, reject) {
_ensureSupportedEnvironment();
_ensureStackFrameIsLegit(stackframe);
var sourceCache = this.sourceCache;
var fileName = stackframe.fileName;
this._get(fileName).then(function(source) {
var sourceMappingURL = _findSourceMappingURL(source);
var isDataUrl = sourceMappingURL.substr(0, 5) === 'data:';
var defaultSourceRoot = fileName.substring(0, fileName.lastIndexOf('/') + 1);
if (sourceMappingURL[0] !== '/' && !isDataUrl && !(/^https?:\/\/|^\/\//i).test(sourceMappingURL)) {
sourceMappingURL = defaultSourceRoot + sourceMappingURL;
}
return this._getSourceMapConsumer(sourceMappingURL, defaultSourceRoot)
.then(function(sourceMapConsumer) {
return _extractLocationInfoFromSourceMapSource(stackframe, sourceMapConsumer, sourceCache)
.then(resolve)['catch'](function() {
resolve(stackframe);
});
});
}.bind(this), reject)['catch'](reject);
}.bind(this));
};
};
}));
});
var _$sourceMapConsumer_7 = createModuleFactory(function (module, exports) {
/* -*- 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
*/
/* removed: var _$util_8 = require('./util'); */;
/* removed: var _$binarySearch_5 = require('./binary-search'); */;
var ArraySet = _$arraySet_2.ArraySet;
/* removed: var _$base64Vlq_3 = require('./base64-vlq'); */;
var quickSort = _$quickSort_6.quickSort;
function SourceMapConsumer(aSourceMap) {
var sourceMap = aSourceMap;
if (typeof aSourceMap === 'string') {
sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, ''));
}
return sourceMap.sections != null
? new IndexedSourceMapConsumer(sourceMap)
: new BasicSourceMapConsumer(sourceMap);
}
SourceMapConsumer.fromSourceMap = function(aSourceMap) {
return BasicSourceMapConsumer.fromSourceMap(aSourceMap);
}
/**
* The version of the source mapping spec that we are consuming.
*/
SourceMapConsumer.prototype._version = 3;
// `__generatedMappings` and `__originalMappings` are arrays that hold the
// parsed mapping coordinates from the source map's "mappings" attribute. They
// are lazily instantiated, accessed via the `_generatedMappings` and
// `_originalMappings` getters respectively, and we only parse the mappings
// and create these arrays once queried for a source location. We jump through
// these hoops because there can be many thousands of mappings, and parsing
// them is expensive, so we only want to do it if we must.
//
// Each object in the arrays is of the form:
//
// {
// generatedLine: The line number in the generated code,
// generatedColumn: The column number in the generated code,
// source: The path to the original source file that generated this
// chunk of code,
// originalLine: The line number in the original source that
// corresponds to this chunk of generated code,
// originalColumn: The column number in the original source that
// corresponds to this chunk of generated code,
// name: The name of the original symbol which generated this chunk of
// code.
// }
//
// All properties except for `generatedLine` and `generatedColumn` can be
// `null`.
//
// `_generatedMappings` is ordered by the generated positions.
//
// `_originalMappings` is ordered by the original positions.
SourceMapConsumer.prototype.__generatedMappings = null;
Object.defineProperty(SourceMapConsumer.prototype, '_generatedMappings', {
get: function () {
if (!this.__generatedMappings) {
this._parseMappings(this._mappings, this.sourceRoot);
}
return this.__generatedMappings;
}
});
SourceMapConsumer.prototype.__originalMappings = null;
Object.defineProperty(SourceMapConsumer.prototype, '_originalMappings', {
get: function () {
if (!this.__originalMappings) {
this._parseMappings(this._mappings, this.sourceRoot);
}
return this.__originalMappings;
}
});
SourceMapConsumer.prototype._charIsMappingSeparator =
function SourceMapConsumer_charIsMappingSeparator(aStr, index) {
var c = aStr.charAt(index);
return c === ";" || c === ",";
};
/**
* 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).
*/
SourceMapConsumer.prototype._parseMappings =
function SourceMapConsumer_parseMappings(aStr, aSourceRoot) {
throw new Error("Subclasses must implement _parseMappings");
};
SourceMapConsumer.GENERATED_ORDER = 1;
SourceMapConsumer.ORIGINAL_ORDER = 2;
SourceMapConsumer.GREATEST_LOWER_BOUND = 1;
SourceMapConsumer.LEAST_UPPER_BOUND = 2;
/**
* Iterate over each mapping between an original source/line/column and a
* generated line/column in this source map.
*
* @param Function aCallback
* The function that is called with each mapping.
* @param Object aContext
* Optional. If specified, this object will be the value of `this` every
* time that `aCallback` is called.
* @param aOrder
* Either `SourceMapConsumer.GENERATED_ORDER` or
* `SourceMapConsumer.ORIGINAL_ORDER`. Specifies whether you want to
* iterate over the mappings sorted by the generated file's line/column
* order or the original's source/line/column order, respectively. Defaults to
* `SourceMapConsumer.GENERATED_ORDER`.
*/
SourceMapConsumer.prototype.eachMapping =
function SourceMapConsumer_eachMapping(aCallback, aContext, aOrder) {
var context = aContext || null;
var order = aOrder || SourceMapConsumer.GENERATED_ORDER;
var mappings;
switch (order) {
case SourceMapConsumer.GENERATED_ORDER:
mappings = this._generatedMappings;
break;
case SourceMapConsumer.ORIGINAL_ORDER:
mappings = this._originalMappings;
break;
default:
throw new Error("Unknown order of iteration.");
}
var sourceRoot = this.sourceRoot;
mappings.map(function (mapping) {
var source = mapping.source === null ? null : this._sources.at(mapping.source);
if (source != null && sourceRoot != null) {
source = _$util_8.join(sourceRoot, source);
}
return {
source: source,
generatedLine: mapping.generatedLine,
generatedColumn: mapping.generatedColumn,
originalLine: mapping.originalLine,
originalColumn: mapping.originalColumn,
name: mapping.name === null ? null : this._names.at(mapping.name)
};
}, this).forEach(aCallback, context);
};
/**
* Returns all generated line and column information for the original source,
* line, and column provided. If no column is provided, returns all mappings
* corresponding to a either the line we are searching for or the next
* closest line that has any mappings. Otherwise, returns all mappings
* corresponding to the given line and either the column we are searching for
* or the next closest column that has any offsets.
*
* 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: Optional. the column number in the original source.
*
* and an array of objects is returned, each with the following properties:
*
* - line: The line number in the generated source, or null.
* - column: The column number in the generated source, or null.
*/
SourceMapConsumer.prototype.allGeneratedPositionsFor =
function SourceMapConsumer_allGeneratedPositionsFor(aArgs) {
var line = _$util_8.getArg(aArgs, 'line');
// When there is no exact match, BasicSourceMapConsumer.prototype._findMapping
// returns the index of the closest mapping less than the needle. By
// setting needle.originalColumn to 0, we thus find the last mapping for
// the given line, provided such a mapping exists.
var needle = {
source: _$util_8.getArg(aArgs, 'source'),
originalLine: line,
originalColumn: _$util_8.getArg(aArgs, 'column', 0)
};
if (this.sourceRoot != null) {
needle.source = _$util_8.relative(this.sourceRoot, needle.source);
}
if (!this._sources.has(needle.source)) {
return [];
}
needle.source = this._sources.indexOf(needle.source);
var mappings = [];
var index = this._findMapping(needle,
this._originalMappings,
"originalLine",
"originalColumn",
_$util_8.compareByOriginalPositions,
_$binarySearch_5.LEAST_UPPER_BOUND);
if (index >= 0) {
var mapping = this._originalMappings[index];
if (aArgs.column === undefined) {
var originalLine = mapping.originalLine;
// Iterate until either we run out of mappings, or we run into
// a mapping for a different line than the one we found. Since
// mappings are sorted, this is guaranteed to find all mappings for
// the line we found.
while (mapping && mapping.originalLine === originalLine) {
mappings.push({
line: _$util_8.getArg(mapping, 'generatedLine', null),
column: _$util_8.getArg(mapping, 'generatedColumn', null),
lastColumn: _$util_8.getArg(mapping, 'lastGeneratedColumn', null)
});
mapping = this._originalMappings[++index];
}
} else {
var originalColumn = mapping.originalColumn;
// Iterate until either we run out of mappings, or we run into
// a mapping for a different line than the one we were searching for.
// Since mappings are sorted, this is guaranteed to find all mappings for
// the line we are searching for.
while (mapping &&
mapping.originalLine === line &&
mapping.originalColumn == originalColumn) {
mappings.push({
line: _$util_8.getArg(mapping, 'generatedLine', null),
column: _$util_8.getArg(mapping, 'generatedColumn', null),
lastColumn: _$util_8.getArg(mapping, 'lastGeneratedColumn', null)
});
mapping = this._originalMappings[++index];
}
}
}
return mappings;
};
exports.SourceMapConsumer = SourceMapConsumer;
/**
* A BasicSourceMapConsumer instance represents a parsed source map which we can
* query for information about the original file positions by giving it a file
* position in the generated source.
*
* The only parameter is the raw source map (either as a JSON string, or
* already parsed to an object). According to the spec, source maps have the
* following attributes:
*
* - version: Which version of the source map spec this map is following.
* - sources: An array of URLs to the original source files.
* - names: An array of identifiers which can be referrenced by individual mappings.
* - sourceRoot: Optional. The URL root from which all sources are relative.
* - sourcesContent: Optional. An array of contents of the original source files.
* - mappings: A string of base64 VLQs which contain the actual mappings.
* - file: Optional. The generated file this source map is associated with.
*
* Here is an example source map, taken from the source map spec[0]:
*
* {
* version : 3,
* file: "out.js",
* sourceRoot : "",
* sources: ["foo.js", "bar.js"],
* names: ["src", "maps", "are", "fun"],
* mappings: "AA,AB;;ABCDE;"
* }
*
* [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit?pli=1#
*/
function BasicSourceMapConsumer(aSourceMap) {
var sourceMap = aSourceMap;
if (typeof aSourceMap === 'string') {
sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, ''));
}
var version = _$util_8.getArg(sourceMap, 'version');
var sources = _$util_8.getArg(sourceMap, 'sources');
// Sass 3.3 leaves out the 'names' array, so we deviate from the spec (which
// requires the array) to play nice here.
var names = _$util_8.getArg(sourceMap, 'names', []);
var sourceRoot = _$util_8.getArg(sourceMap, 'sourceRoot', null);
var sourcesContent = _$util_8.getArg(sourceMap, 'sourcesContent', null);
var mappings = _$util_8.getArg(sourceMap, 'mappings');
var file = _$util_8.getArg(sourceMap, 'file', null);
// Once again, Sass deviates from the spec and supplies the version as a
// string rather than a number, so we use loose equality checking here.
if (version != this._version) {
throw new Error('Unsupported version: ' + version);
}
sources = sources
.map(String)
// Some source maps produce relative source paths like "./foo.js" instead of
// "foo.js". Normalize these first so that future comparisons will succeed.
// See bugzil.la/1090768.
.map(_$util_8.normalize)
// Always ensure that absolute sources are internally stored relative to
// the source root, if the source root is absolute. Not doing this would
// be particularly problematic when the source root is a prefix of the
// source (valid, but why??). See github issue #199 and bugzil.la/1188982.
.map(function (source) {
return sourceRoot && _$util_8.isAbsolute(sourceRoot) && _$util_8.isAbsolute(source)
? _$util_8.relative(sourceRoot, source)
: source;
});
// Pass `true` below to allow duplicate names and sources. While source maps
// are intended to be compressed and deduplicated, the TypeScript compiler
// sometimes generates source maps with duplicates in them. See Github issue
// #72 and bugzil.la/889492.
this._names = ArraySet.fromArray(names.map(String), true);
this._sources = ArraySet.fromArray(sources, true);
this.sourceRoot = sourceRoot;
this.sourcesContent = sourcesContent;
this._mappings = mappings;
this.file = file;
}
BasicSourceMapConsumer.prototype = Object.create(SourceMapConsumer.prototype);
BasicSourceMapConsumer.prototype.consumer = SourceMapConsumer;
/**
* Create a BasicSourceMapConsumer from a SourceMapGenerator.
*
* @param SourceMapGenerator aSourceMap
* The source map that will be consumed.
* @returns BasicSourceMapConsumer
*/
BasicSourceMapConsumer.fromSourceMap =
function SourceMapConsumer_fromSourceMap(aSourceMap) {
var smc = Object.create(BasicSourceMapConsumer.prototype);
var names = smc._names = ArraySet.fromArray(aSourceMap._names.toArray(), true);
var sources = smc._sources = ArraySet.fromArray(aSourceMap._sources.toArray(), true);
smc.sourceRoot = aSourceMap._sourceRoot;
smc.sourcesContent = aSourceMap._generateSourcesContent(smc._sources.toArray(),
smc.sourceRoot);
smc.file = aSourceMap._file;
// Because we are modifying the entries (by converting string sources and
// names to indices into the sources and names ArraySets), we have to make
// a copy of the entry or else bad things happen. Shared mutable state
// strikes again! See github issue #191.
var generatedMappings = aSourceMap._mappings.toArray().slice();
var destGeneratedMappings = smc.__generatedMappings = [];
var destOriginalMappings = smc.__originalMappings = [];
for (var i = 0, length = generatedMappings.length; i < length; i++) {
var srcMapping = generatedMappings[i];
var destMapping = new Mapping;
destMapping.generatedLine = srcMapping.generatedLine;
destMapping.generatedColumn = srcMapping.generatedColumn;
if (srcMapping.source) {
destMapping.source = sources.indexOf(srcMapping.source);
destMapping.originalLine = srcMapping.originalLine;
destMapping.originalColumn = srcMapping.originalColumn;
if (srcMapping.name) {
destMapping.name = names.indexOf(srcMapping.name);
}
destOriginalMappings.push(destMapping);
}
destGeneratedMappings.push(destMapping);
}
quickSort(smc.__originalMappings, _$util_8.compareByOriginalPositions);
return smc;
};
/**
* The version of the source mapping spec that we are consuming.
*/
BasicSourceMapConsumer.prototype._version = 3;
/**
* The list of original sources.
*/
Object.defineProperty(BasicSourceMapConsumer.prototype, 'sources', {
get: function () {
return this._sources.toArray().map(function (s) {
return this.sourceRoot != null ? _$util_8.join(this.sourceRoot, s) : s;
}, this);
}
});
/**
* Provide the JIT with a nice shape / hidden class.
*/
function Mapping() {
this.generatedLine = 0;
this.generatedColumn = 0;
this.source = null;
this.originalLine = null;
this.originalColumn = null;
this.name = 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).
*/
BasicSourceMapConsumer.prototype._parseMappings =
function SourceMapConsumer_parseMappings(aStr, aSourceRoot) {
var generatedLine = 1;
var previousGeneratedColumn = 0;
var previousOriginalLine = 0;
var previousOriginalColumn = 0;
var previousSource = 0;
var previousName = 0;
var length = aStr.length;
var index = 0;
var cachedSegments = {};
var temp = {};
var originalMappings = [];
var generatedMappings = [];
var mapping, str, segment, end, value;
while (index < length) {
if (aStr.charAt(index) === ';') {
generatedLine++;
index++;
previousGeneratedColumn = 0;
}
else if (aStr.charAt(index) === ',') {
index++;
}
else {
mapping = new Mapping();
mapping.generatedLine = generatedLine;
// Because each offset is encoded relative to the previous one,
// many segments often have the same encoding. We can exploit this
// fact by caching the parsed variable length fields of each segment,
// allowing us to avoid a second parse if we encounter the same
// segment again.
for (end = index; end < length; end++) {
if (this._charIsMappingSeparator(aStr, end)) {
break;
}
}
str = aStr.slice(index, end);
segment = cachedSegments[str];
if (segment) {
index += str.length;
} else {
segment = [];
while (index < end) {
_$base64Vlq_3.decode(aStr, index, temp);
value = temp.value;
index = temp.rest;
segment.push(value);
}
if (segment.length === 2) {
throw new Error('Found a source, but no line and column');
}
if (segment.length === 3) {
throw new Error('Found a source and line, but no column');
}
cachedSegments[str] = segment;
}
// Generated column.
mapping.generatedColumn = previousGeneratedColumn + segment[0];
previousGeneratedColumn = mapping.generatedColumn;
if (segment.length > 1) {
// Original source.
mapping.source = previousSource + segment[1];
previousSource += segment[1];
// Original line.
mapping.originalLine = previousOriginalLine + segment[2];
previousOriginalLine = mapping.originalLine;
// Lines are stored 0-based
mapping.originalLine += 1;
// Original column.
mapping.originalColumn = previousOriginalColumn + segment[3];
previousOriginalColumn = mapping.originalColumn;
if (segment.length > 4) {
// Original name.
mapping.name = previousName + segment[4];
previousName += segment[4];
}
}
generatedMappings.push(mapping);
if (typeof mapping.originalLine === 'number') {
originalMappings.push(mapping);
}
}
}
quickSort(generatedMappings, _$util_8.compareByGeneratedPositionsDeflated);
this.__generatedMappings = generatedMappings;
quickSort(originalMappings, _$util_8.compareByOriginalPositions);
this.__originalMappings = originalMappings;
};
/**
* Find the mapping that best matches the hypothetical "needle" mapping that
* we are searching for in the given "haystack" of mappings.
*/
BasicSourceMapConsumer.prototype._findMapping =
function SourceMapConsumer_findMapping(aNeedle, aMappings, aLineName,
aColumnName, aComparator, aBias) {
// To return the position we are searching for, we must first find the
// mapping for the given position and then return the opposite position it
// points to. Because the mappings are sorted, we can use binary search to
// find the best mapping.
if (aNeedle[aLineName] <= 0) {
throw new TypeError('Line must be greater than or equal to 1, got '
+ aNeedle[aLineName]);
}
if (aNeedle[aColumnName] < 0) {
throw new TypeError('Column must be greater than or equal to 0, got '
+ aNeedle[aColumnName]);
}
return _$binarySearch_5.search(aNeedle, aMappings, aComparator, aBias);
};
/**
* Compute the last column for each generated mapping. The last column is
* inclusive.
*/
BasicSourceMapConsumer.prototype.computeColumnSpans =
function SourceMapConsumer_computeColumnSpans() {
for (var index = 0; index < this._generatedMappings.length; ++index) {
var mapping = this._generatedMappings[index];
// Mappings do not contain a field for the last generated columnt. We
// can come up with an optimistic estimate, however, by assuming that
// mappings are contiguous (i.e. given two consecutive mappings, the
// first mapping ends where the second one starts).
if (index + 1 < this._generatedMappings.length) {
var nextMapping = this._generatedMappings[index + 1];
if (mapping.generatedLine === nextMapping.generatedLine) {
mapping.lastGeneratedColumn = nextMapping.generatedColumn - 1;
continue;
}
}
// The last mapping for each line spans the entire line.
mapping.lastGeneratedColumn = Infinity;
}
};
/**
* 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.
* - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or
* 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the
* closest element that is smaller than or greater than the one we are
* searching for, respectively, if the exact element cannot be found.
* Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'.
*
* 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.
*/
BasicSourceMapConsumer.prototype.originalPositionFor =
function SourceMapConsumer_originalPositionFor(aArgs) {
var needle = {
generatedLine: _$util_8.getArg(aArgs, 'line'),
generatedColumn: _$util_8.getArg(aArgs, 'column')
};
var index = this._findMapping(
needle,
this._generatedMappings,
"generatedLine",
"generatedColumn",
_$util_8.compareByGeneratedPositionsDeflated,
_$util_8.getArg(aArgs, 'bias', SourceMapConsumer.GREATEST_LOWER_BOUND)
);
if (index >= 0) {
var mapping = this._generatedMappings[index];
if (mapping.generatedLine === needle.generatedLine) {
var source = _$util_8.getArg(mapping, 'source', null);
if (source !== null) {
source = this._sources.at(source);
if (this.sourceRoot != null) {
source = _$util_8.join(this.sourceRoot, source);
}
}
var name = _$util_8.getArg(mapping, 'name', null);
if (name !== null) {
name = this._names.at(name);
}
return {
source: source,
line: _$util_8.getArg(mapping, 'originalLine', null),
column: _$util_8.getArg(mapping, 'originalColumn', null),
name: name
};
}
}
return {
source: null,
line: null,
column: null,
name: null
};
};
/**
* Return true if we have the source content for every source in the source
* map, false otherwise.
*/
BasicSourceMapConsumer.prototype.hasContentsOfAllSources =
function BasicSourceMapConsumer_hasContentsOfAllSources() {
if (!this.sourcesContent) {
return false;
}
return this.sourcesContent.length >= this._sources.size() &&
!this.sourcesContent.some(function (sc) { return sc == null; });
};
/**
* 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.
*/
BasicSourceMapConsumer.prototype.sourceContentFor =
function SourceMapConsumer_sourceContentFor(aSource, nullOnMissing) {
if (!this.sourcesContent) {
return null;
}
if (this.sourceRoot != null) {
aSource = _$util_8.relative(this.sourceRoot, aSource);
}
if (this._sources.has(aSource)) {
return this.sourcesContent[this._sources.indexOf(aSource)];
}
var url;
if (this.sourceRoot != null
&& (url = _$util_8.urlParse(this.sourceRoot))) {
// XXX: file:// URIs and absolute paths lead to unexpected behavior for
// many users. We can help them out when they expect file:// URIs to
// behave like it would if they were running a local HTTP server. See
// https://bugzilla.mozilla.org/show_bug.cgi?id=885597.
var fileUriAbsPath = aSource.replace(/^file:\/\//, "");
if (url.scheme == "file"
&& this._sources.has(fileUriAbsPath)) {
return this.sourcesContent[this._sources.indexOf(fileUriAbsPath)]
}
if ((!url.path || url.path == "/")
&& this._sources.has("/" + aSource)) {
return this.sourcesContent[this._sources.indexOf("/" + aSource)];
}
}
// This function is used recursively from
// IndexedSourceMapConsumer.prototype.sourceContentFor. In that case, we
// don't want to throw if we can't find the source - we just want to
// return null, so we provide a flag to exit gracefully.
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.
* - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or
* 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the
* closest element that is smaller than or greater than the one we are
* searching for, respectively, if the exact element cannot be found.
* Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'.
*
* 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.
*/
BasicSourceMapConsumer.prototype.generatedPositionFor =
function SourceMapConsumer_generatedPositionFor(aArgs) {
var source = _$util_8.getArg(aArgs, 'source');
if (this.sourceRoot != null) {
source = _$util_8.relative(this.sourceRoot, source);
}
if (!this._sources.has(source)) {
return {
line: null,
column: null,
lastColumn: null
};
}
source = this._sources.indexOf(source);
var needle = {
source: source,
originalLine: _$util_8.getArg(aArgs, 'line'),
originalColumn: _$util_8.getArg(aArgs, 'column')
};
var index = this._findMapping(
needle,
this._originalMappings,
"originalLine",
"originalColumn",
_$util_8.compareByOriginalPositions,
_$util_8.getArg(aArgs, 'bias', SourceMapConsumer.GREATEST_LOWER_BOUND)
);
if (index >= 0) {
var mapping = this._originalMappings[index];
if (mapping.source === needle.source) {
return {
line: _$util_8.getArg(mapping, 'generatedLine', null),
column: _$util_8.getArg(mapping, 'generatedColumn', null),
lastColumn: _$util_8.getArg(mapping, 'lastGeneratedColumn', null)
};
}
}
return {
line: null,
column: null,
lastColumn: null
};
};
exports.BasicSourceMapConsumer = 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_8.getArg(sourceMap, 'version');
var sections = _$util_8.getArg(sourceMap, 'sections');
if (version != this._version) {
throw new Error('Unsupported version: ' + version);
}
this._sources = new ArraySet();
this._names = new ArraySet();
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_8.getArg(s, 'offset');
var offsetLine = _$util_8.getArg(offset, 'line');
var offsetColumn = _$util_8.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_8.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_8.getArg(aArgs, 'line'),
generatedColumn: _$util_8.getArg(aArgs, 'column')
};
// Find the section containing the generated position we're trying to map
// to an original position.
var sectionIndex = _$binarySearch_5.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),
bias: aArgs.bias
});
};
/**
* Return true if we have the source content for every source in the source
* map, false otherwise.
*/
IndexedSourceMapConsumer.prototype.hasContentsOfAllSources =
function IndexedSourceMapConsumer_hasContentsOfAllSources() {
return this._sections.every(function (s) {
return s.consumer.hasContentsOfAllSources();
});
};
/**
* Returns the original source content. The only argument is the url of the
* original source file. Returns null if no original source co