/*!
* depd * Copyright(c) 2014 Douglas Christopher Wilson * MIT Licensed */
/**
* Module dependencies. */
var callSiteToString = require('./lib/compat').callSiteToString 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 }
/**
* Determine if namespace is contained in the string. */
function containsNamespace(str, namespace) {
var val = str.split(/[ ,]+/) namespace = String(namespace).toLowerCase() for (var i = 0 ; i < val.length; i++) { if (!(str = val[i])) continue; // namespace contained if (str === '*' || str.toLowerCase() === namespace) { 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 ' + callSiteToString(stack[i]) } 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 namespace is ignored. */
function isignored(namespace) {
/* istanbul ignore next: tested in a child processs */ 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) {
/* istanbul ignore next: tested in a child processs */ 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 = eventListenerCount(process, 'deprecation') !== 0 // abort early if no destination if (!haslisteners && this._ignored) { return } var caller var callFile 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]) callFile = caller[0] if (callFile === file) { seen = true } else if (callFile === this._file) { file = this._file } 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, stack.slice(i)) process.stderr.write(msg + '\n', 'utf8') return
}
/**
* Get call site location as array. */
function callSiteLocation(callSite) {
var file = callSite.getFileName() || '<anonymous>' 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 var typeName = callSite.getTypeName() // make useful anonymous name if (!funcName) { funcName = '<anonymous@' + formatLocation(site) + '>' } // make useful type name if (typeName === 'Function') { typeName = callSite.getThis().name || typeName } return 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 ' + callSiteToString(stack[i]) } 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 ' + callSiteToString(stack[i]) + '\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 deprecate = this var stack = getStack() var site = callSiteLocation(stack[1]) site.name = fn.name var deprecatedfn = eval('(function (' + args + ') {\n' + '"use strict"\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' && 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
}