/*! * depd * Copyright(c) 2014 Douglas Christopher Wilson * MIT Licensed */ /** * Module dependencies. */ var EventEmitter = require('events').EventEmitter var relative = require('path').relative /** * Module exports. */ module.exports = depd /** * Get the path to base files on. */ var basePath = process.cwd() /** * Get listener count on event emitter. */ /*istanbul ignore next*/ var eventListenerCount = EventEmitter.listenerCount || function (emitter, type) { return emitter.listeners(type).length } /** * 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._warned = Object.create(null) deprecate.function = wrapfunction deprecate.property = wrapproperty return deprecate } /** * Determine if namespace is ignored. */ function isignored(namespace) { var str = process.env.NO_DEPRECATION || '' var val = str.split(/[ ,]+/) namespace = String(namespace).toLowerCase() for (var i = 0 ; i < val.length; i++) { if (!(str = val[i])) continue; // namespace ignored if (str === '*' || str.toLowerCase() === namespace) { return true } } return false } /** * Display deprecation message. */ function log(message, site) { var haslisteners = eventListenerCount(process, 'deprecation') !== 0 // abort early if no destination if (!haslisteners && this._ignored) { return } var caller var callSite var i = 0 var seen = false var stack = getStack() var file = this._file if (site) { // provided site callSite = callSiteLocation(stack[1]) callSite.name = site.name file = callSite[0] } else { // get call site i = 2 site = callSiteLocation(stack[i]) callSite = site } // get caller of deprecated thing in relation to file for (; i < stack.length; i++) { caller = callSiteLocation(stack[i]) if (caller[0] === file) { seen = true } else if (seen) { break } } var key = caller ? site.join(':') + '__' + caller.join(':') : undefined if (key !== undefined && key in this._warned) { // already warned return } this._warned[key] = true // generate automatic message from call site if (!message) { message = callSite === site || !callSite.name ? defaultMessage(site) : defaultMessage(callSite) } // emit deprecation if listeners exist if (haslisteners) { var err = DeprecationError(this._namespace, message, stack.slice(i)) process.emit('deprecation', err) return } // format and write message var format = process.stderr.isTTY ? formatColor : formatPlain var msg = format.call(this, message, caller) process.stderr.write(msg + '\n', 'utf8') return } /** * 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 = '' } return callSite.getMethodName() ? callSite.getTypeName() + '.' + funcName : funcName } /** * Format deprecation message without color. */ function formatPlain(msg, caller) { var timestamp = new Date().toUTCString() var formatted = timestamp + ' ' + this._namespace + ' deprecated ' + msg if (caller) { formatted += ' at ' + formatLocation(caller) } return formatted } /** * Format deprecation message with color. */ function formatColor(msg, caller) { var formatted = '\x1b[36;1m' + this._namespace + '\x1b[22;39m' // bold cyan + ' \x1b[33;1mdeprecated\x1b[22;39m' // bold yellow + ' \x1b[90m' + msg + '\x1b[39m' // grey 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 obj = {} var prep = Error.prepareStackTrace Error.prepareStackTrace = prepareObjectStackTrace Error.captureStackTrace(obj, getStack) var stack = obj.stack Error.prepareStackTrace = prep 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 deprecate = this var stack = getStack() var site = callSiteLocation(stack[1]) site.name = fn.name var deprecatedfn = eval('(function (' + args + ') {\n' + 'log.call(deprecate, message, site)\n' + 'return fn.apply(this, arguments)\n' + '})') return deprecatedfn } /** * Wrap property in a deprecation message. */ function wrapproperty(obj, prop, message) { if (!obj || typeof obj !== 'object') { 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 }