|
|
/* -*- 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
*/
/** * This is a helper function for getting values from parameter/options * objects. * * @param args The object we are extracting values from * @param name The name of the property we are getting. * @param defaultValue An optional value to return if the property is missing * from the object. If this is not specified and the property is missing, an * error will be thrown. */ function getArg(aArgs, aName, aDefaultValue) { if (aName in aArgs) { return aArgs[aName]; } else if (arguments.length === 3) { return aDefaultValue; } else { throw new Error('"' + aName + '" is a required argument.'); } } exports.getArg = getArg;
var urlRegexp = /^(?:([\w+\-.]+):)?\/\/(?:(\w+:\w+)@)?([\w.-]*)(?::(\d+))?(.*)$/; var dataUrlRegexp = /^data:.+\,.+$/;
function urlParse(aUrl) { var match = aUrl.match(urlRegexp); if (!match) { return null; } return { scheme: match[1], auth: match[2], host: match[3], port: match[4], path: match[5] }; } exports.urlParse = urlParse;
function urlGenerate(aParsedUrl) { var url = ''; if (aParsedUrl.scheme) { url += aParsedUrl.scheme + ':'; } url += '//'; if (aParsedUrl.auth) { url += aParsedUrl.auth + '@'; } if (aParsedUrl.host) { url += aParsedUrl.host; } if (aParsedUrl.port) { url += ":" + aParsedUrl.port } if (aParsedUrl.path) { url += aParsedUrl.path; } return url; } exports.urlGenerate = urlGenerate;
var MAX_CACHED_INPUTS = 32;
/** * Takes some function `f(input) -> result` and returns a memoized version of * `f`. * * We keep at most `MAX_CACHED_INPUTS` memoized results of `f` alive. The * memoization is a dumb-simple, linear least-recently-used cache. */ function lruMemoize(f) { var cache = [];
return function(input) { for (var i = 0; i < cache.length; i++) { if (cache[i].input === input) { var temp = cache[0]; cache[0] = cache[i]; cache[i] = temp; return cache[0].result; } }
var result = f(input);
cache.unshift({ input, result, });
if (cache.length > MAX_CACHED_INPUTS) { cache.pop(); }
return result; }; }
/** * Normalizes a path, or the path portion of a URL: * * - Replaces consecutive slashes with one slash. * - Removes unnecessary '.' parts. * - Removes unnecessary '<dir>/..' parts. * * Based on code in the Node.js 'path' core module. * * @param aPath The path or url to normalize. */ var normalize = lruMemoize(function normalize(aPath) { var path = aPath; var url = urlParse(aPath); if (url) { if (!url.path) { return aPath; } path = url.path; } var isAbsolute = exports.isAbsolute(path); // Split the path into parts between `/` characters. This is much faster than
// using `.split(/\/+/g)`.
var parts = []; var start = 0; var i = 0; while (true) { start = i; i = path.indexOf("/", start); if (i === -1) { parts.push(path.slice(start)); break; } else { parts.push(path.slice(start, i)); while (i < path.length && path[i] === "/") { i++; } } }
for (var part, up = 0, i = parts.length - 1; i >= 0; i--) { part = parts[i]; if (part === '.') { parts.splice(i, 1); } else if (part === '..') { up++; } else if (up > 0) { if (part === '') { // The first part is blank if the path is absolute. Trying to go
// above the root is a no-op. Therefore we can remove all '..' parts
// directly after the root.
parts.splice(i + 1, up); up = 0; } else { parts.splice(i, 2); up--; } } } path = parts.join('/');
if (path === '') { path = isAbsolute ? '/' : '.'; }
if (url) { url.path = path; return urlGenerate(url); } return path; }); exports.normalize = normalize;
/** * Joins two paths/URLs. * * @param aRoot The root path or URL. * @param aPath The path or URL to be joined with the root. * * - If aPath is a URL or a data URI, aPath is returned, unless aPath is a * scheme-relative URL: Then the scheme of aRoot, if any, is prepended * first. * - Otherwise aPath is a path. If aRoot is a URL, then its path portion * is updated with the result and aRoot is returned. Otherwise the result * is returned. * - If aPath is absolute, the result is aPath. * - Otherwise the two paths are joined with a slash. * - Joining for example 'http://' and 'www.example.com' is also supported. */ function join(aRoot, aPath) { if (aRoot === "") { aRoot = "."; } if (aPath === "") { aPath = "."; } var aPathUrl = urlParse(aPath); var aRootUrl = urlParse(aRoot); if (aRootUrl) { aRoot = aRootUrl.path || '/'; }
// `join(foo, '//www.example.org')`
if (aPathUrl && !aPathUrl.scheme) { if (aRootUrl) { aPathUrl.scheme = aRootUrl.scheme; } return urlGenerate(aPathUrl); }
if (aPathUrl || aPath.match(dataUrlRegexp)) { return aPath; }
// `join('http://', 'www.example.com')`
if (aRootUrl && !aRootUrl.host && !aRootUrl.path) { aRootUrl.host = aPath; return urlGenerate(aRootUrl); }
var joined = aPath.charAt(0) === '/' ? aPath : normalize(aRoot.replace(/\/+$/, '') + '/' + aPath);
if (aRootUrl) { aRootUrl.path = joined; return urlGenerate(aRootUrl); } return joined; } exports.join = join;
exports.isAbsolute = function (aPath) { return aPath.charAt(0) === '/' || urlRegexp.test(aPath); };
/** * Make a path relative to a URL or another path. * * @param aRoot The root path or URL. * @param aPath The path or URL to be made relative to aRoot. */ function relative(aRoot, aPath) { if (aRoot === "") { aRoot = "."; }
aRoot = aRoot.replace(/\/$/, '');
// It is possible for the path to be above the root. In this case, simply
// checking whether the root is a prefix of the path won't work. Instead, we
// need to remove components from the root one by one, until either we find
// a prefix that fits, or we run out of components to remove.
var level = 0; while (aPath.indexOf(aRoot + '/') !== 0) { var index = aRoot.lastIndexOf("/"); if (index < 0) { return aPath; }
// If the only part of the root that is left is the scheme (i.e. http://,
// file:///, etc.), one or more slashes (/), or simply nothing at all, we
// have exhausted all components, so the path is not relative to the root.
aRoot = aRoot.slice(0, index); if (aRoot.match(/^([^\/]+:\/)?\/*$/)) { return aPath; }
++level; }
// Make sure we add a "../" for each component we removed from the root.
return Array(level + 1).join("../") + aPath.substr(aRoot.length + 1); } exports.relative = relative;
var supportsNullProto = (function () { var obj = Object.create(null); return !('__proto__' in obj); }());
function identity (s) { return s; }
/** * Because behavior goes wacky when you set `__proto__` on objects, we * have to prefix all the strings in our set with an arbitrary character. * * See https://github.com/mozilla/source-map/pull/31 and
* https://github.com/mozilla/source-map/issues/30
* * @param String aStr */ function toSetString(aStr) { if (isProtoString(aStr)) { return '$' + aStr; }
return aStr; } exports.toSetString = supportsNullProto ? identity : toSetString;
function fromSetString(aStr) { if (isProtoString(aStr)) { return aStr.slice(1); }
return aStr; } exports.fromSetString = supportsNullProto ? identity : fromSetString;
function isProtoString(s) { if (!s) { return false; }
var length = s.length;
if (length < 9 /* "__proto__".length */) { return false; }
if (s.charCodeAt(length - 1) !== 95 /* '_' */ || s.charCodeAt(length - 2) !== 95 /* '_' */ || s.charCodeAt(length - 3) !== 111 /* 'o' */ || s.charCodeAt(length - 4) !== 116 /* 't' */ || s.charCodeAt(length - 5) !== 111 /* 'o' */ || s.charCodeAt(length - 6) !== 114 /* 'r' */ || s.charCodeAt(length - 7) !== 112 /* 'p' */ || s.charCodeAt(length - 8) !== 95 /* '_' */ || s.charCodeAt(length - 9) !== 95 /* '_' */) { return false; }
for (var i = length - 10; i >= 0; i--) { if (s.charCodeAt(i) !== 36 /* '$' */) { return false; } }
return true; }
/** * Comparator between two mappings where the original positions are compared. * * Optionally pass in `true` as `onlyCompareGenerated` to consider two * mappings with the same original source/line/column, but different generated * line and column the same. Useful when searching for a mapping with a * stubbed out mapping. */ function compareByOriginalPositions(mappingA, mappingB, onlyCompareOriginal) { var cmp = strcmp(mappingA.source, mappingB.source); if (cmp !== 0) { return cmp; }
cmp = mappingA.originalLine - mappingB.originalLine; if (cmp !== 0) { return cmp; }
cmp = mappingA.originalColumn - mappingB.originalColumn; if (cmp !== 0 || onlyCompareOriginal) { return cmp; }
cmp = mappingA.generatedColumn - mappingB.generatedColumn; if (cmp !== 0) { return cmp; }
cmp = mappingA.generatedLine - mappingB.generatedLine; if (cmp !== 0) { return cmp; }
return strcmp(mappingA.name, mappingB.name); } exports.compareByOriginalPositions = compareByOriginalPositions;
function compareByOriginalPositionsNoSource(mappingA, mappingB, onlyCompareOriginal) { var cmp
cmp = mappingA.originalLine - mappingB.originalLine; if (cmp !== 0) { return cmp; }
cmp = mappingA.originalColumn - mappingB.originalColumn; if (cmp !== 0 || onlyCompareOriginal) { return cmp; }
cmp = mappingA.generatedColumn - mappingB.generatedColumn; if (cmp !== 0) { return cmp; }
cmp = mappingA.generatedLine - mappingB.generatedLine; if (cmp !== 0) { return cmp; }
return strcmp(mappingA.name, mappingB.name); } exports.compareByOriginalPositionsNoSource = compareByOriginalPositionsNoSource;
/** * Comparator between two mappings with deflated source and name indices where * the generated positions are compared. * * Optionally pass in `true` as `onlyCompareGenerated` to consider two * mappings with the same generated line and column, but different * source/name/original line and column the same. Useful when searching for a * mapping with a stubbed out mapping. */ function compareByGeneratedPositionsDeflated(mappingA, mappingB, onlyCompareGenerated) { var cmp = mappingA.generatedLine - mappingB.generatedLine; if (cmp !== 0) { return cmp; }
cmp = mappingA.generatedColumn - mappingB.generatedColumn; if (cmp !== 0 || onlyCompareGenerated) { return cmp; }
cmp = strcmp(mappingA.source, mappingB.source); if (cmp !== 0) { return cmp; }
cmp = mappingA.originalLine - mappingB.originalLine; if (cmp !== 0) { return cmp; }
cmp = mappingA.originalColumn - mappingB.originalColumn; if (cmp !== 0) { return cmp; }
return strcmp(mappingA.name, mappingB.name); } exports.compareByGeneratedPositionsDeflated = compareByGeneratedPositionsDeflated;
function compareByGeneratedPositionsDeflatedNoLine(mappingA, mappingB, onlyCompareGenerated) { var cmp = mappingA.generatedColumn - mappingB.generatedColumn; if (cmp !== 0 || onlyCompareGenerated) { return cmp; }
cmp = strcmp(mappingA.source, mappingB.source); if (cmp !== 0) { return cmp; }
cmp = mappingA.originalLine - mappingB.originalLine; if (cmp !== 0) { return cmp; }
cmp = mappingA.originalColumn - mappingB.originalColumn; if (cmp !== 0) { return cmp; }
return strcmp(mappingA.name, mappingB.name); } exports.compareByGeneratedPositionsDeflatedNoLine = compareByGeneratedPositionsDeflatedNoLine;
function strcmp(aStr1, aStr2) { if (aStr1 === aStr2) { return 0; }
if (aStr1 === null) { return 1; // aStr2 !== null
}
if (aStr2 === null) { return -1; // aStr1 !== null
}
if (aStr1 > aStr2) { return 1; }
return -1; }
/** * Comparator between two mappings with inflated source and name strings where * the generated positions are compared. */ function compareByGeneratedPositionsInflated(mappingA, mappingB) { var cmp = mappingA.generatedLine - mappingB.generatedLine; if (cmp !== 0) { return cmp; }
cmp = mappingA.generatedColumn - mappingB.generatedColumn; if (cmp !== 0) { return cmp; }
cmp = strcmp(mappingA.source, mappingB.source); if (cmp !== 0) { return cmp; }
cmp = mappingA.originalLine - mappingB.originalLine; if (cmp !== 0) { return cmp; }
cmp = mappingA.originalColumn - mappingB.originalColumn; if (cmp !== 0) { return cmp; }
return strcmp(mappingA.name, mappingB.name); } exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflated;
/** * Strip any JSON XSSI avoidance prefix from the string (as documented * in the source maps specification), and then parse the string as * JSON. */ function parseSourceMapInput(str) { return JSON.parse(str.replace(/^\)]}'[^\n]*\n/, '')); } exports.parseSourceMapInput = parseSourceMapInput;
/** * Compute the URL of a source given the the source root, the source's * URL, and the source map's URL. */ function computeSourceURL(sourceRoot, sourceURL, sourceMapURL) { sourceURL = sourceURL || '';
if (sourceRoot) { // This follows what Chrome does.
if (sourceRoot[sourceRoot.length - 1] !== '/' && sourceURL[0] !== '/') { sourceRoot += '/'; } // The spec says:
// Line 4: An optional source root, useful for relocating source
// files on a server or removing repeated values in the
// “sources” entry. This value is prepended to the individual
// entries in the “source” field.
sourceURL = sourceRoot + sourceURL; }
// Historically, SourceMapConsumer did not take the sourceMapURL as
// a parameter. This mode is still somewhat supported, which is why
// this code block is conditional. However, it's preferable to pass
// the source map URL to SourceMapConsumer, so that this function
// can implement the source URL resolution algorithm as outlined in
// the spec. This block is basically the equivalent of:
// new URL(sourceURL, sourceMapURL).toString()
// ... except it avoids using URL, which wasn't available in the
// older releases of node still supported by this library.
//
// The spec says:
// If the sources are not absolute URLs after prepending of the
// “sourceRoot”, the sources are resolved relative to the
// SourceMap (like resolving script src in a html document).
if (sourceMapURL) { var parsed = urlParse(sourceMapURL); if (!parsed) { throw new Error("sourceMapURL could not be parsed"); } if (parsed.path) { // Strip the last path component, but keep the "/".
var index = parsed.path.lastIndexOf('/'); if (index >= 0) { parsed.path = parsed.path.substring(0, index + 1); } } sourceURL = join(urlGenerate(parsed), sourceURL); }
return normalize(sourceURL); } exports.computeSourceURL = computeSourceURL;
|