util.js 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  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. if (typeof define !== 'function') {
  8. var define = require('amdefine')(module, require);
  9. }
  10. define(function (require, exports, module) {
  11. /**
  12. * This is a helper function for getting values from parameter/options
  13. * objects.
  14. *
  15. * @param args The object we are extracting values from
  16. * @param name The name of the property we are getting.
  17. * @param defaultValue An optional value to return if the property is missing
  18. * from the object. If this is not specified and the property is missing, an
  19. * error will be thrown.
  20. */
  21. function getArg(aArgs, aName, aDefaultValue) {
  22. if (aName in aArgs) {
  23. return aArgs[aName];
  24. } else if (arguments.length === 3) {
  25. return aDefaultValue;
  26. } else {
  27. throw new Error('"' + aName + '" is a required argument.');
  28. }
  29. }
  30. exports.getArg = getArg;
  31. var urlRegexp = /^(?:([\w+\-.]+):)?\/\/(?:(\w+:\w+)@)?([\w.]*)(?::(\d+))?(\S*)$/;
  32. var dataUrlRegexp = /^data:.+\,.+$/;
  33. function urlParse(aUrl) {
  34. var match = aUrl.match(urlRegexp);
  35. if (!match) {
  36. return null;
  37. }
  38. return {
  39. scheme: match[1],
  40. auth: match[2],
  41. host: match[3],
  42. port: match[4],
  43. path: match[5]
  44. };
  45. }
  46. exports.urlParse = urlParse;
  47. function urlGenerate(aParsedUrl) {
  48. var url = '';
  49. if (aParsedUrl.scheme) {
  50. url += aParsedUrl.scheme + ':';
  51. }
  52. url += '//';
  53. if (aParsedUrl.auth) {
  54. url += aParsedUrl.auth + '@';
  55. }
  56. if (aParsedUrl.host) {
  57. url += aParsedUrl.host;
  58. }
  59. if (aParsedUrl.port) {
  60. url += ":" + aParsedUrl.port
  61. }
  62. if (aParsedUrl.path) {
  63. url += aParsedUrl.path;
  64. }
  65. return url;
  66. }
  67. exports.urlGenerate = urlGenerate;
  68. /**
  69. * Normalizes a path, or the path portion of a URL:
  70. *
  71. * - Replaces consequtive slashes with one slash.
  72. * - Removes unnecessary '.' parts.
  73. * - Removes unnecessary '<dir>/..' parts.
  74. *
  75. * Based on code in the Node.js 'path' core module.
  76. *
  77. * @param aPath The path or url to normalize.
  78. */
  79. function normalize(aPath) {
  80. var path = aPath;
  81. var url = urlParse(aPath);
  82. if (url) {
  83. if (!url.path) {
  84. return aPath;
  85. }
  86. path = url.path;
  87. }
  88. var isAbsolute = (path.charAt(0) === '/');
  89. var parts = path.split(/\/+/);
  90. for (var part, up = 0, i = parts.length - 1; i >= 0; i--) {
  91. part = parts[i];
  92. if (part === '.') {
  93. parts.splice(i, 1);
  94. } else if (part === '..') {
  95. up++;
  96. } else if (up > 0) {
  97. if (part === '') {
  98. // The first part is blank if the path is absolute. Trying to go
  99. // above the root is a no-op. Therefore we can remove all '..' parts
  100. // directly after the root.
  101. parts.splice(i + 1, up);
  102. up = 0;
  103. } else {
  104. parts.splice(i, 2);
  105. up--;
  106. }
  107. }
  108. }
  109. path = parts.join('/');
  110. if (path === '') {
  111. path = isAbsolute ? '/' : '.';
  112. }
  113. if (url) {
  114. url.path = path;
  115. return urlGenerate(url);
  116. }
  117. return path;
  118. }
  119. exports.normalize = normalize;
  120. /**
  121. * Joins two paths/URLs.
  122. *
  123. * @param aRoot The root path or URL.
  124. * @param aPath The path or URL to be joined with the root.
  125. *
  126. * - If aPath is a URL or a data URI, aPath is returned, unless aPath is a
  127. * scheme-relative URL: Then the scheme of aRoot, if any, is prepended
  128. * first.
  129. * - Otherwise aPath is a path. If aRoot is a URL, then its path portion
  130. * is updated with the result and aRoot is returned. Otherwise the result
  131. * is returned.
  132. * - If aPath is absolute, the result is aPath.
  133. * - Otherwise the two paths are joined with a slash.
  134. * - Joining for example 'http://' and 'www.example.com' is also supported.
  135. */
  136. function join(aRoot, aPath) {
  137. if (aRoot === "") {
  138. aRoot = ".";
  139. }
  140. if (aPath === "") {
  141. aPath = ".";
  142. }
  143. var aPathUrl = urlParse(aPath);
  144. var aRootUrl = urlParse(aRoot);
  145. if (aRootUrl) {
  146. aRoot = aRootUrl.path || '/';
  147. }
  148. // `join(foo, '//www.example.org')`
  149. if (aPathUrl && !aPathUrl.scheme) {
  150. if (aRootUrl) {
  151. aPathUrl.scheme = aRootUrl.scheme;
  152. }
  153. return urlGenerate(aPathUrl);
  154. }
  155. if (aPathUrl || aPath.match(dataUrlRegexp)) {
  156. return aPath;
  157. }
  158. // `join('http://', 'www.example.com')`
  159. if (aRootUrl && !aRootUrl.host && !aRootUrl.path) {
  160. aRootUrl.host = aPath;
  161. return urlGenerate(aRootUrl);
  162. }
  163. var joined = aPath.charAt(0) === '/'
  164. ? aPath
  165. : normalize(aRoot.replace(/\/+$/, '') + '/' + aPath);
  166. if (aRootUrl) {
  167. aRootUrl.path = joined;
  168. return urlGenerate(aRootUrl);
  169. }
  170. return joined;
  171. }
  172. exports.join = join;
  173. /**
  174. * Make a path relative to a URL or another path.
  175. *
  176. * @param aRoot The root path or URL.
  177. * @param aPath The path or URL to be made relative to aRoot.
  178. */
  179. function relative(aRoot, aPath) {
  180. if (aRoot === "") {
  181. aRoot = ".";
  182. }
  183. aRoot = aRoot.replace(/\/$/, '');
  184. // It is possible for the path to be above the root. In this case, simply
  185. // checking whether the root is a prefix of the path won't work. Instead, we
  186. // need to remove components from the root one by one, until either we find
  187. // a prefix that fits, or we run out of components to remove.
  188. var level = 0;
  189. while (aPath.indexOf(aRoot + '/') !== 0) {
  190. var index = aRoot.lastIndexOf("/");
  191. if (index < 0) {
  192. return aPath;
  193. }
  194. // If the only part of the root that is left is the scheme (i.e. http://,
  195. // file:///, etc.), one or more slashes (/), or simply nothing at all, we
  196. // have exhausted all components, so the path is not relative to the root.
  197. aRoot = aRoot.slice(0, index);
  198. if (aRoot.match(/^([^\/]+:\/)?\/*$/)) {
  199. return aPath;
  200. }
  201. ++level;
  202. }
  203. // Make sure we add a "../" for each component we removed from the root.
  204. return Array(level + 1).join("../") + aPath.substr(aRoot.length + 1);
  205. }
  206. exports.relative = relative;
  207. /**
  208. * Because behavior goes wacky when you set `__proto__` on objects, we
  209. * have to prefix all the strings in our set with an arbitrary character.
  210. *
  211. * See https://github.com/mozilla/source-map/pull/31 and
  212. * https://github.com/mozilla/source-map/issues/30
  213. *
  214. * @param String aStr
  215. */
  216. function toSetString(aStr) {
  217. return '$' + aStr;
  218. }
  219. exports.toSetString = toSetString;
  220. function fromSetString(aStr) {
  221. return aStr.substr(1);
  222. }
  223. exports.fromSetString = fromSetString;
  224. /**
  225. * Comparator between two mappings where the original positions are compared.
  226. *
  227. * Optionally pass in `true` as `onlyCompareGenerated` to consider two
  228. * mappings with the same original source/line/column, but different generated
  229. * line and column the same. Useful when searching for a mapping with a
  230. * stubbed out mapping.
  231. */
  232. function compareByOriginalPositions(mappingA, mappingB, onlyCompareOriginal) {
  233. var cmp = mappingA.source - mappingB.source;
  234. if (cmp !== 0) {
  235. return cmp;
  236. }
  237. cmp = mappingA.originalLine - mappingB.originalLine;
  238. if (cmp !== 0) {
  239. return cmp;
  240. }
  241. cmp = mappingA.originalColumn - mappingB.originalColumn;
  242. if (cmp !== 0 || onlyCompareOriginal) {
  243. return cmp;
  244. }
  245. cmp = mappingA.generatedColumn - mappingB.generatedColumn;
  246. if (cmp !== 0) {
  247. return cmp;
  248. }
  249. cmp = mappingA.generatedLine - mappingB.generatedLine;
  250. if (cmp !== 0) {
  251. return cmp;
  252. }
  253. return mappingA.name - mappingB.name;
  254. };
  255. exports.compareByOriginalPositions = compareByOriginalPositions;
  256. /**
  257. * Comparator between two mappings with deflated source and name indices where
  258. * the generated positions are compared.
  259. *
  260. * Optionally pass in `true` as `onlyCompareGenerated` to consider two
  261. * mappings with the same generated line and column, but different
  262. * source/name/original line and column the same. Useful when searching for a
  263. * mapping with a stubbed out mapping.
  264. */
  265. function compareByGeneratedPositionsDeflated(mappingA, mappingB, onlyCompareGenerated) {
  266. var cmp = mappingA.generatedLine - mappingB.generatedLine;
  267. if (cmp !== 0) {
  268. return cmp;
  269. }
  270. cmp = mappingA.generatedColumn - mappingB.generatedColumn;
  271. if (cmp !== 0 || onlyCompareGenerated) {
  272. return cmp;
  273. }
  274. cmp = mappingA.source - mappingB.source;
  275. if (cmp !== 0) {
  276. return cmp;
  277. }
  278. cmp = mappingA.originalLine - mappingB.originalLine;
  279. if (cmp !== 0) {
  280. return cmp;
  281. }
  282. cmp = mappingA.originalColumn - mappingB.originalColumn;
  283. if (cmp !== 0) {
  284. return cmp;
  285. }
  286. return mappingA.name - mappingB.name;
  287. };
  288. exports.compareByGeneratedPositionsDeflated = compareByGeneratedPositionsDeflated;
  289. function strcmp(aStr1, aStr2) {
  290. if (aStr1 === aStr2) {
  291. return 0;
  292. }
  293. if (aStr1 > aStr2) {
  294. return 1;
  295. }
  296. return -1;
  297. }
  298. /**
  299. * Comparator between two mappings with inflated source and name strings where
  300. * the generated positions are compared.
  301. */
  302. function compareByGeneratedPositionsInflated(mappingA, mappingB) {
  303. var cmp = mappingA.generatedLine - mappingB.generatedLine;
  304. if (cmp !== 0) {
  305. return cmp;
  306. }
  307. cmp = mappingA.generatedColumn - mappingB.generatedColumn;
  308. if (cmp !== 0) {
  309. return cmp;
  310. }
  311. cmp = strcmp(mappingA.source, mappingB.source);
  312. if (cmp !== 0) {
  313. return cmp;
  314. }
  315. cmp = mappingA.originalLine - mappingB.originalLine;
  316. if (cmp !== 0) {
  317. return cmp;
  318. }
  319. cmp = mappingA.originalColumn - mappingB.originalColumn;
  320. if (cmp !== 0) {
  321. return cmp;
  322. }
  323. return strcmp(mappingA.name, mappingB.name);
  324. };
  325. exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflated;
  326. });