/*! * depd * Copyright(c) 2014-2018 Douglas Christopher Wilson * MIT Licensed */ /** * Module dependencies. */ var relative = require('path').relative; /** * Module exports. */ module.exports = depd; /** * Get the path to base files on. */ var basePath = process.cwd(); /** * Determine if namespace is contained in the string. */ function containsNamespace(str, namespace) { var vals = str.split(/[ ,]+/); var ns = String(namespace).toLowerCase(); for (var i = 0; i < vals.length; i++) { var val = vals[i]; // namespace contained if (val && (val === '*' || val.toLowerCase() === ns)) { return true; } } return false; } /** * Convert a data descriptor to accessor descriptor. */ function convertDataDescriptorToAccessor(obj, prop, message) { var descriptor = Object.getOwnPropertyDescriptor(obj, prop); var value = descriptor.value; descriptor.get = function getter() { return value; }; if (descriptor.writable) { descriptor.set = function setter(val) { return (value = val); }; } delete descriptor.value; delete descriptor.writable; Object.defineProperty(obj, prop, descriptor); return descriptor; } /** * Create arguments string to keep arity. */ function createArgumentsString(arity) { var str = ''; for (var i = 0; i < arity; i++) { str += ', arg' + i; } return str.substr(2); } /** * Create stack string from stack. */ function createStackString(stack) { var str = this.name + ': ' + this.namespace; if (this.message) { str += ' deprecated ' + this.message; } for (var i = 0; i < stack.length; i++) { str += '\n at ' + stack[i].toString(); } return str; } /** * Create deprecate for namespace in caller. */ function depd(namespace) { if (!namespace) { throw new TypeError('argument namespace is required'); } var stack = getStack(); var site = callSiteLocation(stack[1]); var file = site[0]; function deprecate(message) { // call to self as log log.call(deprecate, message); } deprecate._file = file; deprecate._ignored = isignored(namespace); deprecate._namespace = namespace; deprecate._traced = istraced(namespace); deprecate._warned = Object.create(null); deprecate.function = wrapfunction; deprecate.property = wrapproperty; return deprecate; } /** * Determine if event emitter has listeners of a given type. * * The way to do this check is done three different ways in Node.js >= 0.8 * so this consolidates them into a minimal set using instance methods. * * @param {EventEmitter} emitter * @param {string} type * @returns {boolean} * @private */ function eehaslisteners(emitter, type) { var count = typeof emitter.listenerCount !== 'function' ? emitter.listeners(type).length : emitter.listenerCount(type); return count > 0; } /** * Determine if namespace is ignored. */ function isignored(namespace) { if (process.noDeprecation) { // --no-deprecation support return true; } var str = process.env.NO_DEPRECATION || ''; // namespace ignored return containsNamespace(str, namespace); } /** * Determine if namespace is traced. */ function istraced(namespace) { if (process.traceDeprecation) { // --trace-deprecation support return true; } var str = process.env.TRACE_DEPRECATION || ''; // namespace traced return containsNamespace(str, namespace); } /** * Display deprecation message. */ function log(message, site) { var haslisteners = eehaslisteners(process, 'deprecation'); // abort early if no destination if (!haslisteners && this._ignored) { return; } var caller; var callFile; var callSite; var depSite; var i = 0; var seen = false; var stack = getStack(); var file = this._file; if (site) { // provided site depSite = site; callSite = callSiteLocation(stack[1]); callSite.name = depSite.name; file = callSite[0]; } else { // get call site i = 2; depSite = callSiteLocation(stack[i]); callSite = depSite; } // get caller of deprecated thing in relation to file for (; i < stack.length; i++) { caller = callSiteLocation(stack[i]); callFile = caller[0]; if (callFile === file) { seen = true; } else if (callFile === this._file) { file = this._file; } else if (seen) { break; } } var key = caller ? depSite.join(':') + '__' + caller.join(':') : undefined; if (key !== undefined && key in this._warned) { // already warned return; } this._warned[key] = true; // generate automatic message from call site var msg = message; if (!msg) { msg = callSite === depSite || !callSite.name ? defaultMessage(depSite) : defaultMessage(callSite); } // emit deprecation if listeners exist if (haslisteners) { var err = DeprecationError(this._namespace, msg, stack.slice(i)); process.emit('deprecation', err); return; } // format and write message var format = process.stderr.isTTY ? formatColor : formatPlain; var output = format.call(this, msg, caller, stack.slice(i)); process.stderr.write(output + '\n', 'utf8'); } /** * Get call site location as array. */ function callSiteLocation(callSite) { var file = callSite.getFileName() || ''; var line = callSite.getLineNumber(); var colm = callSite.getColumnNumber(); if (callSite.isEval()) { file = callSite.getEvalOrigin() + ', ' + file; } var site = [file, line, colm]; site.callSite = callSite; site.name = callSite.getFunctionName(); return site; } /** * Generate a default message from the site. */ function defaultMessage(site) { var callSite = site.callSite; var funcName = site.name; // make useful anonymous name if (!funcName) { funcName = ''; } var context = callSite.getThis(); var typeName = context && callSite.getTypeName(); // ignore useless type name if (typeName === 'Object') { typeName = undefined; } // make useful type name if (typeName === 'Function') { typeName = context.name || typeName; } return typeName && callSite.getMethodName() ? typeName + '.' + funcName : funcName; } /** * Format deprecation message without color. */ function formatPlain(msg, caller, stack) { var timestamp = new Date().toUTCString(); var formatted = timestamp + ' ' + this._namespace + ' deprecated ' + msg; // add stack trace if (this._traced) { for (var i = 0; i < stack.length; i++) { formatted += '\n at ' + stack[i].toString(); } return formatted; } if (caller) { formatted += ' at ' + formatLocation(caller); } return formatted; } /** * Format deprecation message with color. */ function formatColor(msg, caller, stack) { var formatted = '\x1b[36;1m' + this._namespace + '\x1b[22;39m' + // bold cyan ' \x1b[33;1mdeprecated\x1b[22;39m' + // bold yellow ' \x1b[0m' + msg + '\x1b[39m'; // reset // add stack trace if (this._traced) { for (var i = 0; i < stack.length; i++) { formatted += '\n \x1b[36mat ' + stack[i].toString() + '\x1b[39m'; // cyan } return formatted; } if (caller) { formatted += ' \x1b[36m' + formatLocation(caller) + '\x1b[39m'; // cyan } return formatted; } /** * Format call site location. */ function formatLocation(callSite) { return ( relative(basePath, callSite[0]) + ':' + callSite[1] + ':' + callSite[2] ); } /** * Get the stack as array of call sites. */ function getStack() { var limit = Error.stackTraceLimit; var obj = {}; var prep = Error.prepareStackTrace; Error.prepareStackTrace = prepareObjectStackTrace; Error.stackTraceLimit = Math.max(10, limit); // capture the stack Error.captureStackTrace(obj); // slice this function off the top var stack = obj.stack.slice(1); Error.prepareStackTrace = prep; Error.stackTraceLimit = limit; return stack; } /** * Capture call site stack from v8. */ function prepareObjectStackTrace(obj, stack) { return stack; } /** * Return a wrapped function in a deprecation message. */ function wrapfunction(fn, message) { if (typeof fn !== 'function') { throw new TypeError('argument fn must be a function'); } var args = createArgumentsString(fn.length); var stack = getStack(); var site = callSiteLocation(stack[1]); site.name = fn.name; // eslint-disable-next-line no-new-func var deprecatedfn = new Function( 'fn', 'log', 'deprecate', 'message', 'site', '"use strict"\n' + 'return function (' + args + ') {' + 'log.call(deprecate, message, site)\n' + 'return fn.apply(this, arguments)\n' + '}' )(fn, log, this, message, site); return deprecatedfn; } /** * Wrap property in a deprecation message. */ function wrapproperty(obj, prop, message) { if (!obj || (typeof obj !== 'object' && typeof obj !== 'function')) { throw new TypeError('argument obj must be object'); } var descriptor = Object.getOwnPropertyDescriptor(obj, prop); if (!descriptor) { throw new TypeError('must call property on owner object'); } if (!descriptor.configurable) { throw new TypeError('property must be configurable'); } var deprecate = this; var stack = getStack(); var site = callSiteLocation(stack[1]); // set site name site.name = prop; // convert data descriptor if ('value' in descriptor) { descriptor = convertDataDescriptorToAccessor(obj, prop, message); } var get = descriptor.get; var set = descriptor.set; // wrap getter if (typeof get === 'function') { descriptor.get = function getter() { log.call(deprecate, message, site); return get.apply(this, arguments); }; } // wrap setter if (typeof set === 'function') { descriptor.set = function setter() { log.call(deprecate, message, site); return set.apply(this, arguments); }; } Object.defineProperty(obj, prop, descriptor); } /** * Create DeprecationError for deprecation */ function DeprecationError(namespace, message, stack) { var error = new Error(); var stackString; Object.defineProperty(error, 'constructor', { value: DeprecationError, }); Object.defineProperty(error, 'message', { configurable: true, enumerable: false, value: message, writable: true, }); Object.defineProperty(error, 'name', { enumerable: false, configurable: true, value: 'DeprecationError', writable: true, }); Object.defineProperty(error, 'namespace', { configurable: true, enumerable: false, value: namespace, writable: true, }); Object.defineProperty(error, 'stack', { configurable: true, enumerable: false, get: function () { if (stackString !== undefined) { return stackString; } // prepare stack trace return (stackString = createStackString.call(this, stack)); }, set: function setter(val) { stackString = val; }, }); return error; }