Spaces:
Paused
Paused
| ; | |
| /** | |
| * Various utility functions used throughout Mocha's codebase. | |
| * @module utils | |
| */ | |
| /** | |
| * Module dependencies. | |
| */ | |
| const {nanoid} = require('nanoid/non-secure'); | |
| var path = require('path'); | |
| var util = require('util'); | |
| var he = require('he'); | |
| const errors = require('./errors'); | |
| const MOCHA_ID_PROP_NAME = '__mocha_id__'; | |
| /** | |
| * Inherit the prototype methods from one constructor into another. | |
| * | |
| * @param {function} ctor - Constructor function which needs to inherit the | |
| * prototype. | |
| * @param {function} superCtor - Constructor function to inherit prototype from. | |
| * @throws {TypeError} if either constructor is null, or if super constructor | |
| * lacks a prototype. | |
| */ | |
| exports.inherits = util.inherits; | |
| /** | |
| * Escape special characters in the given string of html. | |
| * | |
| * @private | |
| * @param {string} html | |
| * @return {string} | |
| */ | |
| exports.escape = function(html) { | |
| return he.encode(String(html), {useNamedReferences: false}); | |
| }; | |
| /** | |
| * Test if the given obj is type of string. | |
| * | |
| * @private | |
| * @param {Object} obj | |
| * @return {boolean} | |
| */ | |
| exports.isString = function(obj) { | |
| return typeof obj === 'string'; | |
| }; | |
| /** | |
| * Compute a slug from the given `str`. | |
| * | |
| * @private | |
| * @param {string} str | |
| * @return {string} | |
| */ | |
| exports.slug = function(str) { | |
| return str | |
| .toLowerCase() | |
| .replace(/\s+/g, '-') | |
| .replace(/[^-\w]/g, '') | |
| .replace(/-{2,}/g, '-'); | |
| }; | |
| /** | |
| * Strip the function definition from `str`, and re-indent for pre whitespace. | |
| * | |
| * @param {string} str | |
| * @return {string} | |
| */ | |
| exports.clean = function(str) { | |
| str = str | |
| .replace(/\r\n?|[\n\u2028\u2029]/g, '\n') | |
| .replace(/^\uFEFF/, '') | |
| // (traditional)-> space/name parameters body (lambda)-> parameters body multi-statement/single keep body content | |
| .replace( | |
| /^function(?:\s*|\s+[^(]*)\([^)]*\)\s*\{((?:.|\n)*?)\s*\}$|^\([^)]*\)\s*=>\s*(?:\{((?:.|\n)*?)\s*\}|((?:.|\n)*))$/, | |
| '$1$2$3' | |
| ); | |
| var spaces = str.match(/^\n?( *)/)[1].length; | |
| var tabs = str.match(/^\n?(\t*)/)[1].length; | |
| var re = new RegExp( | |
| '^\n?' + (tabs ? '\t' : ' ') + '{' + (tabs || spaces) + '}', | |
| 'gm' | |
| ); | |
| str = str.replace(re, ''); | |
| return str.trim(); | |
| }; | |
| /** | |
| * If a value could have properties, and has none, this function is called, | |
| * which returns a string representation of the empty value. | |
| * | |
| * Functions w/ no properties return `'[Function]'` | |
| * Arrays w/ length === 0 return `'[]'` | |
| * Objects w/ no properties return `'{}'` | |
| * All else: return result of `value.toString()` | |
| * | |
| * @private | |
| * @param {*} value The value to inspect. | |
| * @param {string} typeHint The type of the value | |
| * @returns {string} | |
| */ | |
| function emptyRepresentation(value, typeHint) { | |
| switch (typeHint) { | |
| case 'function': | |
| return '[Function]'; | |
| case 'object': | |
| return '{}'; | |
| case 'array': | |
| return '[]'; | |
| default: | |
| return value.toString(); | |
| } | |
| } | |
| /** | |
| * Takes some variable and asks `Object.prototype.toString()` what it thinks it | |
| * is. | |
| * | |
| * @private | |
| * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString | |
| * @param {*} value The value to test. | |
| * @returns {string} Computed type | |
| * @example | |
| * canonicalType({}) // 'object' | |
| * canonicalType([]) // 'array' | |
| * canonicalType(1) // 'number' | |
| * canonicalType(false) // 'boolean' | |
| * canonicalType(Infinity) // 'number' | |
| * canonicalType(null) // 'null' | |
| * canonicalType(new Date()) // 'date' | |
| * canonicalType(/foo/) // 'regexp' | |
| * canonicalType('type') // 'string' | |
| * canonicalType(global) // 'global' | |
| * canonicalType(new String('foo') // 'object' | |
| * canonicalType(async function() {}) // 'asyncfunction' | |
| * canonicalType(await import(name)) // 'module' | |
| */ | |
| var canonicalType = (exports.canonicalType = function canonicalType(value) { | |
| if (value === undefined) { | |
| return 'undefined'; | |
| } else if (value === null) { | |
| return 'null'; | |
| } else if (Buffer.isBuffer(value)) { | |
| return 'buffer'; | |
| } | |
| return Object.prototype.toString | |
| .call(value) | |
| .replace(/^\[.+\s(.+?)]$/, '$1') | |
| .toLowerCase(); | |
| }); | |
| /** | |
| * | |
| * Returns a general type or data structure of a variable | |
| * @private | |
| * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures | |
| * @param {*} value The value to test. | |
| * @returns {string} One of undefined, boolean, number, string, bigint, symbol, object | |
| * @example | |
| * type({}) // 'object' | |
| * type([]) // 'array' | |
| * type(1) // 'number' | |
| * type(false) // 'boolean' | |
| * type(Infinity) // 'number' | |
| * type(null) // 'null' | |
| * type(new Date()) // 'object' | |
| * type(/foo/) // 'object' | |
| * type('type') // 'string' | |
| * type(global) // 'object' | |
| * type(new String('foo') // 'string' | |
| */ | |
| exports.type = function type(value) { | |
| // Null is special | |
| if (value === null) return 'null'; | |
| const primitives = new Set([ | |
| 'undefined', | |
| 'boolean', | |
| 'number', | |
| 'string', | |
| 'bigint', | |
| 'symbol' | |
| ]); | |
| const _type = typeof value; | |
| if (_type === 'function') return _type; | |
| if (primitives.has(_type)) return _type; | |
| if (value instanceof String) return 'string'; | |
| if (value instanceof Error) return 'error'; | |
| if (Array.isArray(value)) return 'array'; | |
| return _type; | |
| }; | |
| /** | |
| * Stringify `value`. Different behavior depending on type of value: | |
| * | |
| * - If `value` is undefined or null, return `'[undefined]'` or `'[null]'`, respectively. | |
| * - If `value` is not an object, function or array, return result of `value.toString()` wrapped in double-quotes. | |
| * - If `value` is an *empty* object, function, or array, return result of function | |
| * {@link emptyRepresentation}. | |
| * - If `value` has properties, call {@link exports.canonicalize} on it, then return result of | |
| * JSON.stringify(). | |
| * | |
| * @private | |
| * @see exports.type | |
| * @param {*} value | |
| * @return {string} | |
| */ | |
| exports.stringify = function(value) { | |
| var typeHint = canonicalType(value); | |
| if (!~['object', 'array', 'function'].indexOf(typeHint)) { | |
| if (typeHint === 'buffer') { | |
| var json = Buffer.prototype.toJSON.call(value); | |
| // Based on the toJSON result | |
| return jsonStringify( | |
| json.data && json.type ? json.data : json, | |
| 2 | |
| ).replace(/,(\n|$)/g, '$1'); | |
| } | |
| // IE7/IE8 has a bizarre String constructor; needs to be coerced | |
| // into an array and back to obj. | |
| if (typeHint === 'string' && typeof value === 'object') { | |
| value = value.split('').reduce(function(acc, char, idx) { | |
| acc[idx] = char; | |
| return acc; | |
| }, {}); | |
| typeHint = 'object'; | |
| } else { | |
| return jsonStringify(value); | |
| } | |
| } | |
| for (var prop in value) { | |
| if (Object.prototype.hasOwnProperty.call(value, prop)) { | |
| return jsonStringify( | |
| exports.canonicalize(value, null, typeHint), | |
| 2 | |
| ).replace(/,(\n|$)/g, '$1'); | |
| } | |
| } | |
| return emptyRepresentation(value, typeHint); | |
| }; | |
| /** | |
| * like JSON.stringify but more sense. | |
| * | |
| * @private | |
| * @param {Object} object | |
| * @param {number=} spaces | |
| * @param {number=} depth | |
| * @returns {*} | |
| */ | |
| function jsonStringify(object, spaces, depth) { | |
| if (typeof spaces === 'undefined') { | |
| // primitive types | |
| return _stringify(object); | |
| } | |
| depth = depth || 1; | |
| var space = spaces * depth; | |
| var str = Array.isArray(object) ? '[' : '{'; | |
| var end = Array.isArray(object) ? ']' : '}'; | |
| var length = | |
| typeof object.length === 'number' | |
| ? object.length | |
| : Object.keys(object).length; | |
| // `.repeat()` polyfill | |
| function repeat(s, n) { | |
| return new Array(n).join(s); | |
| } | |
| function _stringify(val) { | |
| switch (canonicalType(val)) { | |
| case 'null': | |
| case 'undefined': | |
| val = '[' + val + ']'; | |
| break; | |
| case 'array': | |
| case 'object': | |
| val = jsonStringify(val, spaces, depth + 1); | |
| break; | |
| case 'boolean': | |
| case 'regexp': | |
| case 'symbol': | |
| case 'number': | |
| val = | |
| val === 0 && 1 / val === -Infinity // `-0` | |
| ? '-0' | |
| : val.toString(); | |
| break; | |
| case 'bigint': | |
| val = val.toString() + 'n'; | |
| break; | |
| case 'date': | |
| var sDate = isNaN(val.getTime()) ? val.toString() : val.toISOString(); | |
| val = '[Date: ' + sDate + ']'; | |
| break; | |
| case 'buffer': | |
| var json = val.toJSON(); | |
| // Based on the toJSON result | |
| json = json.data && json.type ? json.data : json; | |
| val = '[Buffer: ' + jsonStringify(json, 2, depth + 1) + ']'; | |
| break; | |
| default: | |
| val = | |
| val === '[Function]' || val === '[Circular]' | |
| ? val | |
| : JSON.stringify(val); // string | |
| } | |
| return val; | |
| } | |
| for (var i in object) { | |
| if (!Object.prototype.hasOwnProperty.call(object, i)) { | |
| continue; // not my business | |
| } | |
| --length; | |
| str += | |
| '\n ' + | |
| repeat(' ', space) + | |
| (Array.isArray(object) ? '' : '"' + i + '": ') + // key | |
| _stringify(object[i]) + // value | |
| (length ? ',' : ''); // comma | |
| } | |
| return ( | |
| str + | |
| // [], {} | |
| (str.length !== 1 ? '\n' + repeat(' ', --space) + end : end) | |
| ); | |
| } | |
| /** | |
| * Return a new Thing that has the keys in sorted order. Recursive. | |
| * | |
| * If the Thing... | |
| * - has already been seen, return string `'[Circular]'` | |
| * - is `undefined`, return string `'[undefined]'` | |
| * - is `null`, return value `null` | |
| * - is some other primitive, return the value | |
| * - is not a primitive or an `Array`, `Object`, or `Function`, return the value of the Thing's `toString()` method | |
| * - is a non-empty `Array`, `Object`, or `Function`, return the result of calling this function again. | |
| * - is an empty `Array`, `Object`, or `Function`, return the result of calling `emptyRepresentation()` | |
| * | |
| * @private | |
| * @see {@link exports.stringify} | |
| * @param {*} value Thing to inspect. May or may not have properties. | |
| * @param {Array} [stack=[]] Stack of seen values | |
| * @param {string} [typeHint] Type hint | |
| * @return {(Object|Array|Function|string|undefined)} | |
| */ | |
| exports.canonicalize = function canonicalize(value, stack, typeHint) { | |
| var canonicalizedObj; | |
| /* eslint-disable no-unused-vars */ | |
| var prop; | |
| /* eslint-enable no-unused-vars */ | |
| typeHint = typeHint || canonicalType(value); | |
| function withStack(value, fn) { | |
| stack.push(value); | |
| fn(); | |
| stack.pop(); | |
| } | |
| stack = stack || []; | |
| if (stack.indexOf(value) !== -1) { | |
| return '[Circular]'; | |
| } | |
| switch (typeHint) { | |
| case 'undefined': | |
| case 'buffer': | |
| case 'null': | |
| canonicalizedObj = value; | |
| break; | |
| case 'array': | |
| withStack(value, function() { | |
| canonicalizedObj = value.map(function(item) { | |
| return exports.canonicalize(item, stack); | |
| }); | |
| }); | |
| break; | |
| case 'function': | |
| /* eslint-disable-next-line no-unused-vars */ | |
| for (prop in value) { | |
| canonicalizedObj = {}; | |
| break; | |
| } | |
| /* eslint-enable guard-for-in */ | |
| if (!canonicalizedObj) { | |
| canonicalizedObj = emptyRepresentation(value, typeHint); | |
| break; | |
| } | |
| /* falls through */ | |
| case 'object': | |
| canonicalizedObj = canonicalizedObj || {}; | |
| withStack(value, function() { | |
| Object.keys(value) | |
| .sort() | |
| .forEach(function(key) { | |
| canonicalizedObj[key] = exports.canonicalize(value[key], stack); | |
| }); | |
| }); | |
| break; | |
| case 'date': | |
| case 'number': | |
| case 'regexp': | |
| case 'boolean': | |
| case 'symbol': | |
| canonicalizedObj = value; | |
| break; | |
| default: | |
| canonicalizedObj = value + ''; | |
| } | |
| return canonicalizedObj; | |
| }; | |
| /** | |
| * @summary | |
| * This Filter based on `mocha-clean` module.(see: `github.com/rstacruz/mocha-clean`) | |
| * @description | |
| * When invoking this function you get a filter function that get the Error.stack as an input, | |
| * and return a prettify output. | |
| * (i.e: strip Mocha and internal node functions from stack trace). | |
| * @returns {Function} | |
| */ | |
| exports.stackTraceFilter = function() { | |
| // TODO: Replace with `process.browser` | |
| var is = typeof document === 'undefined' ? {node: true} : {browser: true}; | |
| var slash = path.sep; | |
| var cwd; | |
| if (is.node) { | |
| cwd = exports.cwd() + slash; | |
| } else { | |
| cwd = (typeof location === 'undefined' | |
| ? window.location | |
| : location | |
| ).href.replace(/\/[^/]*$/, '/'); | |
| slash = '/'; | |
| } | |
| function isMochaInternal(line) { | |
| return ( | |
| ~line.indexOf('node_modules' + slash + 'mocha' + slash) || | |
| ~line.indexOf(slash + 'mocha.js') || | |
| ~line.indexOf(slash + 'mocha.min.js') | |
| ); | |
| } | |
| function isNodeInternal(line) { | |
| return ( | |
| ~line.indexOf('(timers.js:') || | |
| ~line.indexOf('(events.js:') || | |
| ~line.indexOf('(node.js:') || | |
| ~line.indexOf('(module.js:') || | |
| ~line.indexOf('GeneratorFunctionPrototype.next (native)') || | |
| false | |
| ); | |
| } | |
| return function(stack) { | |
| stack = stack.split('\n'); | |
| stack = stack.reduce(function(list, line) { | |
| if (isMochaInternal(line)) { | |
| return list; | |
| } | |
| if (is.node && isNodeInternal(line)) { | |
| return list; | |
| } | |
| // Clean up cwd(absolute) | |
| if (/:\d+:\d+\)?$/.test(line)) { | |
| line = line.replace('(' + cwd, '('); | |
| } | |
| list.push(line); | |
| return list; | |
| }, []); | |
| return stack.join('\n'); | |
| }; | |
| }; | |
| /** | |
| * Crude, but effective. | |
| * @public | |
| * @param {*} value | |
| * @returns {boolean} Whether or not `value` is a Promise | |
| */ | |
| exports.isPromise = function isPromise(value) { | |
| return ( | |
| typeof value === 'object' && | |
| value !== null && | |
| typeof value.then === 'function' | |
| ); | |
| }; | |
| /** | |
| * Clamps a numeric value to an inclusive range. | |
| * | |
| * @param {number} value - Value to be clamped. | |
| * @param {number[]} range - Two element array specifying [min, max] range. | |
| * @returns {number} clamped value | |
| */ | |
| exports.clamp = function clamp(value, range) { | |
| return Math.min(Math.max(value, range[0]), range[1]); | |
| }; | |
| /** | |
| * Single quote text by combining with undirectional ASCII quotation marks. | |
| * | |
| * @description | |
| * Provides a simple means of markup for quoting text to be used in output. | |
| * Use this to quote names of variables, methods, and packages. | |
| * | |
| * <samp>package 'foo' cannot be found</samp> | |
| * | |
| * @private | |
| * @param {string} str - Value to be quoted. | |
| * @returns {string} quoted value | |
| * @example | |
| * sQuote('n') // => 'n' | |
| */ | |
| exports.sQuote = function(str) { | |
| return "'" + str + "'"; | |
| }; | |
| /** | |
| * Double quote text by combining with undirectional ASCII quotation marks. | |
| * | |
| * @description | |
| * Provides a simple means of markup for quoting text to be used in output. | |
| * Use this to quote names of datatypes, classes, pathnames, and strings. | |
| * | |
| * <samp>argument 'value' must be "string" or "number"</samp> | |
| * | |
| * @private | |
| * @param {string} str - Value to be quoted. | |
| * @returns {string} quoted value | |
| * @example | |
| * dQuote('number') // => "number" | |
| */ | |
| exports.dQuote = function(str) { | |
| return '"' + str + '"'; | |
| }; | |
| /** | |
| * It's a noop. | |
| * @public | |
| */ | |
| exports.noop = function() {}; | |
| /** | |
| * Creates a map-like object. | |
| * | |
| * @description | |
| * A "map" is an object with no prototype, for our purposes. In some cases | |
| * this would be more appropriate than a `Map`, especially if your environment | |
| * doesn't support it. Recommended for use in Mocha's public APIs. | |
| * | |
| * @public | |
| * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map#Custom_and_Null_objects|MDN:Map} | |
| * @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} | |
| * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#Custom_and_Null_objects|MDN:Object.assign} | |
| * @param {...*} [obj] - Arguments to `Object.assign()`. | |
| * @returns {Object} An object with no prototype, having `...obj` properties | |
| */ | |
| exports.createMap = function(obj) { | |
| return Object.assign.apply( | |
| null, | |
| [Object.create(null)].concat(Array.prototype.slice.call(arguments)) | |
| ); | |
| }; | |
| /** | |
| * Creates a read-only map-like object. | |
| * | |
| * @description | |
| * This differs from {@link module:utils.createMap createMap} only in that | |
| * the argument must be non-empty, because the result is frozen. | |
| * | |
| * @see {@link module:utils.createMap createMap} | |
| * @param {...*} [obj] - Arguments to `Object.assign()`. | |
| * @returns {Object} A frozen object with no prototype, having `...obj` properties | |
| * @throws {TypeError} if argument is not a non-empty object. | |
| */ | |
| exports.defineConstants = function(obj) { | |
| if (canonicalType(obj) !== 'object' || !Object.keys(obj).length) { | |
| throw new TypeError('Invalid argument; expected a non-empty object'); | |
| } | |
| return Object.freeze(exports.createMap(obj)); | |
| }; | |
| /** | |
| * Whether current version of Node support ES modules | |
| * | |
| * @description | |
| * Versions prior to 10 did not support ES Modules, and version 10 has an old incompatible version of ESM. | |
| * This function returns whether Node.JS has ES Module supports that is compatible with Mocha's needs, | |
| * which is version >=12.11. | |
| * | |
| * @param {partialSupport} whether the full Node.js ESM support is available (>= 12) or just something that supports the runtime (>= 10) | |
| * | |
| * @returns {Boolean} whether the current version of Node.JS supports ES Modules in a way that is compatible with Mocha | |
| */ | |
| exports.supportsEsModules = function(partialSupport) { | |
| if (!exports.isBrowser() && process.versions && process.versions.node) { | |
| var versionFields = process.versions.node.split('.'); | |
| var major = +versionFields[0]; | |
| var minor = +versionFields[1]; | |
| if (!partialSupport) { | |
| return major >= 13 || (major === 12 && minor >= 11); | |
| } else { | |
| return major >= 10; | |
| } | |
| } | |
| }; | |
| /** | |
| * Returns current working directory | |
| * | |
| * Wrapper around `process.cwd()` for isolation | |
| * @private | |
| */ | |
| exports.cwd = function cwd() { | |
| return process.cwd(); | |
| }; | |
| /** | |
| * Returns `true` if Mocha is running in a browser. | |
| * Checks for `process.browser`. | |
| * @returns {boolean} | |
| * @private | |
| */ | |
| exports.isBrowser = function isBrowser() { | |
| return Boolean(process.browser); | |
| }; | |
| /** | |
| * Lookup file names at the given `path`. | |
| * | |
| * @description | |
| * Filenames are returned in _traversal_ order by the OS/filesystem. | |
| * **Make no assumption that the names will be sorted in any fashion.** | |
| * | |
| * @public | |
| * @alias module:lib/cli.lookupFiles | |
| * @param {string} filepath - Base path to start searching from. | |
| * @param {string[]} [extensions=[]] - File extensions to look for. | |
| * @param {boolean} [recursive=false] - Whether to recurse into subdirectories. | |
| * @return {string[]} An array of paths. | |
| * @throws {Error} if no files match pattern. | |
| * @throws {TypeError} if `filepath` is directory and `extensions` not provided. | |
| * @deprecated Moved to {@link module:lib/cli.lookupFiles} | |
| */ | |
| exports.lookupFiles = (...args) => { | |
| if (exports.isBrowser()) { | |
| throw errors.createUnsupportedError( | |
| 'lookupFiles() is only supported in Node.js!' | |
| ); | |
| } | |
| errors.deprecate( | |
| '`lookupFiles()` in module `mocha/lib/utils` has moved to module `mocha/lib/cli` and will be removed in the next major revision of Mocha' | |
| ); | |
| return require('./cli').lookupFiles(...args); | |
| }; | |
| /* | |
| * Casts `value` to an array; useful for optionally accepting array parameters | |
| * | |
| * It follows these rules, depending on `value`. If `value` is... | |
| * 1. `undefined`: return an empty Array | |
| * 2. `null`: return an array with a single `null` element | |
| * 3. Any other object: return the value of `Array.from()` _if_ the object is iterable | |
| * 4. otherwise: return an array with a single element, `value` | |
| * @param {*} value - Something to cast to an Array | |
| * @returns {Array<*>} | |
| */ | |
| exports.castArray = function castArray(value) { | |
| if (value === undefined) { | |
| return []; | |
| } | |
| if (value === null) { | |
| return [null]; | |
| } | |
| if ( | |
| typeof value === 'object' && | |
| (typeof value[Symbol.iterator] === 'function' || value.length !== undefined) | |
| ) { | |
| return Array.from(value); | |
| } | |
| return [value]; | |
| }; | |
| exports.constants = exports.defineConstants({ | |
| MOCHA_ID_PROP_NAME | |
| }); | |
| /** | |
| * Creates a new unique identifier | |
| * @returns {string} Unique identifier | |
| */ | |
| exports.uniqueID = () => nanoid(); | |
| exports.assignNewMochaID = obj => { | |
| const id = exports.uniqueID(); | |
| Object.defineProperty(obj, MOCHA_ID_PROP_NAME, { | |
| get() { | |
| return id; | |
| } | |
| }); | |
| return obj; | |
| }; | |
| /** | |
| * Retrieves a Mocha ID from an object, if present. | |
| * @param {*} [obj] - Object | |
| * @returns {string|void} | |
| */ | |
| exports.getMochaID = obj => | |
| obj && typeof obj === 'object' ? obj[MOCHA_ID_PROP_NAME] : undefined; | |