utils.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750
  1. /* eslint-env browser */
  2. /**
  3. * Module dependencies.
  4. */
  5. var basename = require('path').basename;
  6. var debug = require('debug')('mocha:watch');
  7. var exists = require('fs').existsSync || require('path').existsSync;
  8. var glob = require('glob');
  9. var join = require('path').join;
  10. var readdirSync = require('fs').readdirSync;
  11. var statSync = require('fs').statSync;
  12. var watchFile = require('fs').watchFile;
  13. var toISOString = require('to-iso-string');
  14. /**
  15. * Ignored directories.
  16. */
  17. var ignore = ['node_modules', '.git'];
  18. exports.inherits = require('util').inherits;
  19. /**
  20. * Escape special characters in the given string of html.
  21. *
  22. * @api private
  23. * @param {string} html
  24. * @return {string}
  25. */
  26. exports.escape = function(html) {
  27. return String(html)
  28. .replace(/&/g, '&')
  29. .replace(/"/g, '"')
  30. .replace(/</g, '&lt;')
  31. .replace(/>/g, '&gt;');
  32. };
  33. /**
  34. * Array#forEach (<=IE8)
  35. *
  36. * @api private
  37. * @param {Array} arr
  38. * @param {Function} fn
  39. * @param {Object} scope
  40. */
  41. exports.forEach = function(arr, fn, scope) {
  42. for (var i = 0, l = arr.length; i < l; i++) {
  43. fn.call(scope, arr[i], i);
  44. }
  45. };
  46. /**
  47. * Test if the given obj is type of string.
  48. *
  49. * @api private
  50. * @param {Object} obj
  51. * @return {boolean}
  52. */
  53. exports.isString = function(obj) {
  54. return typeof obj === 'string';
  55. };
  56. /**
  57. * Array#map (<=IE8)
  58. *
  59. * @api private
  60. * @param {Array} arr
  61. * @param {Function} fn
  62. * @param {Object} scope
  63. * @return {Array}
  64. */
  65. exports.map = function(arr, fn, scope) {
  66. var result = [];
  67. for (var i = 0, l = arr.length; i < l; i++) {
  68. result.push(fn.call(scope, arr[i], i, arr));
  69. }
  70. return result;
  71. };
  72. /**
  73. * Array#indexOf (<=IE8)
  74. *
  75. * @api private
  76. * @param {Array} arr
  77. * @param {Object} obj to find index of
  78. * @param {number} start
  79. * @return {number}
  80. */
  81. exports.indexOf = function(arr, obj, start) {
  82. for (var i = start || 0, l = arr.length; i < l; i++) {
  83. if (arr[i] === obj) {
  84. return i;
  85. }
  86. }
  87. return -1;
  88. };
  89. /**
  90. * Array#reduce (<=IE8)
  91. *
  92. * @api private
  93. * @param {Array} arr
  94. * @param {Function} fn
  95. * @param {Object} val Initial value.
  96. * @return {*}
  97. */
  98. exports.reduce = function(arr, fn, val) {
  99. var rval = val;
  100. for (var i = 0, l = arr.length; i < l; i++) {
  101. rval = fn(rval, arr[i], i, arr);
  102. }
  103. return rval;
  104. };
  105. /**
  106. * Array#filter (<=IE8)
  107. *
  108. * @api private
  109. * @param {Array} arr
  110. * @param {Function} fn
  111. * @return {Array}
  112. */
  113. exports.filter = function(arr, fn) {
  114. var ret = [];
  115. for (var i = 0, l = arr.length; i < l; i++) {
  116. var val = arr[i];
  117. if (fn(val, i, arr)) {
  118. ret.push(val);
  119. }
  120. }
  121. return ret;
  122. };
  123. /**
  124. * Object.keys (<=IE8)
  125. *
  126. * @api private
  127. * @param {Object} obj
  128. * @return {Array} keys
  129. */
  130. exports.keys = typeof Object.keys === 'function' ? Object.keys : function(obj) {
  131. var keys = [];
  132. var has = Object.prototype.hasOwnProperty; // for `window` on <=IE8
  133. for (var key in obj) {
  134. if (has.call(obj, key)) {
  135. keys.push(key);
  136. }
  137. }
  138. return keys;
  139. };
  140. /**
  141. * Watch the given `files` for changes
  142. * and invoke `fn(file)` on modification.
  143. *
  144. * @api private
  145. * @param {Array} files
  146. * @param {Function} fn
  147. */
  148. exports.watch = function(files, fn) {
  149. var options = { interval: 100 };
  150. files.forEach(function(file) {
  151. debug('file %s', file);
  152. watchFile(file, options, function(curr, prev) {
  153. if (prev.mtime < curr.mtime) {
  154. fn(file);
  155. }
  156. });
  157. });
  158. };
  159. /**
  160. * Array.isArray (<=IE8)
  161. *
  162. * @api private
  163. * @param {Object} obj
  164. * @return {Boolean}
  165. */
  166. var isArray = typeof Array.isArray === 'function' ? Array.isArray : function(obj) {
  167. return Object.prototype.toString.call(obj) === '[object Array]';
  168. };
  169. exports.isArray = isArray;
  170. /**
  171. * Buffer.prototype.toJSON polyfill.
  172. *
  173. * @type {Function}
  174. */
  175. if (typeof Buffer !== 'undefined' && Buffer.prototype) {
  176. Buffer.prototype.toJSON = Buffer.prototype.toJSON || function() {
  177. return Array.prototype.slice.call(this, 0);
  178. };
  179. }
  180. /**
  181. * Ignored files.
  182. *
  183. * @api private
  184. * @param {string} path
  185. * @return {boolean}
  186. */
  187. function ignored(path) {
  188. return !~ignore.indexOf(path);
  189. }
  190. /**
  191. * Lookup files in the given `dir`.
  192. *
  193. * @api private
  194. * @param {string} dir
  195. * @param {string[]} [ext=['.js']]
  196. * @param {Array} [ret=[]]
  197. * @return {Array}
  198. */
  199. exports.files = function(dir, ext, ret) {
  200. ret = ret || [];
  201. ext = ext || ['js'];
  202. var re = new RegExp('\\.(' + ext.join('|') + ')$');
  203. readdirSync(dir)
  204. .filter(ignored)
  205. .forEach(function(path) {
  206. path = join(dir, path);
  207. if (statSync(path).isDirectory()) {
  208. exports.files(path, ext, ret);
  209. } else if (path.match(re)) {
  210. ret.push(path);
  211. }
  212. });
  213. return ret;
  214. };
  215. /**
  216. * Compute a slug from the given `str`.
  217. *
  218. * @api private
  219. * @param {string} str
  220. * @return {string}
  221. */
  222. exports.slug = function(str) {
  223. return str
  224. .toLowerCase()
  225. .replace(/ +/g, '-')
  226. .replace(/[^-\w]/g, '');
  227. };
  228. /**
  229. * Strip the function definition from `str`, and re-indent for pre whitespace.
  230. *
  231. * @param {string} str
  232. * @return {string}
  233. */
  234. exports.clean = function(str) {
  235. str = str
  236. .replace(/\r\n?|[\n\u2028\u2029]/g, '\n').replace(/^\uFEFF/, '')
  237. .replace(/^function *\(.*\)\s*\{|\(.*\) *=> *\{?/, '')
  238. .replace(/\s+\}$/, '');
  239. var spaces = str.match(/^\n?( *)/)[1].length;
  240. var tabs = str.match(/^\n?(\t*)/)[1].length;
  241. var re = new RegExp('^\n?' + (tabs ? '\t' : ' ') + '{' + (tabs ? tabs : spaces) + '}', 'gm');
  242. str = str.replace(re, '');
  243. return exports.trim(str);
  244. };
  245. /**
  246. * Trim the given `str`.
  247. *
  248. * @api private
  249. * @param {string} str
  250. * @return {string}
  251. */
  252. exports.trim = function(str) {
  253. return str.replace(/^\s+|\s+$/g, '');
  254. };
  255. /**
  256. * Parse the given `qs`.
  257. *
  258. * @api private
  259. * @param {string} qs
  260. * @return {Object}
  261. */
  262. exports.parseQuery = function(qs) {
  263. return exports.reduce(qs.replace('?', '').split('&'), function(obj, pair) {
  264. var i = pair.indexOf('=');
  265. var key = pair.slice(0, i);
  266. var val = pair.slice(++i);
  267. obj[key] = decodeURIComponent(val);
  268. return obj;
  269. }, {});
  270. };
  271. /**
  272. * Highlight the given string of `js`.
  273. *
  274. * @api private
  275. * @param {string} js
  276. * @return {string}
  277. */
  278. function highlight(js) {
  279. return js
  280. .replace(/</g, '&lt;')
  281. .replace(/>/g, '&gt;')
  282. .replace(/\/\/(.*)/gm, '<span class="comment">//$1</span>')
  283. .replace(/('.*?')/gm, '<span class="string">$1</span>')
  284. .replace(/(\d+\.\d+)/gm, '<span class="number">$1</span>')
  285. .replace(/(\d+)/gm, '<span class="number">$1</span>')
  286. .replace(/\bnew[ \t]+(\w+)/gm, '<span class="keyword">new</span> <span class="init">$1</span>')
  287. .replace(/\b(function|new|throw|return|var|if|else)\b/gm, '<span class="keyword">$1</span>');
  288. }
  289. /**
  290. * Highlight the contents of tag `name`.
  291. *
  292. * @api private
  293. * @param {string} name
  294. */
  295. exports.highlightTags = function(name) {
  296. var code = document.getElementById('mocha').getElementsByTagName(name);
  297. for (var i = 0, len = code.length; i < len; ++i) {
  298. code[i].innerHTML = highlight(code[i].innerHTML);
  299. }
  300. };
  301. /**
  302. * If a value could have properties, and has none, this function is called,
  303. * which returns a string representation of the empty value.
  304. *
  305. * Functions w/ no properties return `'[Function]'`
  306. * Arrays w/ length === 0 return `'[]'`
  307. * Objects w/ no properties return `'{}'`
  308. * All else: return result of `value.toString()`
  309. *
  310. * @api private
  311. * @param {*} value The value to inspect.
  312. * @param {string} [type] The type of the value, if known.
  313. * @returns {string}
  314. */
  315. function emptyRepresentation(value, type) {
  316. type = type || exports.type(value);
  317. switch (type) {
  318. case 'function':
  319. return '[Function]';
  320. case 'object':
  321. return '{}';
  322. case 'array':
  323. return '[]';
  324. default:
  325. return value.toString();
  326. }
  327. }
  328. /**
  329. * Takes some variable and asks `Object.prototype.toString()` what it thinks it
  330. * is.
  331. *
  332. * @api private
  333. * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString
  334. * @param {*} value The value to test.
  335. * @returns {string}
  336. * @example
  337. * type({}) // 'object'
  338. * type([]) // 'array'
  339. * type(1) // 'number'
  340. * type(false) // 'boolean'
  341. * type(Infinity) // 'number'
  342. * type(null) // 'null'
  343. * type(new Date()) // 'date'
  344. * type(/foo/) // 'regexp'
  345. * type('type') // 'string'
  346. * type(global) // 'global'
  347. */
  348. exports.type = function type(value) {
  349. if (value === undefined) {
  350. return 'undefined';
  351. } else if (value === null) {
  352. return 'null';
  353. } else if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) {
  354. return 'buffer';
  355. }
  356. return Object.prototype.toString.call(value)
  357. .replace(/^\[.+\s(.+?)\]$/, '$1')
  358. .toLowerCase();
  359. };
  360. /**
  361. * Stringify `value`. Different behavior depending on type of value:
  362. *
  363. * - If `value` is undefined or null, return `'[undefined]'` or `'[null]'`, respectively.
  364. * - If `value` is not an object, function or array, return result of `value.toString()` wrapped in double-quotes.
  365. * - If `value` is an *empty* object, function, or array, return result of function
  366. * {@link emptyRepresentation}.
  367. * - If `value` has properties, call {@link exports.canonicalize} on it, then return result of
  368. * JSON.stringify().
  369. *
  370. * @api private
  371. * @see exports.type
  372. * @param {*} value
  373. * @return {string}
  374. */
  375. exports.stringify = function(value) {
  376. var type = exports.type(value);
  377. if (!~exports.indexOf(['object', 'array', 'function'], type)) {
  378. if (type !== 'buffer') {
  379. return jsonStringify(value);
  380. }
  381. var json = value.toJSON();
  382. // Based on the toJSON result
  383. return jsonStringify(json.data && json.type ? json.data : json, 2)
  384. .replace(/,(\n|$)/g, '$1');
  385. }
  386. for (var prop in value) {
  387. if (Object.prototype.hasOwnProperty.call(value, prop)) {
  388. return jsonStringify(exports.canonicalize(value), 2).replace(/,(\n|$)/g, '$1');
  389. }
  390. }
  391. return emptyRepresentation(value, type);
  392. };
  393. /**
  394. * like JSON.stringify but more sense.
  395. *
  396. * @api private
  397. * @param {Object} object
  398. * @param {number=} spaces
  399. * @param {number=} depth
  400. * @returns {*}
  401. */
  402. function jsonStringify(object, spaces, depth) {
  403. if (typeof spaces === 'undefined') {
  404. // primitive types
  405. return _stringify(object);
  406. }
  407. depth = depth || 1;
  408. var space = spaces * depth;
  409. var str = isArray(object) ? '[' : '{';
  410. var end = isArray(object) ? ']' : '}';
  411. var length = typeof object.length === 'number' ? object.length : exports.keys(object).length;
  412. // `.repeat()` polyfill
  413. function repeat(s, n) {
  414. return new Array(n).join(s);
  415. }
  416. function _stringify(val) {
  417. switch (exports.type(val)) {
  418. case 'null':
  419. case 'undefined':
  420. val = '[' + val + ']';
  421. break;
  422. case 'array':
  423. case 'object':
  424. val = jsonStringify(val, spaces, depth + 1);
  425. break;
  426. case 'boolean':
  427. case 'regexp':
  428. case 'symbol':
  429. case 'number':
  430. val = val === 0 && (1 / val) === -Infinity // `-0`
  431. ? '-0'
  432. : val.toString();
  433. break;
  434. case 'date':
  435. var sDate;
  436. if (isNaN(val.getTime())) { // Invalid date
  437. sDate = val.toString();
  438. } else {
  439. sDate = val.toISOString ? val.toISOString() : toISOString(val);
  440. }
  441. val = '[Date: ' + sDate + ']';
  442. break;
  443. case 'buffer':
  444. var json = val.toJSON();
  445. // Based on the toJSON result
  446. json = json.data && json.type ? json.data : json;
  447. val = '[Buffer: ' + jsonStringify(json, 2, depth + 1) + ']';
  448. break;
  449. default:
  450. val = (val === '[Function]' || val === '[Circular]')
  451. ? val
  452. : JSON.stringify(val); // string
  453. }
  454. return val;
  455. }
  456. for (var i in object) {
  457. if (!Object.prototype.hasOwnProperty.call(object, i)) {
  458. continue; // not my business
  459. }
  460. --length;
  461. str += '\n ' + repeat(' ', space)
  462. + (isArray(object) ? '' : '"' + i + '": ') // key
  463. + _stringify(object[i]) // value
  464. + (length ? ',' : ''); // comma
  465. }
  466. return str
  467. // [], {}
  468. + (str.length !== 1 ? '\n' + repeat(' ', --space) + end : end);
  469. }
  470. /**
  471. * Test if a value is a buffer.
  472. *
  473. * @api private
  474. * @param {*} value The value to test.
  475. * @return {boolean} True if `value` is a buffer, otherwise false
  476. */
  477. exports.isBuffer = function(value) {
  478. return typeof Buffer !== 'undefined' && Buffer.isBuffer(value);
  479. };
  480. /**
  481. * Return a new Thing that has the keys in sorted order. Recursive.
  482. *
  483. * If the Thing...
  484. * - has already been seen, return string `'[Circular]'`
  485. * - is `undefined`, return string `'[undefined]'`
  486. * - is `null`, return value `null`
  487. * - is some other primitive, return the value
  488. * - is not a primitive or an `Array`, `Object`, or `Function`, return the value of the Thing's `toString()` method
  489. * - is a non-empty `Array`, `Object`, or `Function`, return the result of calling this function again.
  490. * - is an empty `Array`, `Object`, or `Function`, return the result of calling `emptyRepresentation()`
  491. *
  492. * @api private
  493. * @see {@link exports.stringify}
  494. * @param {*} value Thing to inspect. May or may not have properties.
  495. * @param {Array} [stack=[]] Stack of seen values
  496. * @return {(Object|Array|Function|string|undefined)}
  497. */
  498. exports.canonicalize = function(value, stack) {
  499. var canonicalizedObj;
  500. /* eslint-disable no-unused-vars */
  501. var prop;
  502. /* eslint-enable no-unused-vars */
  503. var type = exports.type(value);
  504. function withStack(value, fn) {
  505. stack.push(value);
  506. fn();
  507. stack.pop();
  508. }
  509. stack = stack || [];
  510. if (exports.indexOf(stack, value) !== -1) {
  511. return '[Circular]';
  512. }
  513. switch (type) {
  514. case 'undefined':
  515. case 'buffer':
  516. case 'null':
  517. canonicalizedObj = value;
  518. break;
  519. case 'array':
  520. withStack(value, function() {
  521. canonicalizedObj = exports.map(value, function(item) {
  522. return exports.canonicalize(item, stack);
  523. });
  524. });
  525. break;
  526. case 'function':
  527. /* eslint-disable guard-for-in */
  528. for (prop in value) {
  529. canonicalizedObj = {};
  530. break;
  531. }
  532. /* eslint-enable guard-for-in */
  533. if (!canonicalizedObj) {
  534. canonicalizedObj = emptyRepresentation(value, type);
  535. break;
  536. }
  537. /* falls through */
  538. case 'object':
  539. canonicalizedObj = canonicalizedObj || {};
  540. withStack(value, function() {
  541. exports.forEach(exports.keys(value).sort(), function(key) {
  542. canonicalizedObj[key] = exports.canonicalize(value[key], stack);
  543. });
  544. });
  545. break;
  546. case 'date':
  547. case 'number':
  548. case 'regexp':
  549. case 'boolean':
  550. case 'symbol':
  551. canonicalizedObj = value;
  552. break;
  553. default:
  554. canonicalizedObj = value + '';
  555. }
  556. return canonicalizedObj;
  557. };
  558. /**
  559. * Lookup file names at the given `path`.
  560. *
  561. * @api public
  562. * @param {string} path Base path to start searching from.
  563. * @param {string[]} extensions File extensions to look for.
  564. * @param {boolean} recursive Whether or not to recurse into subdirectories.
  565. * @return {string[]} An array of paths.
  566. */
  567. exports.lookupFiles = function lookupFiles(path, extensions, recursive) {
  568. var files = [];
  569. var re = new RegExp('\\.(' + extensions.join('|') + ')$');
  570. if (!exists(path)) {
  571. if (exists(path + '.js')) {
  572. path += '.js';
  573. } else {
  574. files = glob.sync(path);
  575. if (!files.length) {
  576. throw new Error("cannot resolve path (or pattern) '" + path + "'");
  577. }
  578. return files;
  579. }
  580. }
  581. try {
  582. var stat = statSync(path);
  583. if (stat.isFile()) {
  584. return path;
  585. }
  586. } catch (err) {
  587. // ignore error
  588. return;
  589. }
  590. readdirSync(path).forEach(function(file) {
  591. file = join(path, file);
  592. try {
  593. var stat = statSync(file);
  594. if (stat.isDirectory()) {
  595. if (recursive) {
  596. files = files.concat(lookupFiles(file, extensions, recursive));
  597. }
  598. return;
  599. }
  600. } catch (err) {
  601. // ignore error
  602. return;
  603. }
  604. if (!stat.isFile() || !re.test(file) || basename(file)[0] === '.') {
  605. return;
  606. }
  607. files.push(file);
  608. });
  609. return files;
  610. };
  611. /**
  612. * Generate an undefined error with a message warning the user.
  613. *
  614. * @return {Error}
  615. */
  616. exports.undefinedError = function() {
  617. return new Error('Caught undefined error, did you throw without specifying what?');
  618. };
  619. /**
  620. * Generate an undefined error if `err` is not defined.
  621. *
  622. * @param {Error} err
  623. * @return {Error}
  624. */
  625. exports.getError = function(err) {
  626. return err || exports.undefinedError();
  627. };
  628. /**
  629. * @summary
  630. * This Filter based on `mocha-clean` module.(see: `github.com/rstacruz/mocha-clean`)
  631. * @description
  632. * When invoking this function you get a filter function that get the Error.stack as an input,
  633. * and return a prettify output.
  634. * (i.e: strip Mocha and internal node functions from stack trace).
  635. * @returns {Function}
  636. */
  637. exports.stackTraceFilter = function() {
  638. // TODO: Replace with `process.browser`
  639. var slash = '/';
  640. var is = typeof document === 'undefined' ? { node: true } : { browser: true };
  641. var cwd = is.node
  642. ? process.cwd() + slash
  643. : (typeof location === 'undefined' ? window.location : location).href.replace(/\/[^\/]*$/, '/');
  644. function isMochaInternal(line) {
  645. return (~line.indexOf('node_modules' + slash + 'mocha' + slash))
  646. || (~line.indexOf('components' + slash + 'mochajs' + slash))
  647. || (~line.indexOf('components' + slash + 'mocha' + slash))
  648. || (~line.indexOf(slash + 'mocha.js'));
  649. }
  650. function isNodeInternal(line) {
  651. return (~line.indexOf('(timers.js:'))
  652. || (~line.indexOf('(events.js:'))
  653. || (~line.indexOf('(node.js:'))
  654. || (~line.indexOf('(module.js:'))
  655. || (~line.indexOf('GeneratorFunctionPrototype.next (native)'))
  656. || false;
  657. }
  658. return function(stack) {
  659. stack = stack.split('\n');
  660. stack = exports.reduce(stack, function(list, line) {
  661. if (isMochaInternal(line)) {
  662. return list;
  663. }
  664. if (is.node && isNodeInternal(line)) {
  665. return list;
  666. }
  667. // Clean up cwd(absolute)
  668. if (/\(?.+:\d+:\d+\)?$/.test(line)) {
  669. line = line.replace(cwd, '');
  670. }
  671. list.push(line);
  672. return list;
  673. }, []);
  674. return stack.join('\n');
  675. };
  676. };