552 lines
11 KiB
JavaScript
552 lines
11 KiB
JavaScript
/*!
|
|
* 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;
|
|
}
|