You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
538 lines
11 KiB
538 lines
11 KiB
/*! |
|
* 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() || '<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 ' + 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 |
|
}
|
|
|