utils.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698
  1. 'use strict';
  2. /**
  3. * Various utility functions used throughout Mocha's codebase.
  4. * @module utils
  5. */
  6. /**
  7. * Module dependencies.
  8. */
  9. var path = require('node:path');
  10. var util = require('node:util');
  11. var he = require('he');
  12. const MOCHA_ID_PROP_NAME = '__mocha_id__';
  13. /**
  14. * Inherit the prototype methods from one constructor into another.
  15. *
  16. * @param {function} ctor - Constructor function which needs to inherit the
  17. * prototype.
  18. * @param {function} superCtor - Constructor function to inherit prototype from.
  19. * @throws {TypeError} if either constructor is null, or if super constructor
  20. * lacks a prototype.
  21. */
  22. exports.inherits = util.inherits;
  23. /**
  24. * Escape special characters in the given string of html.
  25. *
  26. * @private
  27. * @param {string} html
  28. * @return {string}
  29. */
  30. exports.escape = function (html) {
  31. return he.encode(String(html), {useNamedReferences: false});
  32. };
  33. /**
  34. * Test if the given obj is type of string.
  35. *
  36. * @private
  37. * @param {Object} obj
  38. * @return {boolean}
  39. */
  40. exports.isString = function (obj) {
  41. return typeof obj === 'string';
  42. };
  43. /**
  44. * Compute a slug from the given `str`.
  45. *
  46. * @private
  47. * @param {string} str
  48. * @return {string}
  49. */
  50. exports.slug = function (str) {
  51. return str
  52. .toLowerCase()
  53. .replace(/\s+/g, '-')
  54. .replace(/[^-\w]/g, '')
  55. .replace(/-{2,}/g, '-');
  56. };
  57. /**
  58. * Strip the function definition from `str`, and re-indent for pre whitespace.
  59. *
  60. * @param {string} str
  61. * @return {string}
  62. */
  63. exports.clean = function (str) {
  64. str = str
  65. .replace(/\r\n?|[\n\u2028\u2029]/g, '\n')
  66. .replace(/^\uFEFF/, '')
  67. // (traditional)-> space/name parameters body (lambda)-> parameters body multi-statement/single keep body content
  68. .replace(
  69. /^function(?:\s*|\s[^(]*)\([^)]*\)\s*\{((?:.|\n)*?)\}$|^\([^)]*\)\s*=>\s*(?:\{((?:.|\n)*?)\}|((?:.|\n)*))$/,
  70. '$1$2$3'
  71. );
  72. var spaces = str.match(/^\n?( *)/)[1].length;
  73. var tabs = str.match(/^\n?(\t*)/)[1].length;
  74. var re = new RegExp(
  75. '^\n?' + (tabs ? '\t' : ' ') + '{' + (tabs || spaces) + '}',
  76. 'gm'
  77. );
  78. str = str.replace(re, '');
  79. return str.trim();
  80. };
  81. /**
  82. * If a value could have properties, and has none, this function is called,
  83. * which returns a string representation of the empty value.
  84. *
  85. * Functions w/ no properties return `'[Function]'`
  86. * Arrays w/ length === 0 return `'[]'`
  87. * Objects w/ no properties return `'{}'`
  88. * All else: return result of `value.toString()`
  89. *
  90. * @private
  91. * @param {*} value The value to inspect.
  92. * @param {string} typeHint The type of the value
  93. * @returns {string}
  94. */
  95. function emptyRepresentation(value, typeHint) {
  96. switch (typeHint) {
  97. case 'function':
  98. return '[Function]';
  99. case 'object':
  100. return '{}';
  101. case 'array':
  102. return '[]';
  103. default:
  104. return value.toString();
  105. }
  106. }
  107. /**
  108. * Takes some variable and asks `Object.prototype.toString()` what it thinks it
  109. * is.
  110. *
  111. * @private
  112. * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString
  113. * @param {*} value The value to test.
  114. * @returns {string} Computed type
  115. * @example
  116. * canonicalType({}) // 'object'
  117. * canonicalType([]) // 'array'
  118. * canonicalType(1) // 'number'
  119. * canonicalType(false) // 'boolean'
  120. * canonicalType(Infinity) // 'number'
  121. * canonicalType(null) // 'null'
  122. * canonicalType(new Date()) // 'date'
  123. * canonicalType(/foo/) // 'regexp'
  124. * canonicalType('type') // 'string'
  125. * canonicalType(global) // 'global'
  126. * canonicalType(new String('foo') // 'object'
  127. * canonicalType(async function() {}) // 'asyncfunction'
  128. * canonicalType(Object.create(null)) // 'null-prototype'
  129. */
  130. var canonicalType = (exports.canonicalType = function canonicalType(value) {
  131. if (value === undefined) {
  132. return 'undefined';
  133. } else if (value === null) {
  134. return 'null';
  135. } else if (Buffer.isBuffer(value)) {
  136. return 'buffer';
  137. } else if (Object.getPrototypeOf(value) === null) {
  138. return 'null-prototype';
  139. }
  140. return Object.prototype.toString
  141. .call(value)
  142. .replace(/^\[.+\s(.+?)]$/, '$1')
  143. .toLowerCase();
  144. });
  145. /**
  146. *
  147. * Returns a general type or data structure of a variable
  148. * @private
  149. * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures
  150. * @param {*} value The value to test.
  151. * @returns {string} One of undefined, boolean, number, string, bigint, symbol, object
  152. * @example
  153. * type({}) // 'object'
  154. * type([]) // 'array'
  155. * type(1) // 'number'
  156. * type(false) // 'boolean'
  157. * type(Infinity) // 'number'
  158. * type(null) // 'null'
  159. * type(new Date()) // 'object'
  160. * type(/foo/) // 'object'
  161. * type('type') // 'string'
  162. * type(global) // 'object'
  163. * type(new String('foo') // 'string'
  164. */
  165. exports.type = function type(value) {
  166. // Null is special
  167. if (value === null) return 'null';
  168. const primitives = new Set([
  169. 'undefined',
  170. 'boolean',
  171. 'number',
  172. 'string',
  173. 'bigint',
  174. 'symbol'
  175. ]);
  176. const _type = typeof value;
  177. if (_type === 'function') return _type;
  178. if (primitives.has(_type)) return _type;
  179. if (value instanceof String) return 'string';
  180. if (value instanceof Error) return 'error';
  181. if (Array.isArray(value)) return 'array';
  182. return _type;
  183. };
  184. /**
  185. * Stringify `value`. Different behavior depending on type of value:
  186. *
  187. * - If `value` is undefined or null, return `'[undefined]'` or `'[null]'`, respectively.
  188. * - If `value` is not an object, function or array, return result of `value.toString()` wrapped in double-quotes.
  189. * - If `value` is an *empty* object, function, or array, return result of function
  190. * {@link emptyRepresentation}.
  191. * - If `value` has properties, call {@link exports.canonicalize} on it, then return result of
  192. * JSON.stringify().
  193. *
  194. * @private
  195. * @see exports.type
  196. * @param {*} value
  197. * @return {string}
  198. */
  199. exports.stringify = function (value) {
  200. var typeHint = canonicalType(value);
  201. if (!~['object', 'array', 'function', 'null-prototype'].indexOf(typeHint)) {
  202. if (typeHint === 'buffer') {
  203. var json = Buffer.prototype.toJSON.call(value);
  204. // Based on the toJSON result
  205. return jsonStringify(
  206. json.data && json.type ? json.data : json,
  207. 2
  208. ).replace(/,(\n|$)/g, '$1');
  209. }
  210. // IE7/IE8 has a bizarre String constructor; needs to be coerced
  211. // into an array and back to obj.
  212. if (typeHint === 'string' && typeof value === 'object') {
  213. value = value.split('').reduce(function (acc, char, idx) {
  214. acc[idx] = char;
  215. return acc;
  216. }, {});
  217. typeHint = 'object';
  218. } else {
  219. return jsonStringify(value);
  220. }
  221. }
  222. for (var prop in value) {
  223. if (Object.prototype.hasOwnProperty.call(value, prop)) {
  224. return jsonStringify(
  225. exports.canonicalize(value, null, typeHint),
  226. 2
  227. ).replace(/,(\n|$)/g, '$1');
  228. }
  229. }
  230. return emptyRepresentation(value, typeHint);
  231. };
  232. /**
  233. * like JSON.stringify but more sense.
  234. *
  235. * @private
  236. * @param {Object} object
  237. * @param {number=} spaces
  238. * @param {number=} depth
  239. * @returns {*}
  240. */
  241. function jsonStringify(object, spaces, depth) {
  242. if (typeof spaces === 'undefined') {
  243. // primitive types
  244. return _stringify(object);
  245. }
  246. depth = depth || 1;
  247. var space = spaces * depth;
  248. var str = Array.isArray(object) ? '[' : '{';
  249. var end = Array.isArray(object) ? ']' : '}';
  250. var length =
  251. typeof object.length === 'number'
  252. ? object.length
  253. : Object.keys(object).length;
  254. // `.repeat()` polyfill
  255. function repeat(s, n) {
  256. return new Array(n).join(s);
  257. }
  258. function _stringify(val) {
  259. switch (canonicalType(val)) {
  260. case 'null':
  261. case 'undefined':
  262. val = '[' + val + ']';
  263. break;
  264. case 'array':
  265. case 'object':
  266. val = jsonStringify(val, spaces, depth + 1);
  267. break;
  268. case 'boolean':
  269. case 'regexp':
  270. case 'symbol':
  271. case 'number':
  272. val =
  273. val === 0 && 1 / val === -Infinity // `-0`
  274. ? '-0'
  275. : val.toString();
  276. break;
  277. case 'bigint':
  278. val = val.toString() + 'n';
  279. break;
  280. case 'date':
  281. var sDate = isNaN(val.getTime()) ? val.toString() : val.toISOString();
  282. val = '[Date: ' + sDate + ']';
  283. break;
  284. case 'buffer':
  285. var json = val.toJSON();
  286. // Based on the toJSON result
  287. json = json.data && json.type ? json.data : json;
  288. val = '[Buffer: ' + jsonStringify(json, 2, depth + 1) + ']';
  289. break;
  290. default:
  291. val =
  292. val === '[Function]' || val === '[Circular]'
  293. ? val
  294. : JSON.stringify(val); // string
  295. }
  296. return val;
  297. }
  298. for (var i in object) {
  299. if (!Object.prototype.hasOwnProperty.call(object, i)) {
  300. continue; // not my business
  301. }
  302. --length;
  303. str +=
  304. '\n ' +
  305. repeat(' ', space) +
  306. (Array.isArray(object) ? '' : '"' + i + '": ') + // key
  307. _stringify(object[i]) + // value
  308. (length ? ',' : ''); // comma
  309. }
  310. return (
  311. str +
  312. // [], {}
  313. (str.length !== 1 ? '\n' + repeat(' ', --space) + end : end)
  314. );
  315. }
  316. /**
  317. * Return a new Thing that has the keys in sorted order. Recursive.
  318. *
  319. * If the Thing...
  320. * - has already been seen, return string `'[Circular]'`
  321. * - is `undefined`, return string `'[undefined]'`
  322. * - is `null`, return value `null`
  323. * - is some other primitive, return the value
  324. * - is not a primitive or an `Array`, `Object`, or `Function`, return the value of the Thing's `toString()` method
  325. * - is a non-empty `Array`, `Object`, or `Function`, return the result of calling this function again.
  326. * - is an empty `Array`, `Object`, or `Function`, return the result of calling `emptyRepresentation()`
  327. *
  328. * @private
  329. * @see {@link exports.stringify}
  330. * @param {*} value Thing to inspect. May or may not have properties.
  331. * @param {Array} [stack=[]] Stack of seen values
  332. * @param {string} [typeHint] Type hint
  333. * @return {(Object|Array|Function|string|undefined)}
  334. */
  335. exports.canonicalize = function canonicalize(value, stack, typeHint) {
  336. var canonicalizedObj;
  337. /* eslint-disable no-unused-vars */
  338. var prop;
  339. /* eslint-enable no-unused-vars */
  340. typeHint = typeHint || canonicalType(value);
  341. function withStack(value, fn) {
  342. stack.push(value);
  343. fn();
  344. stack.pop();
  345. }
  346. stack = stack || [];
  347. if (stack.indexOf(value) !== -1) {
  348. return '[Circular]';
  349. }
  350. switch (typeHint) {
  351. case 'undefined':
  352. case 'buffer':
  353. case 'null':
  354. canonicalizedObj = value;
  355. break;
  356. case 'array':
  357. withStack(value, function () {
  358. canonicalizedObj = value.map(function (item) {
  359. return exports.canonicalize(item, stack);
  360. });
  361. });
  362. break;
  363. case 'function':
  364. /* eslint-disable-next-line no-unused-vars, no-unreachable-loop */
  365. for (prop in value) {
  366. canonicalizedObj = {};
  367. break;
  368. }
  369. /* eslint-enable guard-for-in */
  370. if (!canonicalizedObj) {
  371. canonicalizedObj = emptyRepresentation(value, typeHint);
  372. break;
  373. }
  374. /* falls through */
  375. case 'null-prototype':
  376. case 'object':
  377. canonicalizedObj = canonicalizedObj || {};
  378. if (typeHint === 'null-prototype' && Symbol.toStringTag in value) {
  379. canonicalizedObj['[Symbol.toStringTag]'] = value[Symbol.toStringTag];
  380. }
  381. withStack(value, function () {
  382. Object.keys(value)
  383. .sort()
  384. .forEach(function (key) {
  385. canonicalizedObj[key] = exports.canonicalize(value[key], stack);
  386. });
  387. });
  388. break;
  389. case 'date':
  390. case 'number':
  391. case 'regexp':
  392. case 'boolean':
  393. case 'symbol':
  394. canonicalizedObj = value;
  395. break;
  396. default:
  397. canonicalizedObj = value + '';
  398. }
  399. return canonicalizedObj;
  400. };
  401. /**
  402. * @summary
  403. * This Filter based on `mocha-clean` module.(see: `github.com/rstacruz/mocha-clean`)
  404. * @description
  405. * When invoking this function you get a filter function that get the Error.stack as an input,
  406. * and return a prettify output.
  407. * (i.e: strip Mocha and internal node functions from stack trace).
  408. * @returns {Function}
  409. */
  410. exports.stackTraceFilter = function () {
  411. // TODO: Replace with `process.browser`
  412. var is = typeof document === 'undefined' ? {node: true} : {browser: true};
  413. var slash = path.sep;
  414. var cwd;
  415. if (is.node) {
  416. cwd = exports.cwd() + slash;
  417. } else {
  418. cwd = (
  419. typeof location === 'undefined' ? window.location : location
  420. ).href.replace(/\/[^/]*$/, '/');
  421. slash = '/';
  422. }
  423. function isMochaInternal(line) {
  424. return (
  425. ~line.indexOf('node_modules' + slash + 'mocha' + slash) ||
  426. ~line.indexOf(slash + 'mocha.js') ||
  427. ~line.indexOf(slash + 'mocha.min.js')
  428. );
  429. }
  430. function isNodeInternal(line) {
  431. return (
  432. ~line.indexOf('(timers.js:') ||
  433. ~line.indexOf('(events.js:') ||
  434. ~line.indexOf('(node.js:') ||
  435. ~line.indexOf('(module.js:') ||
  436. ~line.indexOf('GeneratorFunctionPrototype.next (native)') ||
  437. false
  438. );
  439. }
  440. return function (stack) {
  441. stack = stack.split('\n');
  442. stack = stack.reduce(function (list, line) {
  443. if (isMochaInternal(line)) {
  444. return list;
  445. }
  446. if (is.node && isNodeInternal(line)) {
  447. return list;
  448. }
  449. // Clean up cwd(absolute)
  450. if (/:\d+:\d+\)?$/.test(line)) {
  451. line = line.replace('(' + cwd, '(');
  452. }
  453. list.push(line);
  454. return list;
  455. }, []);
  456. return stack.join('\n');
  457. };
  458. };
  459. /**
  460. * Crude, but effective.
  461. * @public
  462. * @param {*} value
  463. * @returns {boolean} Whether or not `value` is a Promise
  464. */
  465. exports.isPromise = function isPromise(value) {
  466. return (
  467. typeof value === 'object' &&
  468. value !== null &&
  469. typeof value.then === 'function'
  470. );
  471. };
  472. /**
  473. * Clamps a numeric value to an inclusive range.
  474. *
  475. * @param {number} value - Value to be clamped.
  476. * @param {number[]} range - Two element array specifying [min, max] range.
  477. * @returns {number} clamped value
  478. */
  479. exports.clamp = function clamp(value, range) {
  480. return Math.min(Math.max(value, range[0]), range[1]);
  481. };
  482. /**
  483. * It's a noop.
  484. * @public
  485. */
  486. exports.noop = function () {};
  487. /**
  488. * Creates a map-like object.
  489. *
  490. * @description
  491. * A "map" is an object with no prototype, for our purposes. In some cases
  492. * this would be more appropriate than a `Map`, especially if your environment
  493. * doesn't support it. Recommended for use in Mocha's public APIs.
  494. *
  495. * @public
  496. * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map#Custom_and_Null_objects|MDN:Map}
  497. * @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}
  498. * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#Custom_and_Null_objects|MDN:Object.assign}
  499. * @param {...*} [obj] - Arguments to `Object.assign()`.
  500. * @returns {Object} An object with no prototype, having `...obj` properties
  501. */
  502. exports.createMap = function () {
  503. return Object.assign.apply(
  504. null,
  505. [Object.create(null)].concat(Array.prototype.slice.call(arguments))
  506. );
  507. };
  508. /**
  509. * Creates a read-only map-like object.
  510. *
  511. * @description
  512. * This differs from {@link module:utils.createMap createMap} only in that
  513. * the argument must be non-empty, because the result is frozen.
  514. *
  515. * @see {@link module:utils.createMap createMap}
  516. * @param {...*} [obj] - Arguments to `Object.assign()`.
  517. * @returns {Object} A frozen object with no prototype, having `...obj` properties
  518. * @throws {TypeError} if argument is not a non-empty object.
  519. */
  520. exports.defineConstants = function (obj) {
  521. if (canonicalType(obj) !== 'object' || !Object.keys(obj).length) {
  522. throw new TypeError('Invalid argument; expected a non-empty object');
  523. }
  524. return Object.freeze(exports.createMap(obj));
  525. };
  526. /**
  527. * Returns current working directory
  528. *
  529. * Wrapper around `process.cwd()` for isolation
  530. * @private
  531. */
  532. exports.cwd = function cwd() {
  533. return process.cwd();
  534. };
  535. /**
  536. * Returns `true` if Mocha is running in a browser.
  537. * Checks for `process.browser`.
  538. * @returns {boolean}
  539. * @private
  540. */
  541. exports.isBrowser = function isBrowser() {
  542. return Boolean(process.browser);
  543. };
  544. /*
  545. * Casts `value` to an array; useful for optionally accepting array parameters
  546. *
  547. * It follows these rules, depending on `value`. If `value` is...
  548. * 1. `undefined`: return an empty Array
  549. * 2. `null`: return an array with a single `null` element
  550. * 3. Any other object: return the value of `Array.from()` _if_ the object is iterable
  551. * 4. otherwise: return an array with a single element, `value`
  552. * @param {*} value - Something to cast to an Array
  553. * @returns {Array<*>}
  554. */
  555. exports.castArray = function castArray(value) {
  556. if (value === undefined) {
  557. return [];
  558. }
  559. if (value === null) {
  560. return [null];
  561. }
  562. if (
  563. typeof value === 'object' &&
  564. (typeof value[Symbol.iterator] === 'function' || value.length !== undefined)
  565. ) {
  566. return Array.from(value);
  567. }
  568. return [value];
  569. };
  570. exports.constants = exports.defineConstants({
  571. MOCHA_ID_PROP_NAME
  572. });
  573. const uniqueIDBase =
  574. 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_';
  575. /**
  576. * Creates a new unique identifier
  577. * Does not create cryptographically safe ids.
  578. * Trivial copy of nanoid/non-secure
  579. * @returns {string} Unique identifier
  580. */
  581. exports.uniqueID = () => {
  582. let id = '';
  583. for (let i = 0; i < 21; i++) {
  584. id += uniqueIDBase[(Math.random() * 64) | 0];
  585. }
  586. return id;
  587. };
  588. exports.assignNewMochaID = obj => {
  589. const id = exports.uniqueID();
  590. Object.defineProperty(obj, MOCHA_ID_PROP_NAME, {
  591. get() {
  592. return id;
  593. }
  594. });
  595. return obj;
  596. };
  597. /**
  598. * Retrieves a Mocha ID from an object, if present.
  599. * @param {*} [obj] - Object
  600. * @returns {string|void}
  601. */
  602. exports.getMochaID = obj =>
  603. obj && typeof obj === 'object' ? obj[MOCHA_ID_PROP_NAME] : undefined;
  604. /**
  605. * Replaces any detected circular dependency with the string '[Circular]'
  606. * Mutates original object
  607. * @param inputObj {*}
  608. * @returns {*}
  609. */
  610. exports.breakCircularDeps = inputObj => {
  611. const seen = new Set();
  612. function _breakCircularDeps(obj) {
  613. if (obj && typeof obj !== 'object') {
  614. return obj;
  615. }
  616. if (seen.has(obj)) {
  617. return '[Circular]';
  618. }
  619. seen.add(obj);
  620. for (const k in obj) {
  621. const descriptor = Object.getOwnPropertyDescriptor(obj, k);
  622. if (descriptor && descriptor.writable) {
  623. obj[k] = _breakCircularDeps(obj[k], k);
  624. }
  625. }
  626. // deleting means only a seen object that is its own child will be detected
  627. seen.delete(obj);
  628. return obj;
  629. }
  630. return _breakCircularDeps(inputObj);
  631. };
  632. /**
  633. * Checks if provided input can be parsed as a JavaScript Number.
  634. */
  635. exports.isNumeric = input => {
  636. return !isNaN(parseFloat(input));
  637. };