| 'use strict'; |
| |
| /** |
| * Various utility functions used throughout Mocha's codebase. |
| * @module utils |
| */ |
| |
| /** |
| * Module dependencies. |
| */ |
| |
| var fs = require('fs'); |
| var path = require('path'); |
| var util = require('util'); |
| var glob = require('glob'); |
| var he = require('he'); |
| var errors = require('./errors'); |
| var createNoFilesMatchPatternError = errors.createNoFilesMatchPatternError; |
| var createMissingArgumentError = errors.createMissingArgumentError; |
| |
| var assign = (exports.assign = require('object.assign').getPolyfill()); |
| |
| /** |
| * 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'; |
| }; |
| |
| /** |
| * Watch the given `files` for changes |
| * and invoke `fn(file)` on modification. |
| * |
| * @private |
| * @param {Array} files |
| * @param {Function} fn |
| */ |
| exports.watch = function(files, fn) { |
| var options = {interval: 100}; |
| var debug = require('debug')('mocha:watch'); |
| files.forEach(function(file) { |
| debug('file %s', file); |
| fs.watchFile(file, options, function(curr, prev) { |
| if (prev.mtime < curr.mtime) { |
| fn(file); |
| } |
| }); |
| }); |
| }; |
| |
| /** |
| * Predicate to screen `pathname` for further consideration. |
| * |
| * @description |
| * Returns <code>false</code> for pathname referencing: |
| * <ul> |
| * <li>'npm' package installation directory |
| * <li>'git' version control directory |
| * </ul> |
| * |
| * @private |
| * @param {string} pathname - File or directory name to screen |
| * @return {boolean} whether pathname should be further considered |
| * @example |
| * ['node_modules', 'test.js'].filter(considerFurther); // => ['test.js'] |
| */ |
| function considerFurther(pathname) { |
| var ignore = ['node_modules', '.git']; |
| |
| return !~ignore.indexOf(pathname); |
| } |
| |
| /** |
| * Lookup files in the given `dir`. |
| * |
| * @description |
| * Filenames are returned in _traversal_ order by the OS/filesystem. |
| * **Make no assumption that the names will be sorted in any fashion.** |
| * |
| * @private |
| * @param {string} dir |
| * @param {string[]} [exts=['js']] |
| * @param {Array} [ret=[]] |
| * @return {Array} |
| */ |
| exports.files = function(dir, exts, ret) { |
| ret = ret || []; |
| exts = exts || ['js']; |
| |
| fs.readdirSync(dir) |
| .filter(considerFurther) |
| .forEach(function(dirent) { |
| var pathname = path.join(dir, dirent); |
| if (fs.lstatSync(pathname).isDirectory()) { |
| exports.files(pathname, exts, ret); |
| } else if (hasMatchingExtname(pathname, exts)) { |
| ret.push(pathname); |
| } |
| }); |
| |
| return ret; |
| }; |
| |
| /** |
| * Compute a slug from the given `str`. |
| * |
| * @private |
| * @param {string} str |
| * @return {string} |
| */ |
| exports.slug = function(str) { |
| return str |
| .toLowerCase() |
| .replace(/ +/g, '-') |
| .replace(/[^-\w]/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(); |
| }; |
| |
| /** |
| * Parse the given `qs`. |
| * |
| * @private |
| * @param {string} qs |
| * @return {Object} |
| */ |
| exports.parseQuery = function(qs) { |
| return qs |
| .replace('?', '') |
| .split('&') |
| .reduce(function(obj, pair) { |
| var i = pair.indexOf('='); |
| var key = pair.slice(0, i); |
| var val = pair.slice(++i); |
| |
| // Due to how the URLSearchParams API treats spaces |
| obj[key] = decodeURIComponent(val.replace(/\+/g, '%20')); |
| |
| return obj; |
| }, {}); |
| }; |
| |
| /** |
| * Highlight the given string of `js`. |
| * |
| * @private |
| * @param {string} js |
| * @return {string} |
| */ |
| function highlight(js) { |
| return js |
| .replace(/</g, '<') |
| .replace(/>/g, '>') |
| .replace(/\/\/(.*)/gm, '<span class="comment">//$1</span>') |
| .replace(/('.*?')/gm, '<span class="string">$1</span>') |
| .replace(/(\d+\.\d+)/gm, '<span class="number">$1</span>') |
| .replace(/(\d+)/gm, '<span class="number">$1</span>') |
| .replace( |
| /\bnew[ \t]+(\w+)/gm, |
| '<span class="keyword">new</span> <span class="init">$1</span>' |
| ) |
| .replace( |
| /\b(function|new|throw|return|var|if|else)\b/gm, |
| '<span class="keyword">$1</span>' |
| ); |
| } |
| |
| /** |
| * Highlight the contents of tag `name`. |
| * |
| * @private |
| * @param {string} name |
| */ |
| exports.highlightTags = function(name) { |
| var code = document.getElementById('mocha').getElementsByTagName(name); |
| for (var i = 0, len = code.length; i < len; ++i) { |
| code[i].innerHTML = highlight(code[i].innerHTML); |
| } |
| }; |
| |
| /** |
| * 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 |
| * type({}) // 'object' |
| * type([]) // 'array' |
| * type(1) // 'number' |
| * type(false) // 'boolean' |
| * type(Infinity) // 'number' |
| * type(null) // 'null' |
| * type(new Date()) // 'date' |
| * type(/foo/) // 'regexp' |
| * type('type') // 'string' |
| * type(global) // 'global' |
| * type(new String('foo') // 'object' |
| */ |
| var type = (exports.type = function type(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(); |
| }); |
| |
| /** |
| * 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 = type(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 (type(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 '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 || type(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 guard-for-in */ |
| 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; |
| }; |
| |
| /** |
| * Determines if pathname has a matching file extension. |
| * |
| * @private |
| * @param {string} pathname - Pathname to check for match. |
| * @param {string[]} exts - List of file extensions (sans period). |
| * @return {boolean} whether file extension matches. |
| * @example |
| * hasMatchingExtname('foo.html', ['js', 'css']); // => false |
| */ |
| function hasMatchingExtname(pathname, exts) { |
| var suffix = path.extname(pathname).slice(1); |
| return exts.some(function(element) { |
| return suffix === element; |
| }); |
| } |
| |
| /** |
| * Determines if pathname would be a "hidden" file (or directory) on UN*X. |
| * |
| * @description |
| * On UN*X, pathnames beginning with a full stop (aka dot) are hidden during |
| * typical usage. Dotfiles, plain-text configuration files, are prime examples. |
| * |
| * @see {@link http://xahlee.info/UnixResource_dir/writ/unix_origin_of_dot_filename.html|Origin of Dot File Names} |
| * |
| * @private |
| * @param {string} pathname - Pathname to check for match. |
| * @return {boolean} whether pathname would be considered a hidden file. |
| * @example |
| * isHiddenOnUnix('.profile'); // => true |
| */ |
| function isHiddenOnUnix(pathname) { |
| return path.basename(pathname)[0] === '.'; |
| } |
| |
| /** |
| * 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 |
| * @memberof Mocha.utils |
| * @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. |
| */ |
| exports.lookupFiles = function lookupFiles(filepath, extensions, recursive) { |
| extensions = extensions || []; |
| recursive = recursive || false; |
| var files = []; |
| var stat; |
| |
| if (!fs.existsSync(filepath)) { |
| var pattern; |
| if (glob.hasMagic(filepath)) { |
| // Handle glob as is without extensions |
| pattern = filepath; |
| } else { |
| // glob pattern e.g. 'filepath+(.js|.ts)' |
| var strExtensions = extensions |
| .map(function(v) { |
| return '.' + v; |
| }) |
| .join('|'); |
| pattern = filepath + '+(' + strExtensions + ')'; |
| } |
| files = glob.sync(pattern, {nodir: true}); |
| if (!files.length) { |
| throw createNoFilesMatchPatternError( |
| 'Cannot find any files matching pattern ' + exports.dQuote(filepath), |
| filepath |
| ); |
| } |
| return files; |
| } |
| |
| // Handle file |
| try { |
| stat = fs.statSync(filepath); |
| if (stat.isFile()) { |
| return filepath; |
| } |
| } catch (err) { |
| // ignore error |
| return; |
| } |
| |
| // Handle directory |
| fs.readdirSync(filepath).forEach(function(dirent) { |
| var pathname = path.join(filepath, dirent); |
| var stat; |
| |
| try { |
| stat = fs.statSync(pathname); |
| if (stat.isDirectory()) { |
| if (recursive) { |
| files = files.concat(lookupFiles(pathname, extensions, recursive)); |
| } |
| return; |
| } |
| } catch (err) { |
| // ignore error |
| return; |
| } |
| if (!extensions.length) { |
| throw createMissingArgumentError( |
| util.format( |
| 'Argument %s required when argument %s is a directory', |
| exports.sQuote('extensions'), |
| exports.sQuote('filepath') |
| ), |
| 'extensions', |
| 'array' |
| ); |
| } |
| |
| if ( |
| !stat.isFile() || |
| !hasMatchingExtname(pathname, extensions) || |
| isHiddenOnUnix(pathname) |
| ) { |
| return; |
| } |
| files.push(pathname); |
| }); |
| |
| return files; |
| }; |
| |
| /** |
| * process.emitWarning or a polyfill |
| * @see https://nodejs.org/api/process.html#process_process_emitwarning_warning_options |
| * @ignore |
| */ |
| function emitWarning(msg, type) { |
| if (process.emitWarning) { |
| process.emitWarning(msg, type); |
| } else { |
| process.nextTick(function() { |
| console.warn(type + ': ' + msg); |
| }); |
| } |
| } |
| |
| /** |
| * Show a deprecation warning. Each distinct message is only displayed once. |
| * Ignores empty messages. |
| * |
| * @param {string} [msg] - Warning to print |
| * @private |
| */ |
| exports.deprecate = function deprecate(msg) { |
| msg = String(msg); |
| if (msg && !deprecate.cache[msg]) { |
| deprecate.cache[msg] = true; |
| emitWarning(msg, 'DeprecationWarning'); |
| } |
| }; |
| exports.deprecate.cache = {}; |
| |
| /** |
| * Show a generic warning. |
| * Ignores empty messages. |
| * |
| * @param {string} [msg] - Warning to print |
| * @private |
| */ |
| exports.warn = function warn(msg) { |
| if (msg) { |
| emitWarning(msg); |
| } |
| }; |
| |
| /** |
| * @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 = process.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 {numer[]} 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 + '"'; |
| }; |
| |
| /** |
| * Provides simplistic message translation for dealing with plurality. |
| * |
| * @description |
| * Use this to create messages which need to be singular or plural. |
| * Some languages have several plural forms, so _complete_ message clauses |
| * are preferable to generating the message on the fly. |
| * |
| * @private |
| * @param {number} n - Non-negative integer |
| * @param {string} msg1 - Message to be used in English for `n = 1` |
| * @param {string} msg2 - Message to be used in English for `n = 0, 2, 3, ...` |
| * @returns {string} message corresponding to value of `n` |
| * @example |
| * var sprintf = require('util').format; |
| * var pkgs = ['one', 'two']; |
| * var msg = sprintf( |
| * ngettext( |
| * pkgs.length, |
| * 'cannot load package: %s', |
| * 'cannot load packages: %s' |
| * ), |
| * pkgs.map(sQuote).join(', ') |
| * ); |
| * console.log(msg); // => cannot load packages: 'one', 'two' |
| */ |
| exports.ngettext = function(n, msg1, msg2) { |
| if (typeof n === 'number' && n >= 0) { |
| return n === 1 ? msg1 : msg2; |
| } |
| }; |
| |
| /** |
| * 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|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|MDN:Object.assign} |
| * @param {...*} [obj] - Arguments to `Object.assign()`. |
| * @returns {Object} An object with no prototype, having `...obj` properties |
| */ |
| exports.createMap = function(obj) { |
| return 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 (type(obj) !== 'object' || !Object.keys(obj).length) { |
| throw new TypeError('Invalid argument; expected a non-empty object'); |
| } |
| return Object.freeze(exports.createMap(obj)); |
| }; |