|
/*! |
|
* 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. |
|
|