utils.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897
  1. 'use strict';
  2. /**
  3. * Various utility functions used throughout Mocha's codebase.
  4. * @module utils
  5. */
  6. /**
  7. * Module dependencies.
  8. */
  9. var fs = require('fs');
  10. var path = require('path');
  11. var util = require('util');
  12. var glob = require('glob');
  13. var he = require('he');
  14. var errors = require('./errors');
  15. var createNoFilesMatchPatternError = errors.createNoFilesMatchPatternError;
  16. var createMissingArgumentError = errors.createMissingArgumentError;
  17. var assign = (exports.assign = require('object.assign').getPolyfill());
  18. /**
  19. * Inherit the prototype methods from one constructor into another.
  20. *
  21. * @param {function} ctor - Constructor function which needs to inherit the
  22. * prototype.
  23. * @param {function} superCtor - Constructor function to inherit prototype from.
  24. * @throws {TypeError} if either constructor is null, or if super constructor
  25. * lacks a prototype.
  26. */
  27. exports.inherits = util.inherits;
  28. /**
  29. * Escape special characters in the given string of html.
  30. *
  31. * @private
  32. * @param {string} html
  33. * @return {string}
  34. */
  35. exports.escape = function(html) {
  36. return he.encode(String(html), {useNamedReferences: false});
  37. };
  38. /**
  39. * Test if the given obj is type of string.
  40. *
  41. * @private
  42. * @param {Object} obj
  43. * @return {boolean}
  44. */
  45. exports.isString = function(obj) {
  46. return typeof obj === 'string';
  47. };
  48. /**
  49. * Watch the given `files` for changes
  50. * and invoke `fn(file)` on modification.
  51. *
  52. * @private
  53. * @param {Array} files
  54. * @param {Function} fn
  55. */
  56. exports.watch = function(files, fn) {
  57. var options = {interval: 100};
  58. var debug = require('debug')('mocha:watch');
  59. files.forEach(function(file) {
  60. debug('file %s', file);
  61. fs.watchFile(file, options, function(curr, prev) {
  62. if (prev.mtime < curr.mtime) {
  63. fn(file);
  64. }
  65. });
  66. });
  67. };
  68. /**
  69. * Predicate to screen `pathname` for further consideration.
  70. *
  71. * @description
  72. * Returns <code>false</code> for pathname referencing:
  73. * <ul>
  74. * <li>'npm' package installation directory
  75. * <li>'git' version control directory
  76. * </ul>
  77. *
  78. * @private
  79. * @param {string} pathname - File or directory name to screen
  80. * @return {boolean} whether pathname should be further considered
  81. * @example
  82. * ['node_modules', 'test.js'].filter(considerFurther); // => ['test.js']
  83. */
  84. function considerFurther(pathname) {
  85. var ignore = ['node_modules', '.git'];
  86. return !~ignore.indexOf(pathname);
  87. }
  88. /**
  89. * Lookup files in the given `dir`.
  90. *
  91. * @description
  92. * Filenames are returned in _traversal_ order by the OS/filesystem.
  93. * **Make no assumption that the names will be sorted in any fashion.**
  94. *
  95. * @private
  96. * @param {string} dir
  97. * @param {string[]} [exts=['js']]
  98. * @param {Array} [ret=[]]
  99. * @return {Array}
  100. */
  101. exports.files = function(dir, exts, ret) {
  102. ret = ret || [];
  103. exts = exts || ['js'];
  104. fs.readdirSync(dir)
  105. .filter(considerFurther)
  106. .forEach(function(dirent) {
  107. var pathname = path.join(dir, dirent);
  108. if (fs.lstatSync(pathname).isDirectory()) {
  109. exports.files(pathname, exts, ret);
  110. } else if (hasMatchingExtname(pathname, exts)) {
  111. ret.push(pathname);
  112. }
  113. });
  114. return ret;
  115. };
  116. /**
  117. * Compute a slug from the given `str`.
  118. *
  119. * @private
  120. * @param {string} str
  121. * @return {string}
  122. */
  123. exports.slug = function(str) {
  124. return str
  125. .toLowerCase()
  126. .replace(/ +/g, '-')
  127. .replace(/[^-\w]/g, '');
  128. };
  129. /**
  130. * Strip the function definition from `str`, and re-indent for pre whitespace.
  131. *
  132. * @param {string} str
  133. * @return {string}
  134. */
  135. exports.clean = function(str) {
  136. str = str
  137. .replace(/\r\n?|[\n\u2028\u2029]/g, '\n')
  138. .replace(/^\uFEFF/, '')
  139. // (traditional)-> space/name parameters body (lambda)-> parameters body multi-statement/single keep body content
  140. .replace(
  141. /^function(?:\s*|\s+[^(]*)\([^)]*\)\s*\{((?:.|\n)*?)\s*\}$|^\([^)]*\)\s*=>\s*(?:\{((?:.|\n)*?)\s*\}|((?:.|\n)*))$/,
  142. '$1$2$3'
  143. );
  144. var spaces = str.match(/^\n?( *)/)[1].length;
  145. var tabs = str.match(/^\n?(\t*)/)[1].length;
  146. var re = new RegExp(
  147. '^\n?' + (tabs ? '\t' : ' ') + '{' + (tabs || spaces) + '}',
  148. 'gm'
  149. );
  150. str = str.replace(re, '');
  151. return str.trim();
  152. };
  153. /**
  154. * Parse the given `qs`.
  155. *
  156. * @private
  157. * @param {string} qs
  158. * @return {Object}
  159. */
  160. exports.parseQuery = function(qs) {
  161. return qs
  162. .replace('?', '')
  163. .split('&')
  164. .reduce(function(obj, pair) {
  165. var i = pair.indexOf('=');
  166. var key = pair.slice(0, i);
  167. var val = pair.slice(++i);
  168. // Due to how the URLSearchParams API treats spaces
  169. obj[key] = decodeURIComponent(val.replace(/\+/g, '%20'));
  170. return obj;
  171. }, {});
  172. };
  173. /**
  174. * Highlight the given string of `js`.
  175. *
  176. * @private
  177. * @param {string} js
  178. * @return {string}
  179. */
  180. function highlight(js) {
  181. return js
  182. .replace(/</g, '&lt;')
  183. .replace(/>/g, '&gt;')
  184. .replace(/\/\/(.*)/gm, '<span class="comment">//$1</span>')
  185. .replace(/('.*?')/gm, '<span class="string">$1</span>')
  186. .replace(/(\d+\.\d+)/gm, '<span class="number">$1</span>')
  187. .replace(/(\d+)/gm, '<span class="number">$1</span>')
  188. .replace(
  189. /\bnew[ \t]+(\w+)/gm,
  190. '<span class="keyword">new</span> <span class="init">$1</span>'
  191. )
  192. .replace(
  193. /\b(function|new|throw|return|var|if|else)\b/gm,
  194. '<span class="keyword">$1</span>'
  195. );
  196. }
  197. /**
  198. * Highlight the contents of tag `name`.
  199. *
  200. * @private
  201. * @param {string} name
  202. */
  203. exports.highlightTags = function(name) {
  204. var code = document.getElementById('mocha').getElementsByTagName(name);
  205. for (var i = 0, len = code.length; i < len; ++i) {
  206. code[i].innerHTML = highlight(code[i].innerHTML);
  207. }
  208. };
  209. /**
  210. * If a value could have properties, and has none, this function is called,
  211. * which returns a string representation of the empty value.
  212. *
  213. * Functions w/ no properties return `'[Function]'`
  214. * Arrays w/ length === 0 return `'[]'`
  215. * Objects w/ no properties return `'{}'`
  216. * All else: return result of `value.toString()`
  217. *
  218. * @private
  219. * @param {*} value The value to inspect.
  220. * @param {string} typeHint The type of the value
  221. * @returns {string}
  222. */
  223. function emptyRepresentation(value, typeHint) {
  224. switch (typeHint) {
  225. case 'function':
  226. return '[Function]';
  227. case 'object':
  228. return '{}';
  229. case 'array':
  230. return '[]';
  231. default:
  232. return value.toString();
  233. }
  234. }
  235. /**
  236. * Takes some variable and asks `Object.prototype.toString()` what it thinks it
  237. * is.
  238. *
  239. * @private
  240. * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString
  241. * @param {*} value The value to test.
  242. * @returns {string} Computed type
  243. * @example
  244. * type({}) // 'object'
  245. * type([]) // 'array'
  246. * type(1) // 'number'
  247. * type(false) // 'boolean'
  248. * type(Infinity) // 'number'
  249. * type(null) // 'null'
  250. * type(new Date()) // 'date'
  251. * type(/foo/) // 'regexp'
  252. * type('type') // 'string'
  253. * type(global) // 'global'
  254. * type(new String('foo') // 'object'
  255. */
  256. var type = (exports.type = function type(value) {
  257. if (value === undefined) {
  258. return 'undefined';
  259. } else if (value === null) {
  260. return 'null';
  261. } else if (Buffer.isBuffer(value)) {
  262. return 'buffer';
  263. }
  264. return Object.prototype.toString
  265. .call(value)
  266. .replace(/^\[.+\s(.+?)]$/, '$1')
  267. .toLowerCase();
  268. });
  269. /**
  270. * Stringify `value`. Different behavior depending on type of value:
  271. *
  272. * - If `value` is undefined or null, return `'[undefined]'` or `'[null]'`, respectively.
  273. * - If `value` is not an object, function or array, return result of `value.toString()` wrapped in double-quotes.
  274. * - If `value` is an *empty* object, function, or array, return result of function
  275. * {@link emptyRepresentation}.
  276. * - If `value` has properties, call {@link exports.canonicalize} on it, then return result of
  277. * JSON.stringify().
  278. *
  279. * @private
  280. * @see exports.type
  281. * @param {*} value
  282. * @return {string}
  283. */
  284. exports.stringify = function(value) {
  285. var typeHint = type(value);
  286. if (!~['object', 'array', 'function'].indexOf(typeHint)) {
  287. if (typeHint === 'buffer') {
  288. var json = Buffer.prototype.toJSON.call(value);
  289. // Based on the toJSON result
  290. return jsonStringify(
  291. json.data && json.type ? json.data : json,
  292. 2
  293. ).replace(/,(\n|$)/g, '$1');
  294. }
  295. // IE7/IE8 has a bizarre String constructor; needs to be coerced
  296. // into an array and back to obj.
  297. if (typeHint === 'string' && typeof value === 'object') {
  298. value = value.split('').reduce(function(acc, char, idx) {
  299. acc[idx] = char;
  300. return acc;
  301. }, {});
  302. typeHint = 'object';
  303. } else {
  304. return jsonStringify(value);
  305. }
  306. }
  307. for (var prop in value) {
  308. if (Object.prototype.hasOwnProperty.call(value, prop)) {
  309. return jsonStringify(
  310. exports.canonicalize(value, null, typeHint),
  311. 2
  312. ).replace(/,(\n|$)/g, '$1');
  313. }
  314. }
  315. return emptyRepresentation(value, typeHint);
  316. };
  317. /**
  318. * like JSON.stringify but more sense.
  319. *
  320. * @private
  321. * @param {Object} object
  322. * @param {number=} spaces
  323. * @param {number=} depth
  324. * @returns {*}
  325. */
  326. function jsonStringify(object, spaces, depth) {
  327. if (typeof spaces === 'undefined') {
  328. // primitive types
  329. return _stringify(object);
  330. }
  331. depth = depth || 1;
  332. var space = spaces * depth;
  333. var str = Array.isArray(object) ? '[' : '{';
  334. var end = Array.isArray(object) ? ']' : '}';
  335. var length =
  336. typeof object.length === 'number'
  337. ? object.length
  338. : Object.keys(object).length;
  339. // `.repeat()` polyfill
  340. function repeat(s, n) {
  341. return new Array(n).join(s);
  342. }
  343. function _stringify(val) {
  344. switch (type(val)) {
  345. case 'null':
  346. case 'undefined':
  347. val = '[' + val + ']';
  348. break;
  349. case 'array':
  350. case 'object':
  351. val = jsonStringify(val, spaces, depth + 1);
  352. break;
  353. case 'boolean':
  354. case 'regexp':
  355. case 'symbol':
  356. case 'number':
  357. val =
  358. val === 0 && 1 / val === -Infinity // `-0`
  359. ? '-0'
  360. : val.toString();
  361. break;
  362. case 'date':
  363. var sDate = isNaN(val.getTime()) ? val.toString() : val.toISOString();
  364. val = '[Date: ' + sDate + ']';
  365. break;
  366. case 'buffer':
  367. var json = val.toJSON();
  368. // Based on the toJSON result
  369. json = json.data && json.type ? json.data : json;
  370. val = '[Buffer: ' + jsonStringify(json, 2, depth + 1) + ']';
  371. break;
  372. default:
  373. val =
  374. val === '[Function]' || val === '[Circular]'
  375. ? val
  376. : JSON.stringify(val); // string
  377. }
  378. return val;
  379. }
  380. for (var i in object) {
  381. if (!Object.prototype.hasOwnProperty.call(object, i)) {
  382. continue; // not my business
  383. }
  384. --length;
  385. str +=
  386. '\n ' +
  387. repeat(' ', space) +
  388. (Array.isArray(object) ? '' : '"' + i + '": ') + // key
  389. _stringify(object[i]) + // value
  390. (length ? ',' : ''); // comma
  391. }
  392. return (
  393. str +
  394. // [], {}
  395. (str.length !== 1 ? '\n' + repeat(' ', --space) + end : end)
  396. );
  397. }
  398. /**
  399. * Return a new Thing that has the keys in sorted order. Recursive.
  400. *
  401. * If the Thing...
  402. * - has already been seen, return string `'[Circular]'`
  403. * - is `undefined`, return string `'[undefined]'`
  404. * - is `null`, return value `null`
  405. * - is some other primitive, return the value
  406. * - is not a primitive or an `Array`, `Object`, or `Function`, return the value of the Thing's `toString()` method
  407. * - is a non-empty `Array`, `Object`, or `Function`, return the result of calling this function again.
  408. * - is an empty `Array`, `Object`, or `Function`, return the result of calling `emptyRepresentation()`
  409. *
  410. * @private
  411. * @see {@link exports.stringify}
  412. * @param {*} value Thing to inspect. May or may not have properties.
  413. * @param {Array} [stack=[]] Stack of seen values
  414. * @param {string} [typeHint] Type hint
  415. * @return {(Object|Array|Function|string|undefined)}
  416. */
  417. exports.canonicalize = function canonicalize(value, stack, typeHint) {
  418. var canonicalizedObj;
  419. /* eslint-disable no-unused-vars */
  420. var prop;
  421. /* eslint-enable no-unused-vars */
  422. typeHint = typeHint || type(value);
  423. function withStack(value, fn) {
  424. stack.push(value);
  425. fn();
  426. stack.pop();
  427. }
  428. stack = stack || [];
  429. if (stack.indexOf(value) !== -1) {
  430. return '[Circular]';
  431. }
  432. switch (typeHint) {
  433. case 'undefined':
  434. case 'buffer':
  435. case 'null':
  436. canonicalizedObj = value;
  437. break;
  438. case 'array':
  439. withStack(value, function() {
  440. canonicalizedObj = value.map(function(item) {
  441. return exports.canonicalize(item, stack);
  442. });
  443. });
  444. break;
  445. case 'function':
  446. /* eslint-disable guard-for-in */
  447. for (prop in value) {
  448. canonicalizedObj = {};
  449. break;
  450. }
  451. /* eslint-enable guard-for-in */
  452. if (!canonicalizedObj) {
  453. canonicalizedObj = emptyRepresentation(value, typeHint);
  454. break;
  455. }
  456. /* falls through */
  457. case 'object':
  458. canonicalizedObj = canonicalizedObj || {};
  459. withStack(value, function() {
  460. Object.keys(value)
  461. .sort()
  462. .forEach(function(key) {
  463. canonicalizedObj[key] = exports.canonicalize(value[key], stack);
  464. });
  465. });
  466. break;
  467. case 'date':
  468. case 'number':
  469. case 'regexp':
  470. case 'boolean':
  471. case 'symbol':
  472. canonicalizedObj = value;
  473. break;
  474. default:
  475. canonicalizedObj = value + '';
  476. }
  477. return canonicalizedObj;
  478. };
  479. /**
  480. * Determines if pathname has a matching file extension.
  481. *
  482. * @private
  483. * @param {string} pathname - Pathname to check for match.
  484. * @param {string[]} exts - List of file extensions (sans period).
  485. * @return {boolean} whether file extension matches.
  486. * @example
  487. * hasMatchingExtname('foo.html', ['js', 'css']); // => false
  488. */
  489. function hasMatchingExtname(pathname, exts) {
  490. var suffix = path.extname(pathname).slice(1);
  491. return exts.some(function(element) {
  492. return suffix === element;
  493. });
  494. }
  495. /**
  496. * Determines if pathname would be a "hidden" file (or directory) on UN*X.
  497. *
  498. * @description
  499. * On UN*X, pathnames beginning with a full stop (aka dot) are hidden during
  500. * typical usage. Dotfiles, plain-text configuration files, are prime examples.
  501. *
  502. * @see {@link http://xahlee.info/UnixResource_dir/writ/unix_origin_of_dot_filename.html|Origin of Dot File Names}
  503. *
  504. * @private
  505. * @param {string} pathname - Pathname to check for match.
  506. * @return {boolean} whether pathname would be considered a hidden file.
  507. * @example
  508. * isHiddenOnUnix('.profile'); // => true
  509. */
  510. function isHiddenOnUnix(pathname) {
  511. return path.basename(pathname)[0] === '.';
  512. }
  513. /**
  514. * Lookup file names at the given `path`.
  515. *
  516. * @description
  517. * Filenames are returned in _traversal_ order by the OS/filesystem.
  518. * **Make no assumption that the names will be sorted in any fashion.**
  519. *
  520. * @public
  521. * @memberof Mocha.utils
  522. * @todo Fix extension handling
  523. * @param {string} filepath - Base path to start searching from.
  524. * @param {string[]} extensions - File extensions to look for.
  525. * @param {boolean} recursive - Whether to recurse into subdirectories.
  526. * @return {string[]} An array of paths.
  527. * @throws {Error} if no files match pattern.
  528. * @throws {TypeError} if `filepath` is directory and `extensions` not provided.
  529. */
  530. exports.lookupFiles = function lookupFiles(filepath, extensions, recursive) {
  531. var files = [];
  532. var stat;
  533. if (!fs.existsSync(filepath)) {
  534. if (fs.existsSync(filepath + '.js')) {
  535. filepath += '.js';
  536. } else {
  537. // Handle glob
  538. files = glob.sync(filepath);
  539. if (!files.length) {
  540. throw createNoFilesMatchPatternError(
  541. 'Cannot find any files matching pattern ' + exports.dQuote(filepath),
  542. filepath
  543. );
  544. }
  545. return files;
  546. }
  547. }
  548. // Handle file
  549. try {
  550. stat = fs.statSync(filepath);
  551. if (stat.isFile()) {
  552. return filepath;
  553. }
  554. } catch (err) {
  555. // ignore error
  556. return;
  557. }
  558. // Handle directory
  559. fs.readdirSync(filepath).forEach(function(dirent) {
  560. var pathname = path.join(filepath, dirent);
  561. var stat;
  562. try {
  563. stat = fs.statSync(pathname);
  564. if (stat.isDirectory()) {
  565. if (recursive) {
  566. files = files.concat(lookupFiles(pathname, extensions, recursive));
  567. }
  568. return;
  569. }
  570. } catch (err) {
  571. // ignore error
  572. return;
  573. }
  574. if (!extensions) {
  575. throw createMissingArgumentError(
  576. util.format(
  577. 'Argument %s required when argument %s is a directory',
  578. exports.sQuote('extensions'),
  579. exports.sQuote('filepath')
  580. ),
  581. 'extensions',
  582. 'array'
  583. );
  584. }
  585. if (
  586. !stat.isFile() ||
  587. !hasMatchingExtname(pathname, extensions) ||
  588. isHiddenOnUnix(pathname)
  589. ) {
  590. return;
  591. }
  592. files.push(pathname);
  593. });
  594. return files;
  595. };
  596. /**
  597. * process.emitWarning or a polyfill
  598. * @see https://nodejs.org/api/process.html#process_process_emitwarning_warning_options
  599. * @ignore
  600. */
  601. function emitWarning(msg, type) {
  602. if (process.emitWarning) {
  603. process.emitWarning(msg, type);
  604. } else {
  605. process.nextTick(function() {
  606. console.warn(type + ': ' + msg);
  607. });
  608. }
  609. }
  610. /**
  611. * Show a deprecation warning. Each distinct message is only displayed once.
  612. * Ignores empty messages.
  613. *
  614. * @param {string} [msg] - Warning to print
  615. * @private
  616. */
  617. exports.deprecate = function deprecate(msg) {
  618. msg = String(msg);
  619. if (msg && !deprecate.cache[msg]) {
  620. deprecate.cache[msg] = true;
  621. emitWarning(msg, 'DeprecationWarning');
  622. }
  623. };
  624. exports.deprecate.cache = {};
  625. /**
  626. * Show a generic warning.
  627. * Ignores empty messages.
  628. *
  629. * @param {string} [msg] - Warning to print
  630. * @private
  631. */
  632. exports.warn = function warn(msg) {
  633. if (msg) {
  634. emitWarning(msg);
  635. }
  636. };
  637. /**
  638. * @summary
  639. * This Filter based on `mocha-clean` module.(see: `github.com/rstacruz/mocha-clean`)
  640. * @description
  641. * When invoking this function you get a filter function that get the Error.stack as an input,
  642. * and return a prettify output.
  643. * (i.e: strip Mocha and internal node functions from stack trace).
  644. * @returns {Function}
  645. */
  646. exports.stackTraceFilter = function() {
  647. // TODO: Replace with `process.browser`
  648. var is = typeof document === 'undefined' ? {node: true} : {browser: true};
  649. var slash = path.sep;
  650. var cwd;
  651. if (is.node) {
  652. cwd = process.cwd() + slash;
  653. } else {
  654. cwd = (typeof location === 'undefined'
  655. ? window.location
  656. : location
  657. ).href.replace(/\/[^/]*$/, '/');
  658. slash = '/';
  659. }
  660. function isMochaInternal(line) {
  661. return (
  662. ~line.indexOf('node_modules' + slash + 'mocha' + slash) ||
  663. ~line.indexOf(slash + 'mocha.js')
  664. );
  665. }
  666. function isNodeInternal(line) {
  667. return (
  668. ~line.indexOf('(timers.js:') ||
  669. ~line.indexOf('(events.js:') ||
  670. ~line.indexOf('(node.js:') ||
  671. ~line.indexOf('(module.js:') ||
  672. ~line.indexOf('GeneratorFunctionPrototype.next (native)') ||
  673. false
  674. );
  675. }
  676. return function(stack) {
  677. stack = stack.split('\n');
  678. stack = stack.reduce(function(list, line) {
  679. if (isMochaInternal(line)) {
  680. return list;
  681. }
  682. if (is.node && isNodeInternal(line)) {
  683. return list;
  684. }
  685. // Clean up cwd(absolute)
  686. if (/:\d+:\d+\)?$/.test(line)) {
  687. line = line.replace('(' + cwd, '(');
  688. }
  689. list.push(line);
  690. return list;
  691. }, []);
  692. return stack.join('\n');
  693. };
  694. };
  695. /**
  696. * Crude, but effective.
  697. * @public
  698. * @param {*} value
  699. * @returns {boolean} Whether or not `value` is a Promise
  700. */
  701. exports.isPromise = function isPromise(value) {
  702. return (
  703. typeof value === 'object' &&
  704. value !== null &&
  705. typeof value.then === 'function'
  706. );
  707. };
  708. /**
  709. * Clamps a numeric value to an inclusive range.
  710. *
  711. * @param {number} value - Value to be clamped.
  712. * @param {numer[]} range - Two element array specifying [min, max] range.
  713. * @returns {number} clamped value
  714. */
  715. exports.clamp = function clamp(value, range) {
  716. return Math.min(Math.max(value, range[0]), range[1]);
  717. };
  718. /**
  719. * Single quote text by combining with undirectional ASCII quotation marks.
  720. *
  721. * @description
  722. * Provides a simple means of markup for quoting text to be used in output.
  723. * Use this to quote names of variables, methods, and packages.
  724. *
  725. * <samp>package 'foo' cannot be found</samp>
  726. *
  727. * @private
  728. * @param {string} str - Value to be quoted.
  729. * @returns {string} quoted value
  730. * @example
  731. * sQuote('n') // => 'n'
  732. */
  733. exports.sQuote = function(str) {
  734. return "'" + str + "'";
  735. };
  736. /**
  737. * Double quote text by combining with undirectional ASCII quotation marks.
  738. *
  739. * @description
  740. * Provides a simple means of markup for quoting text to be used in output.
  741. * Use this to quote names of datatypes, classes, pathnames, and strings.
  742. *
  743. * <samp>argument 'value' must be "string" or "number"</samp>
  744. *
  745. * @private
  746. * @param {string} str - Value to be quoted.
  747. * @returns {string} quoted value
  748. * @example
  749. * dQuote('number') // => "number"
  750. */
  751. exports.dQuote = function(str) {
  752. return '"' + str + '"';
  753. };
  754. /**
  755. * Provides simplistic message translation for dealing with plurality.
  756. *
  757. * @description
  758. * Use this to create messages which need to be singular or plural.
  759. * Some languages have several plural forms, so _complete_ message clauses
  760. * are preferable to generating the message on the fly.
  761. *
  762. * @private
  763. * @param {number} n - Non-negative integer
  764. * @param {string} msg1 - Message to be used in English for `n = 1`
  765. * @param {string} msg2 - Message to be used in English for `n = 0, 2, 3, ...`
  766. * @returns {string} message corresponding to value of `n`
  767. * @example
  768. * var sprintf = require('util').format;
  769. * var pkgs = ['one', 'two'];
  770. * var msg = sprintf(
  771. * ngettext(
  772. * pkgs.length,
  773. * 'cannot load package: %s',
  774. * 'cannot load packages: %s'
  775. * ),
  776. * pkgs.map(sQuote).join(', ')
  777. * );
  778. * console.log(msg); // => cannot load packages: 'one', 'two'
  779. */
  780. exports.ngettext = function(n, msg1, msg2) {
  781. if (typeof n === 'number' && n >= 0) {
  782. return n === 1 ? msg1 : msg2;
  783. }
  784. };
  785. /**
  786. * It's a noop.
  787. * @public
  788. */
  789. exports.noop = function() {};
  790. /**
  791. * Creates a map-like object.
  792. *
  793. * @description
  794. * A "map" is an object with no prototype, for our purposes. In some cases
  795. * this would be more appropriate than a `Map`, especially if your environment
  796. * doesn't support it. Recommended for use in Mocha's public APIs.
  797. *
  798. * @public
  799. * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map|MDN:Map}
  800. * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create#Custom_and_Null_objects|MDN:Object.create - Custom objects}
  801. * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign|MDN:Object.assign}
  802. * @param {...*} [obj] - Arguments to `Object.assign()`.
  803. * @returns {Object} An object with no prototype, having `...obj` properties
  804. */
  805. exports.createMap = function(obj) {
  806. return assign.apply(
  807. null,
  808. [Object.create(null)].concat(Array.prototype.slice.call(arguments))
  809. );
  810. };
  811. /**
  812. * Creates a read-only map-like object.
  813. *
  814. * @description
  815. * This differs from {@link module:utils.createMap createMap} only in that
  816. * the argument must be non-empty, because the result is frozen.
  817. *
  818. * @see {@link module:utils.createMap createMap}
  819. * @param {...*} [obj] - Arguments to `Object.assign()`.
  820. * @returns {Object} A frozen object with no prototype, having `...obj` properties
  821. * @throws {TypeError} if argument is not a non-empty object.
  822. */
  823. exports.defineConstants = function(obj) {
  824. if (type(obj) !== 'object' || !Object.keys(obj).length) {
  825. throw new TypeError('Invalid argument; expected a non-empty object');
  826. }
  827. return Object.freeze(exports.createMap(obj));
  828. };