util.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  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. /**
  65. * Normalizes a path, or the path portion of a URL:
  66. *
  67. * - Replaces consecutive slashes with one slash.
  68. * - Removes unnecessary '.' parts.
  69. * - Removes unnecessary '<dir>/..' parts.
  70. *
  71. * Based on code in the Node.js 'path' core module.
  72. *
  73. * @param aPath The path or url to normalize.
  74. */
  75. function normalize(aPath) {
  76. var path = aPath;
  77. var url = urlParse(aPath);
  78. if (url) {
  79. if (!url.path) {
  80. return aPath;
  81. }
  82. path = url.path;
  83. }
  84. var isAbsolute = exports.isAbsolute(path);
  85. var parts = path.split(/\/+/);
  86. for (var part, up = 0, i = parts.length - 1; i >= 0; i--) {
  87. part = parts[i];
  88. if (part === '.') {
  89. parts.splice(i, 1);
  90. } else if (part === '..') {
  91. up++;
  92. } else if (up > 0) {
  93. if (part === '') {
  94. // The first part is blank if the path is absolute. Trying to go
  95. // above the root is a no-op. Therefore we can remove all '..' parts
  96. // directly after the root.
  97. parts.splice(i + 1, up);
  98. up = 0;
  99. } else {
  100. parts.splice(i, 2);
  101. up--;
  102. }
  103. }
  104. }
  105. path = parts.join('/');
  106. if (path === '') {
  107. path = isAbsolute ? '/' : '.';
  108. }
  109. if (url) {
  110. url.path = path;
  111. return urlGenerate(url);
  112. }
  113. return path;
  114. }
  115. exports.normalize = normalize;
  116. /**
  117. * Joins two paths/URLs.
  118. *
  119. * @param aRoot The root path or URL.
  120. * @param aPath The path or URL to be joined with the root.
  121. *
  122. * - If aPath is a URL or a data URI, aPath is returned, unless aPath is a
  123. * scheme-relative URL: Then the scheme of aRoot, if any, is prepended
  124. * first.
  125. * - Otherwise aPath is a path. If aRoot is a URL, then its path portion
  126. * is updated with the result and aRoot is returned. Otherwise the result
  127. * is returned.
  128. * - If aPath is absolute, the result is aPath.
  129. * - Otherwise the two paths are joined with a slash.
  130. * - Joining for example 'http://' and 'www.example.com' is also supported.
  131. */
  132. function join(aRoot, aPath) {
  133. if (aRoot === "") {
  134. aRoot = ".";
  135. }
  136. if (aPath === "") {
  137. aPath = ".";
  138. }
  139. var aPathUrl = urlParse(aPath);
  140. var aRootUrl = urlParse(aRoot);
  141. if (aRootUrl) {
  142. aRoot = aRootUrl.path || '/';
  143. }
  144. // `join(foo, '//www.example.org')`
  145. if (aPathUrl && !aPathUrl.scheme) {
  146. if (aRootUrl) {
  147. aPathUrl.scheme = aRootUrl.scheme;
  148. }
  149. return urlGenerate(aPathUrl);
  150. }
  151. if (aPathUrl || aPath.match(dataUrlRegexp)) {
  152. return aPath;
  153. }
  154. // `join('http://', 'www.example.com')`
  155. if (aRootUrl && !aRootUrl.host && !aRootUrl.path) {
  156. aRootUrl.host = aPath;
  157. return urlGenerate(aRootUrl);
  158. }
  159. var joined = aPath.charAt(0) === '/'
  160. ? aPath
  161. : normalize(aRoot.replace(/\/+$/, '') + '/' + aPath);
  162. if (aRootUrl) {
  163. aRootUrl.path = joined;
  164. return urlGenerate(aRootUrl);
  165. }
  166. return joined;
  167. }
  168. exports.join = join;
  169. exports.isAbsolute = function (aPath) {
  170. return aPath.charAt(0) === '/' || urlRegexp.test(aPath);
  171. };
  172. /**
  173. * Make a path relative to a URL or another path.
  174. *
  175. * @param aRoot The root path or URL.
  176. * @param aPath The path or URL to be made relative to aRoot.
  177. */
  178. function relative(aRoot, aPath) {
  179. if (aRoot === "") {
  180. aRoot = ".";
  181. }
  182. aRoot = aRoot.replace(/\/$/, '');
  183. // It is possible for the path to be above the root. In this case, simply
  184. // checking whether the root is a prefix of the path won't work. Instead, we
  185. // need to remove components from the root one by one, until either we find
  186. // a prefix that fits, or we run out of components to remove.
  187. var level = 0;
  188. while (aPath.indexOf(aRoot + '/') !== 0) {
  189. var index = aRoot.lastIndexOf("/");
  190. if (index < 0) {
  191. return aPath;
  192. }
  193. // If the only part of the root that is left is the scheme (i.e. http://,
  194. // file:///, etc.), one or more slashes (/), or simply nothing at all, we
  195. // have exhausted all components, so the path is not relative to the root.
  196. aRoot = aRoot.slice(0, index);
  197. if (aRoot.match(/^([^\/]+:\/)?\/*$/)) {
  198. return aPath;
  199. }
  200. ++level;
  201. }
  202. // Make sure we add a "../" for each component we removed from the root.
  203. return Array(level + 1).join("../") + aPath.substr(aRoot.length + 1);
  204. }
  205. exports.relative = relative;
  206. var supportsNullProto = (function () {
  207. var obj = Object.create(null);
  208. return !('__proto__' in obj);
  209. }());
  210. function identity (s) {
  211. return s;
  212. }
  213. /**
  214. * Because behavior goes wacky when you set `__proto__` on objects, we
  215. * have to prefix all the strings in our set with an arbitrary character.
  216. *
  217. * See https://github.com/mozilla/source-map/pull/31 and
  218. * https://github.com/mozilla/source-map/issues/30
  219. *
  220. * @param String aStr
  221. */
  222. function toSetString(aStr) {
  223. if (isProtoString(aStr)) {
  224. return '$' + aStr;
  225. }
  226. return aStr;
  227. }
  228. exports.toSetString = supportsNullProto ? identity : toSetString;
  229. function fromSetString(aStr) {
  230. if (isProtoString(aStr)) {
  231. return aStr.slice(1);
  232. }
  233. return aStr;
  234. }
  235. exports.fromSetString = supportsNullProto ? identity : fromSetString;
  236. function isProtoString(s) {
  237. if (!s) {
  238. return false;
  239. }
  240. var length = s.length;
  241. if (length < 9 /* "__proto__".length */) {
  242. return false;
  243. }
  244. if (s.charCodeAt(length - 1) !== 95 /* '_' */ ||
  245. s.charCodeAt(length - 2) !== 95 /* '_' */ ||
  246. s.charCodeAt(length - 3) !== 111 /* 'o' */ ||
  247. s.charCodeAt(length - 4) !== 116 /* 't' */ ||
  248. s.charCodeAt(length - 5) !== 111 /* 'o' */ ||
  249. s.charCodeAt(length - 6) !== 114 /* 'r' */ ||
  250. s.charCodeAt(length - 7) !== 112 /* 'p' */ ||
  251. s.charCodeAt(length - 8) !== 95 /* '_' */ ||
  252. s.charCodeAt(length - 9) !== 95 /* '_' */) {
  253. return false;
  254. }
  255. for (var i = length - 10; i >= 0; i--) {
  256. if (s.charCodeAt(i) !== 36 /* '$' */) {
  257. return false;
  258. }
  259. }
  260. return true;
  261. }
  262. /**
  263. * Comparator between two mappings where the original positions are compared.
  264. *
  265. * Optionally pass in `true` as `onlyCompareGenerated` to consider two
  266. * mappings with the same original source/line/column, but different generated
  267. * line and column the same. Useful when searching for a mapping with a
  268. * stubbed out mapping.
  269. */
  270. function compareByOriginalPositions(mappingA, mappingB, onlyCompareOriginal) {
  271. var cmp = strcmp(mappingA.source, mappingB.source);
  272. if (cmp !== 0) {
  273. return cmp;
  274. }
  275. cmp = mappingA.originalLine - mappingB.originalLine;
  276. if (cmp !== 0) {
  277. return cmp;
  278. }
  279. cmp = mappingA.originalColumn - mappingB.originalColumn;
  280. if (cmp !== 0 || onlyCompareOriginal) {
  281. return cmp;
  282. }
  283. cmp = mappingA.generatedColumn - mappingB.generatedColumn;
  284. if (cmp !== 0) {
  285. return cmp;
  286. }
  287. cmp = mappingA.generatedLine - mappingB.generatedLine;
  288. if (cmp !== 0) {
  289. return cmp;
  290. }
  291. return strcmp(mappingA.name, mappingB.name);
  292. }
  293. exports.compareByOriginalPositions = compareByOriginalPositions;
  294. /**
  295. * Comparator between two mappings with deflated source and name indices where
  296. * the generated positions are compared.
  297. *
  298. * Optionally pass in `true` as `onlyCompareGenerated` to consider two
  299. * mappings with the same generated line and column, but different
  300. * source/name/original line and column the same. Useful when searching for a
  301. * mapping with a stubbed out mapping.
  302. */
  303. function compareByGeneratedPositionsDeflated(mappingA, mappingB, onlyCompareGenerated) {
  304. var cmp = mappingA.generatedLine - mappingB.generatedLine;
  305. if (cmp !== 0) {
  306. return cmp;
  307. }
  308. cmp = mappingA.generatedColumn - mappingB.generatedColumn;
  309. if (cmp !== 0 || onlyCompareGenerated) {
  310. return cmp;
  311. }
  312. cmp = strcmp(mappingA.source, mappingB.source);
  313. if (cmp !== 0) {
  314. return cmp;
  315. }
  316. cmp = mappingA.originalLine - mappingB.originalLine;
  317. if (cmp !== 0) {
  318. return cmp;
  319. }
  320. cmp = mappingA.originalColumn - mappingB.originalColumn;
  321. if (cmp !== 0) {
  322. return cmp;
  323. }
  324. return strcmp(mappingA.name, mappingB.name);
  325. }
  326. exports.compareByGeneratedPositionsDeflated = compareByGeneratedPositionsDeflated;
  327. function strcmp(aStr1, aStr2) {
  328. if (aStr1 === aStr2) {
  329. return 0;
  330. }
  331. if (aStr1 === null) {
  332. return 1; // aStr2 !== null
  333. }
  334. if (aStr2 === null) {
  335. return -1; // aStr1 !== null
  336. }
  337. if (aStr1 > aStr2) {
  338. return 1;
  339. }
  340. return -1;
  341. }
  342. /**
  343. * Comparator between two mappings with inflated source and name strings where
  344. * the generated positions are compared.
  345. */
  346. function compareByGeneratedPositionsInflated(mappingA, mappingB) {
  347. var cmp = mappingA.generatedLine - mappingB.generatedLine;
  348. if (cmp !== 0) {
  349. return cmp;
  350. }
  351. cmp = mappingA.generatedColumn - mappingB.generatedColumn;
  352. if (cmp !== 0) {
  353. return cmp;
  354. }
  355. cmp = strcmp(mappingA.source, mappingB.source);
  356. if (cmp !== 0) {
  357. return cmp;
  358. }
  359. cmp = mappingA.originalLine - mappingB.originalLine;
  360. if (cmp !== 0) {
  361. return cmp;
  362. }
  363. cmp = mappingA.originalColumn - mappingB.originalColumn;
  364. if (cmp !== 0) {
  365. return cmp;
  366. }
  367. return strcmp(mappingA.name, mappingB.name);
  368. }
  369. exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflated;
  370. /**
  371. * Strip any JSON XSSI avoidance prefix from the string (as documented
  372. * in the source maps specification), and then parse the string as
  373. * JSON.
  374. */
  375. function parseSourceMapInput(str) {
  376. return JSON.parse(str.replace(/^\)]}'[^\n]*\n/, ''));
  377. }
  378. exports.parseSourceMapInput = parseSourceMapInput;
  379. /**
  380. * Compute the URL of a source given the the source root, the source's
  381. * URL, and the source map's URL.
  382. */
  383. function computeSourceURL(sourceRoot, sourceURL, sourceMapURL) {
  384. sourceURL = sourceURL || '';
  385. if (sourceRoot) {
  386. // This follows what Chrome does.
  387. if (sourceRoot[sourceRoot.length - 1] !== '/' && sourceURL[0] !== '/') {
  388. sourceRoot += '/';
  389. }
  390. // The spec says:
  391. // Line 4: An optional source root, useful for relocating source
  392. // files on a server or removing repeated values in the
  393. // “sources” entry. This value is prepended to the individual
  394. // entries in the “source” field.
  395. sourceURL = sourceRoot + sourceURL;
  396. }
  397. // Historically, SourceMapConsumer did not take the sourceMapURL as
  398. // a parameter. This mode is still somewhat supported, which is why
  399. // this code block is conditional. However, it's preferable to pass
  400. // the source map URL to SourceMapConsumer, so that this function
  401. // can implement the source URL resolution algorithm as outlined in
  402. // the spec. This block is basically the equivalent of:
  403. // new URL(sourceURL, sourceMapURL).toString()
  404. // ... except it avoids using URL, which wasn't available in the
  405. // older releases of node still supported by this library.
  406. //
  407. // The spec says:
  408. // If the sources are not absolute URLs after prepending of the
  409. // “sourceRoot”, the sources are resolved relative to the
  410. // SourceMap (like resolving script src in a html document).
  411. if (sourceMapURL) {
  412. var parsed = urlParse(sourceMapURL);
  413. if (!parsed) {
  414. throw new Error("sourceMapURL could not be parsed");
  415. }
  416. if (parsed.path) {
  417. // Strip the last path component, but keep the "/".
  418. var index = parsed.path.lastIndexOf('/');
  419. if (index >= 0) {
  420. parsed.path = parsed.path.substring(0, index + 1);
  421. }
  422. }
  423. sourceURL = join(urlGenerate(parsed), sourceURL);
  424. }
  425. return normalize(sourceURL);
  426. }
  427. exports.computeSourceURL = computeSourceURL;