'use strict'; const {format} = require('util'); /** * Contains error codes, factory functions to create throwable error objects, * and warning/deprecation functions. * @module */ /** * process.emitWarning or a polyfill * @see https://nodejs.org/api/process.html#process_process_emitwarning_warning_options * @ignore */ const emitWarning = (msg, type) => { if (process.emitWarning) { process.emitWarning(msg, type); } else { /* istanbul ignore next */ 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 */ const deprecate = msg => { msg = String(msg); if (msg && !deprecate.cache[msg]) { deprecate.cache[msg] = true; emitWarning(msg, 'DeprecationWarning'); } }; deprecate.cache = {}; /** * Show a generic warning. * Ignores empty messages. * * @param {string} [msg] - Warning to print * @private */ const warn = msg => { if (msg) { emitWarning(msg); } }; /** * When Mocha throws exceptions (or rejects `Promise`s), it attempts to assign a `code` property to the `Error` object, for easier handling. These are the potential values of `code`. * @public * @namespace * @memberof module:lib/errors */ var constants = { /** * An unrecoverable error. * @constant * @default */ FATAL: 'ERR_MOCHA_FATAL', /** * The type of an argument to a function call is invalid * @constant * @default */ INVALID_ARG_TYPE: 'ERR_MOCHA_INVALID_ARG_TYPE', /** * The value of an argument to a function call is invalid * @constant * @default */ INVALID_ARG_VALUE: 'ERR_MOCHA_INVALID_ARG_VALUE', /** * Something was thrown, but it wasn't an `Error` * @constant * @default */ INVALID_EXCEPTION: 'ERR_MOCHA_INVALID_EXCEPTION', /** * An interface (e.g., `Mocha.interfaces`) is unknown or invalid * @constant * @default */ INVALID_INTERFACE: 'ERR_MOCHA_INVALID_INTERFACE', /** * A reporter (.e.g, `Mocha.reporters`) is unknown or invalid * @constant * @default */ INVALID_REPORTER: 'ERR_MOCHA_INVALID_REPORTER', /** * `done()` was called twice in a `Test` or `Hook` callback * @constant * @default */ MULTIPLE_DONE: 'ERR_MOCHA_MULTIPLE_DONE', /** * No files matched the pattern provided by the user * @constant * @default */ NO_FILES_MATCH_PATTERN: 'ERR_MOCHA_NO_FILES_MATCH_PATTERN', /** * Known, but unsupported behavior of some kind * @constant * @default */ UNSUPPORTED: 'ERR_MOCHA_UNSUPPORTED', /** * Invalid state transition occurring in `Mocha` instance * @constant * @default */ INSTANCE_ALREADY_RUNNING: 'ERR_MOCHA_INSTANCE_ALREADY_RUNNING', /** * Invalid state transition occurring in `Mocha` instance * @constant * @default */ INSTANCE_ALREADY_DISPOSED: 'ERR_MOCHA_INSTANCE_ALREADY_DISPOSED', /** * Use of `only()` w/ `--forbid-only` results in this error. * @constant * @default */ FORBIDDEN_EXCLUSIVITY: 'ERR_MOCHA_FORBIDDEN_EXCLUSIVITY', /** * To be thrown when a user-defined plugin implementation (e.g., `mochaHooks`) is invalid * @constant * @default */ INVALID_PLUGIN_IMPLEMENTATION: 'ERR_MOCHA_INVALID_PLUGIN_IMPLEMENTATION', /** * To be thrown when a builtin or third-party plugin definition (the _definition_ of `mochaHooks`) is invalid * @constant * @default */ INVALID_PLUGIN_DEFINITION: 'ERR_MOCHA_INVALID_PLUGIN_DEFINITION', /** * When a runnable exceeds its allowed run time. * @constant * @default */ TIMEOUT: 'ERR_MOCHA_TIMEOUT', /** * Input file is not able to be parsed * @constant * @default */ UNPARSABLE_FILE: 'ERR_MOCHA_UNPARSABLE_FILE' }; /** * A set containing all string values of all Mocha error constants, for use by {@link isMochaError}. * @private */ const MOCHA_ERRORS = new Set(Object.values(constants)); /** * Creates an error object to be thrown when no files to be tested could be found using specified pattern. * * @public * @static * @param {string} message - Error message to be displayed. * @param {string} pattern - User-specified argument value. * @returns {Error} instance detailing the error condition */ function createNoFilesMatchPatternError(message, pattern) { var err = new Error(message); err.code = constants.NO_FILES_MATCH_PATTERN; err.pattern = pattern; return err; } /** * Creates an error object to be thrown when the reporter specified in the options was not found. * * @public * @param {string} message - Error message to be displayed. * @param {string} reporter - User-specified reporter value. * @returns {Error} instance detailing the error condition */ function createInvalidReporterError(message, reporter) { var err = new TypeError(message); err.code = constants.INVALID_REPORTER; err.reporter = reporter; return err; } /** * Creates an error object to be thrown when the interface specified in the options was not found. * * @public * @static * @param {string} message - Error message to be displayed. * @param {string} ui - User-specified interface value. * @returns {Error} instance detailing the error condition */ function createInvalidInterfaceError(message, ui) { var err = new Error(message); err.code = constants.INVALID_INTERFACE; err.interface = ui; return err; } /** * Creates an error object to be thrown when a behavior, option, or parameter is unsupported. * * @public * @static * @param {string} message - Error message to be displayed. * @returns {Error} instance detailing the error condition */ function createUnsupportedError(message) { var err = new Error(message); err.code = constants.UNSUPPORTED; return err; } /** * Creates an error object to be thrown when an argument is missing. * * @public * @static * @param {string} message - Error message to be displayed. * @param {string} argument - Argument name. * @param {string} expected - Expected argument datatype. * @returns {Error} instance detailing the error condition */ function createMissingArgumentError(message, argument, expected) { return createInvalidArgumentTypeError(message, argument, expected); } /** * Creates an error object to be thrown when an argument did not use the supported type * * @public * @static * @param {string} message - Error message to be displayed. * @param {string} argument - Argument name. * @param {string} expected - Expected argument datatype. * @returns {Error} instance detailing the error condition */ function createInvalidArgumentTypeError(message, argument, expected) { var err = new TypeError(message); err.code = constants.INVALID_ARG_TYPE; err.argument = argument; err.expected = expected; err.actual = typeof argument; return err; } /** * Creates an error object to be thrown when an argument did not use the supported value * * @public * @static * @param {string} message - Error message to be displayed. * @param {string} argument - Argument name. * @param {string} value - Argument value. * @param {string} [reason] - Why value is invalid. * @returns {Error} instance detailing the error condition */ function createInvalidArgumentValueError(message, argument, value, reason) { var err = new TypeError(message); err.code = constants.INVALID_ARG_VALUE; err.argument = argument; err.value = value; err.reason = typeof reason !== 'undefined' ? reason : 'is invalid'; return err; } /** * Creates an error object to be thrown when an exception was caught, but the `Error` is falsy or undefined. * * @public * @static * @param {string} message - Error message to be displayed. * @returns {Error} instance detailing the error condition */ function createInvalidExceptionError(message, value) { var err = new Error(message); err.code = constants.INVALID_EXCEPTION; err.valueType = typeof value; err.value = value; return err; } /** * Creates an error object to be thrown when an unrecoverable error occurs. * * @public * @static * @param {string} message - Error message to be displayed. * @returns {Error} instance detailing the error condition */ function createFatalError(message, value) { var err = new Error(message); err.code = constants.FATAL; err.valueType = typeof value; err.value = value; return err; } /** * Dynamically creates a plugin-type-specific error based on plugin type * @param {string} message - Error message * @param {"reporter"|"interface"} pluginType - Plugin type. Future: expand as needed * @param {string} [pluginId] - Name/path of plugin, if any * @throws When `pluginType` is not known * @public * @static * @returns {Error} */ function createInvalidLegacyPluginError(message, pluginType, pluginId) { switch (pluginType) { case 'reporter': return createInvalidReporterError(message, pluginId); case 'interface': return createInvalidInterfaceError(message, pluginId); default: throw new Error('unknown pluginType "' + pluginType + '"'); } } /** * **DEPRECATED**. Use {@link createInvalidLegacyPluginError} instead Dynamically creates a plugin-type-specific error based on plugin type * @deprecated * @param {string} message - Error message * @param {"reporter"|"interface"} pluginType - Plugin type. Future: expand as needed * @param {string} [pluginId] - Name/path of plugin, if any * @throws When `pluginType` is not known * @public * @static * @returns {Error} */ function createInvalidPluginError(...args) { deprecate('Use createInvalidLegacyPluginError() instead'); return createInvalidLegacyPluginError(...args); } /** * Creates an error object to be thrown when a mocha object's `run` method is executed while it is already disposed. * @param {string} message The error message to be displayed. * @param {boolean} cleanReferencesAfterRun the value of `cleanReferencesAfterRun` * @param {Mocha} instance the mocha instance that throw this error * @static */ function createMochaInstanceAlreadyDisposedError( message, cleanReferencesAfterRun, instance ) { var err = new Error(message); err.code = constants.INSTANCE_ALREADY_DISPOSED; err.cleanReferencesAfterRun = cleanReferencesAfterRun; err.instance = instance; return err; } /** * Creates an error object to be thrown when a mocha object's `run` method is called while a test run is in progress. * @param {string} message The error message to be displayed. * @static * @public */ function createMochaInstanceAlreadyRunningError(message, instance) { var err = new Error(message); err.code = constants.INSTANCE_ALREADY_RUNNING; err.instance = instance; return err; } /** * Creates an error object to be thrown when done() is called multiple times in a test * * @public * @param {Runnable} runnable - Original runnable * @param {Error} [originalErr] - Original error, if any * @returns {Error} instance detailing the error condition * @static */ function createMultipleDoneError(runnable, originalErr) { var title; try { title = format('<%s>', runnable.fullTitle()); if (runnable.parent.root) { title += ' (of root suite)'; } } catch (ignored) { title = format('<%s> (of unknown suite)', runnable.title); } var message = format( 'done() called multiple times in %s %s', runnable.type ? runnable.type : 'unknown runnable', title ); if (runnable.file) { message += format(' of file %s', runnable.file); } if (originalErr) { message += format('; in addition, done() received error: %s', originalErr); } var err = new Error(message); err.code = constants.MULTIPLE_DONE; err.valueType = typeof originalErr; err.value = originalErr; return err; } /** * Creates an error object to be thrown when `.only()` is used with * `--forbid-only`. * @static * @public * @param {Mocha} mocha - Mocha instance * @returns {Error} Error with code {@link constants.FORBIDDEN_EXCLUSIVITY} */ function createForbiddenExclusivityError(mocha) { var err = new Error( mocha.isWorker ? '`.only` is not supported in parallel mode' : '`.only` forbidden by --forbid-only' ); err.code = constants.FORBIDDEN_EXCLUSIVITY; return err; } /** * Creates an error object to be thrown when a plugin definition is invalid * @static * @param {string} msg - Error message * @param {PluginDefinition} [pluginDef] - Problematic plugin definition * @public * @returns {Error} Error with code {@link constants.INVALID_PLUGIN_DEFINITION} */ function createInvalidPluginDefinitionError(msg, pluginDef) { const err = new Error(msg); err.code = constants.INVALID_PLUGIN_DEFINITION; err.pluginDef = pluginDef; return err; } /** * Creates an error object to be thrown when a plugin implementation (user code) is invalid * @static * @param {string} msg - Error message * @param {Object} [opts] - Plugin definition and user-supplied implementation * @param {PluginDefinition} [opts.pluginDef] - Plugin Definition * @param {*} [opts.pluginImpl] - Plugin Implementation (user-supplied) * @public * @returns {Error} Error with code {@link constants.INVALID_PLUGIN_DEFINITION} */ function createInvalidPluginImplementationError( msg, {pluginDef, pluginImpl} = {} ) { const err = new Error(msg); err.code = constants.INVALID_PLUGIN_IMPLEMENTATION; err.pluginDef = pluginDef; err.pluginImpl = pluginImpl; return err; } /** * Creates an error object to be thrown when a runnable exceeds its allowed run time. * @static * @param {string} msg - Error message * @param {number} [timeout] - Timeout in ms * @param {string} [file] - File, if given * @returns {MochaTimeoutError} */ function createTimeoutError(msg, timeout, file) { const err = new Error(msg); err.code = constants.TIMEOUT; err.timeout = timeout; err.file = file; return err; } /** * Creates an error object to be thrown when file is unparsable * @public * @static * @param {string} message - Error message to be displayed. * @param {string} filename - File name * @returns {Error} Error with code {@link constants.UNPARSABLE_FILE} */ function createUnparsableFileError(message, filename) { var err = new Error(message); err.code = constants.UNPARSABLE_FILE; return err; } /** * Returns `true` if an error came out of Mocha. * _Can suffer from false negatives, but not false positives._ * @static * @public * @param {*} err - Error, or anything * @returns {boolean} */ const isMochaError = err => Boolean(err && typeof err === 'object' && MOCHA_ERRORS.has(err.code)); module.exports = { constants, createFatalError, createForbiddenExclusivityError, createInvalidArgumentTypeError, createInvalidArgumentValueError, createInvalidExceptionError, createInvalidInterfaceError, createInvalidLegacyPluginError, createInvalidPluginDefinitionError, createInvalidPluginError, createInvalidPluginImplementationError, createInvalidReporterError, createMissingArgumentError, createMochaInstanceAlreadyDisposedError, createMochaInstanceAlreadyRunningError, createMultipleDoneError, createNoFilesMatchPatternError, createTimeoutError, createUnparsableFileError, createUnsupportedError, deprecate, isMochaError, warn }; /** * The error thrown when a Runnable times out * @memberof module:lib/errors * @typedef {Error} MochaTimeoutError * @property {constants.TIMEOUT} code - Error code * @property {number?} timeout Timeout in ms * @property {string?} file Filepath, if given */