util.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594
  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. * This is a helper function for getting values from parameter/options
  9. * objects.
  10. *
  11. * @param args The object we are extracting values from
  12. * @param name The name of the property we are getting.
  13. * @param defaultValue An optional value to return if the property is missing
  14. * from the object. If this is not specified and the property is missing, an
  15. * error will be thrown.
  16. */
  17. function getArg(aArgs, aName, aDefaultValue) {
  18. if (aName in aArgs) {
  19. return aArgs[aName];
  20. } else if (arguments.length === 3) {
  21. return aDefaultValue;
  22. } else {
  23. throw new Error('"' + aName + '" is a required argument.');
  24. }
  25. }
  26. exports.getArg = getArg;
  27. var urlRegexp = /^(?:([\w+\-.]+):)?\/\/(?:(\w+:\w+)@)?([\w.-]*)(?::(\d+))?(.*)$/;
  28. var dataUrlRegexp = /^data:.+\,.+$/;
  29. function urlParse(aUrl) {
  30. var match = aUrl.match(urlRegexp);
  31. if (!match) {
  32. return null;
  33. }
  34. return {
  35. scheme: match[1],
  36. auth: match[2],
  37. host: match[3],
  38. port: match[4],
  39. path: match[5]
  40. };
  41. }
  42. exports.urlParse = urlParse;
  43. function urlGenerate(aParsedUrl) {
  44. var url = '';
  45. if (aParsedUrl.scheme) {
  46. url += aParsedUrl.scheme + ':';
  47. }
  48. url += '//';
  49. if (aParsedUrl.auth) {
  50. url += aParsedUrl.auth + '@';
  51. }
  52. if (aParsedUrl.host) {
  53. url += aParsedUrl.host;
  54. }
  55. if (aParsedUrl.port) {
  56. url += ":" + aParsedUrl.port
  57. }
  58. if (aParsedUrl.path) {
  59. url += aParsedUrl.path;
  60. }
  61. return url;
  62. }
  63. exports.urlGenerate = urlGenerate;
  64. var MAX_CACHED_INPUTS = 32;
  65. /**
  66. * Takes some function `f(input) -> result` and returns a memoized version of
  67. * `f`.
  68. *
  69. * We keep at most `MAX_CACHED_INPUTS` memoized results of `f` alive. The
  70. * memoization is a dumb-simple, linear least-recently-used cache.
  71. */
  72. function lruMemoize(f) {
  73. var cache = [];
  74. return function(input) {
  75. for (var i = 0; i < cache.length; i++) {
  76. if (cache[i].input === input) {
  77. var temp = cache[0];
  78. cache[0] = cache[i];
  79. cache[i] = temp;
  80. return cache[0].result;
  81. }
  82. }
  83. var result = f(input);
  84. cache.unshift({
  85. input,
  86. result,
  87. });
  88. if (cache.length > MAX_CACHED_INPUTS) {
  89. cache.pop();
  90. }
  91. return result;
  92. };
  93. }
  94. /**
  95. * Normalizes a path, or the path portion of a URL:
  96. *
  97. * - Replaces consecutive slashes with one slash.
  98. * - Removes unnecessary '.' parts.
  99. * - Removes unnecessary '<dir>/..' parts.
  100. *
  101. * Based on code in the Node.js 'path' core module.
  102. *
  103. * @param aPath The path or url to normalize.
  104. */
  105. var normalize = lruMemoize(function normalize(aPath) {
  106. var path = aPath;
  107. var url = urlParse(aPath);
  108. if (url) {
  109. if (!url.path) {
  110. return aPath;
  111. }
  112. path = url.path;
  113. }
  114. var isAbsolute = exports.isAbsolute(path);
  115. // Split the path into parts between `/` characters. This is much faster than
  116. // using `.split(/\/+/g)`.
  117. var parts = [];
  118. var start = 0;
  119. var i = 0;
  120. while (true) {
  121. start = i;
  122. i = path.indexOf("/", start);
  123. if (i === -1) {
  124. parts.push(path.slice(start));
  125. break;
  126. } else {
  127. parts.push(path.slice(start, i));
  128. while (i < path.length && path[i] === "/") {
  129. i++;
  130. }
  131. }
  132. }
  133. for (var part, up = 0, i = parts.length - 1; i >= 0; i--) {
  134. part = parts[i];
  135. if (part === '.') {
  136. parts.splice(i, 1);
  137. } else if (part === '..') {
  138. up++;
  139. } else if (up > 0) {
  140. if (part === '') {
  141. // The first part is blank if the path is absolute. Trying to go
  142. // above the root is a no-op. Therefore we can remove all '..' parts
  143. // directly after the root.
  144. parts.splice(i + 1, up);
  145. up = 0;
  146. } else {
  147. parts.splice(i, 2);
  148. up--;
  149. }
  150. }
  151. }
  152. path = parts.join('/');
  153. if (path === '') {
  154. path = isAbsolute ? '/' : '.';
  155. }
  156. if (url) {
  157. url.path = path;
  158. return urlGenerate(url);
  159. }
  160. return path;
  161. });
  162. exports.normalize = normalize;
  163. /**
  164. * Joins two paths/URLs.
  165. *
  166. * @param aRoot The root path or URL.
  167. * @param aPath The path or URL to be joined with the root.
  168. *
  169. * - If aPath is a URL or a data URI, aPath is returned, unless aPath is a
  170. * scheme-relative URL: Then the scheme of aRoot, if any, is prepended
  171. * first.
  172. * - Otherwise aPath is a path. If aRoot is a URL, then its path portion
  173. * is updated with the result and aRoot is returned. Otherwise the result
  174. * is returned.
  175. * - If aPath is absolute, the result is aPath.
  176. * - Otherwise the two paths are joined with a slash.
  177. * - Joining for example 'http://' and 'www.example.com' is also supported.
  178. */
  179. function join(aRoot, aPath) {
  180. if (aRoot === "") {
  181. aRoot = ".";
  182. }
  183. if (aPath === "") {
  184. aPath = ".";
  185. }
  186. var aPathUrl = urlParse(aPath);
  187. var aRootUrl = urlParse(aRoot);
  188. if (aRootUrl) {
  189. aRoot = aRootUrl.path || '/';
  190. }
  191. // `join(foo, '//www.example.org')`
  192. if (aPathUrl && !aPathUrl.scheme) {
  193. if (aRootUrl) {
  194. aPathUrl.scheme = aRootUrl.scheme;
  195. }
  196. return urlGenerate(aPathUrl);
  197. }
  198. if (aPathUrl || aPath.match(dataUrlRegexp)) {
  199. return aPath;
  200. }
  201. // `join('http://', 'www.example.com')`
  202. if (aRootUrl && !aRootUrl.host && !aRootUrl.path) {
  203. aRootUrl.host = aPath;
  204. return urlGenerate(aRootUrl);
  205. }
  206. var joined = aPath.charAt(0) === '/'
  207. ? aPath
  208. : normalize(aRoot.replace(/\/+$/, '') + '/' + aPath);
  209. if (aRootUrl) {
  210. aRootUrl.path = joined;
  211. return urlGenerate(aRootUrl);
  212. }
  213. return joined;
  214. }
  215. exports.join = join;
  216. exports.isAbsolute = function (aPath) {
  217. return aPath.charAt(0) === '/' || urlRegexp.test(aPath);
  218. };
  219. /**
  220. * Make a path relative to a URL or another path.
  221. *
  222. * @param aRoot The root path or URL.
  223. * @param aPath The path or URL to be made relative to aRoot.
  224. */
  225. function relative(aRoot, aPath) {
  226. if (aRoot === "") {
  227. aRoot = ".";
  228. }
  229. aRoot = aRoot.replace(/\/$/, '');
  230. // It is possible for the path to be above the root. In this case, simply
  231. // checking whether the root is a prefix of the path won't work. Instead, we
  232. // need to remove components from the root one by one, until either we find
  233. // a prefix that fits, or we run out of components to remove.
  234. var level = 0;
  235. while (aPath.indexOf(aRoot + '/') !== 0) {
  236. var index = aRoot.lastIndexOf("/");
  237. if (index < 0) {
  238. return aPath;
  239. }
  240. // If the only part of the root that is left is the scheme (i.e. http://,
  241. // file:///, etc.), one or more slashes (/), or simply nothing at all, we
  242. // have exhausted all components, so the path is not relative to the root.
  243. aRoot = aRoot.slice(0, index);
  244. if (aRoot.match(/^([^\/]+:\/)?\/*$/)) {
  245. return aPath;
  246. }
  247. ++level;
  248. }
  249. // Make sure we add a "../" for each component we removed from the root.
  250. return Array(level + 1).join("../") + aPath.substr(aRoot.length + 1);
  251. }
  252. exports.relative = relative;
  253. var supportsNullProto = (function () {
  254. var obj = Object.create(null);
  255. return !('__proto__' in obj);
  256. }());
  257. function identity (s) {
  258. return s;
  259. }
  260. /**
  261. * Because behavior goes wacky when you set `__proto__` on objects, we
  262. * have to prefix all the strings in our set with an arbitrary character.
  263. *
  264. * See https://github.com/mozilla/source-map/pull/31 and
  265. * https://github.com/mozilla/source-map/issues/30
  266. *
  267. * @param String aStr
  268. */
  269. function toSetString(aStr) {
  270. if (isProtoString(aStr)) {
  271. return '$' + aStr;
  272. }
  273. return aStr;
  274. }
  275. exports.toSetString = supportsNullProto ? identity : toSetString;
  276. function fromSetString(aStr) {
  277. if (isProtoString(aStr)) {
  278. return aStr.slice(1);
  279. }
  280. return aStr;
  281. }
  282. exports.fromSetString = supportsNullProto ? identity : fromSetString;
  283. function isProtoString(s) {
  284. if (!s) {
  285. return false;
  286. }
  287. var length = s.length;
  288. if (length < 9 /* "__proto__".length */) {
  289. return false;
  290. }
  291. if (s.charCodeAt(length - 1) !== 95 /* '_' */ ||
  292. s.charCodeAt(length - 2) !== 95 /* '_' */ ||
  293. s.charCodeAt(length - 3) !== 111 /* 'o' */ ||
  294. s.charCodeAt(length - 4) !== 116 /* 't' */ ||
  295. s.charCodeAt(length - 5) !== 111 /* 'o' */ ||
  296. s.charCodeAt(length - 6) !== 114 /* 'r' */ ||
  297. s.charCodeAt(length - 7) !== 112 /* 'p' */ ||
  298. s.charCodeAt(length - 8) !== 95 /* '_' */ ||
  299. s.charCodeAt(length - 9) !== 95 /* '_' */) {
  300. return false;
  301. }
  302. for (var i = length - 10; i >= 0; i--) {
  303. if (s.charCodeAt(i) !== 36 /* '$' */) {
  304. return false;
  305. }
  306. }
  307. return true;
  308. }
  309. /**
  310. * Comparator between two mappings where the original positions are compared.
  311. *
  312. * Optionally pass in `true` as `onlyCompareGenerated` to consider two
  313. * mappings with the same original source/line/column, but different generated
  314. * line and column the same. Useful when searching for a mapping with a
  315. * stubbed out mapping.
  316. */
  317. function compareByOriginalPositions(mappingA, mappingB, onlyCompareOriginal) {
  318. var cmp = strcmp(mappingA.source, mappingB.source);
  319. if (cmp !== 0) {
  320. return cmp;
  321. }
  322. cmp = mappingA.originalLine - mappingB.originalLine;
  323. if (cmp !== 0) {
  324. return cmp;
  325. }
  326. cmp = mappingA.originalColumn - mappingB.originalColumn;
  327. if (cmp !== 0 || onlyCompareOriginal) {
  328. return cmp;
  329. }
  330. cmp = mappingA.generatedColumn - mappingB.generatedColumn;
  331. if (cmp !== 0) {
  332. return cmp;
  333. }
  334. cmp = mappingA.generatedLine - mappingB.generatedLine;
  335. if (cmp !== 0) {
  336. return cmp;
  337. }
  338. return strcmp(mappingA.name, mappingB.name);
  339. }
  340. exports.compareByOriginalPositions = compareByOriginalPositions;
  341. function compareByOriginalPositionsNoSource(mappingA, mappingB, onlyCompareOriginal) {
  342. var cmp
  343. cmp = mappingA.originalLine - mappingB.originalLine;
  344. if (cmp !== 0) {
  345. return cmp;
  346. }
  347. cmp = mappingA.originalColumn - mappingB.originalColumn;
  348. if (cmp !== 0 || onlyCompareOriginal) {
  349. return cmp;
  350. }
  351. cmp = mappingA.generatedColumn - mappingB.generatedColumn;
  352. if (cmp !== 0) {
  353. return cmp;
  354. }
  355. cmp = mappingA.generatedLine - mappingB.generatedLine;
  356. if (cmp !== 0) {
  357. return cmp;
  358. }
  359. return strcmp(mappingA.name, mappingB.name);
  360. }
  361. exports.compareByOriginalPositionsNoSource = compareByOriginalPositionsNoSource;
  362. /**
  363. * Comparator between two mappings with deflated source and name indices where
  364. * the generated positions are compared.
  365. *
  366. * Optionally pass in `true` as `onlyCompareGenerated` to consider two
  367. * mappings with the same generated line and column, but different
  368. * source/name/original line and column the same. Useful when searching for a
  369. * mapping with a stubbed out mapping.
  370. */
  371. function compareByGeneratedPositionsDeflated(mappingA, mappingB, onlyCompareGenerated) {
  372. var cmp = mappingA.generatedLine - mappingB.generatedLine;
  373. if (cmp !== 0) {
  374. return cmp;
  375. }
  376. cmp = mappingA.generatedColumn - mappingB.generatedColumn;
  377. if (cmp !== 0 || onlyCompareGenerated) {
  378. return cmp;
  379. }
  380. cmp = strcmp(mappingA.source, mappingB.source);
  381. if (cmp !== 0) {
  382. return cmp;
  383. }
  384. cmp = mappingA.originalLine - mappingB.originalLine;
  385. if (cmp !== 0) {
  386. return cmp;
  387. }
  388. cmp = mappingA.originalColumn - mappingB.originalColumn;
  389. if (cmp !== 0) {
  390. return cmp;
  391. }
  392. return strcmp(mappingA.name, mappingB.name);
  393. }
  394. exports.compareByGeneratedPositionsDeflated = compareByGeneratedPositionsDeflated;
  395. function compareByGeneratedPositionsDeflatedNoLine(mappingA, mappingB, onlyCompareGenerated) {
  396. var cmp = mappingA.generatedColumn - mappingB.generatedColumn;
  397. if (cmp !== 0 || onlyCompareGenerated) {
  398. return cmp;
  399. }
  400. cmp = strcmp(mappingA.source, mappingB.source);
  401. if (cmp !== 0) {
  402. return cmp;
  403. }
  404. cmp = mappingA.originalLine - mappingB.originalLine;
  405. if (cmp !== 0) {
  406. return cmp;
  407. }
  408. cmp = mappingA.originalColumn - mappingB.originalColumn;
  409. if (cmp !== 0) {
  410. return cmp;
  411. }
  412. return strcmp(mappingA.name, mappingB.name);
  413. }
  414. exports.compareByGeneratedPositionsDeflatedNoLine = compareByGeneratedPositionsDeflatedNoLine;
  415. function strcmp(aStr1, aStr2) {
  416. if (aStr1 === aStr2) {
  417. return 0;
  418. }
  419. if (aStr1 === null) {
  420. return 1; // aStr2 !== null
  421. }
  422. if (aStr2 === null) {
  423. return -1; // aStr1 !== null
  424. }
  425. if (aStr1 > aStr2) {
  426. return 1;
  427. }
  428. return -1;
  429. }
  430. /**
  431. * Comparator between two mappings with inflated source and name strings where
  432. * the generated positions are compared.
  433. */
  434. function compareByGeneratedPositionsInflated(mappingA, mappingB) {
  435. var cmp = mappingA.generatedLine - mappingB.generatedLine;
  436. if (cmp !== 0) {
  437. return cmp;
  438. }
  439. cmp = mappingA.generatedColumn - mappingB.generatedColumn;
  440. if (cmp !== 0) {
  441. return cmp;
  442. }
  443. cmp = strcmp(mappingA.source, mappingB.source);
  444. if (cmp !== 0) {
  445. return cmp;
  446. }
  447. cmp = mappingA.originalLine - mappingB.originalLine;
  448. if (cmp !== 0) {
  449. return cmp;
  450. }
  451. cmp = mappingA.originalColumn - mappingB.originalColumn;
  452. if (cmp !== 0) {
  453. return cmp;
  454. }
  455. return strcmp(mappingA.name, mappingB.name);
  456. }
  457. exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflated;
  458. /**
  459. * Strip any JSON XSSI avoidance prefix from the string (as documented
  460. * in the source maps specification), and then parse the string as
  461. * JSON.
  462. */
  463. function parseSourceMapInput(str) {
  464. return JSON.parse(str.replace(/^\)]}'[^\n]*\n/, ''));
  465. }
  466. exports.parseSourceMapInput = parseSourceMapInput;
  467. /**
  468. * Compute the URL of a source given the the source root, the source's
  469. * URL, and the source map's URL.
  470. */
  471. function computeSourceURL(sourceRoot, sourceURL, sourceMapURL) {
  472. sourceURL = sourceURL || '';
  473. if (sourceRoot) {
  474. // This follows what Chrome does.
  475. if (sourceRoot[sourceRoot.length - 1] !== '/' && sourceURL[0] !== '/') {
  476. sourceRoot += '/';
  477. }
  478. // The spec says:
  479. // Line 4: An optional source root, useful for relocating source
  480. // files on a server or removing repeated values in the
  481. // “sources” entry. This value is prepended to the individual
  482. // entries in the “source” field.
  483. sourceURL = sourceRoot + sourceURL;
  484. }
  485. // Historically, SourceMapConsumer did not take the sourceMapURL as
  486. // a parameter. This mode is still somewhat supported, which is why
  487. // this code block is conditional. However, it's preferable to pass
  488. // the source map URL to SourceMapConsumer, so that this function
  489. // can implement the source URL resolution algorithm as outlined in
  490. // the spec. This block is basically the equivalent of:
  491. // new URL(sourceURL, sourceMapURL).toString()
  492. // ... except it avoids using URL, which wasn't available in the
  493. // older releases of node still supported by this library.
  494. //
  495. // The spec says:
  496. // If the sources are not absolute URLs after prepending of the
  497. // “sourceRoot”, the sources are resolved relative to the
  498. // SourceMap (like resolving script src in a html document).
  499. if (sourceMapURL) {
  500. var parsed = urlParse(sourceMapURL);
  501. if (!parsed) {
  502. throw new Error("sourceMapURL could not be parsed");
  503. }
  504. if (parsed.path) {
  505. // Strip the last path component, but keep the "/".
  506. var index = parsed.path.lastIndexOf('/');
  507. if (index >= 0) {
  508. parsed.path = parsed.path.substring(0, index + 1);
  509. }
  510. }
  511. sourceURL = join(urlGenerate(parsed), sourceURL);
  512. }
  513. return normalize(sourceURL);
  514. }
  515. exports.computeSourceURL = computeSourceURL;