|
|
/*! * 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
// make useful anonymous name
if (!funcName) { funcName = '<anonymous@' + formatLocation(site) + '>' }
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 ' + 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 }
|