| 'use strict'; |
| |
| /** |
| * 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 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, no-unreachable-loop */ |
| 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]); |
| }; |
| |
| /** |
| * 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)); |
| }; |
| |
| /** |
| * 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); |
| }; |
| |
| /* |
| * 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; |