| /*! |
| * depd |
| * Copyright(c) 2014-2017 Douglas Christopher Wilson |
| * MIT Licensed |
| */ |
| |
| /** |
| * Module dependencies. |
| */ |
| |
| var callSiteToString = require('./lib/compat').callSiteToString |
| var eventListenerCount = require('./lib/compat').eventListenerCount |
| 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 ' + 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 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 ' + 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 // eslint-disable-line no-unused-vars |
| var stack = getStack() |
| var site = callSiteLocation(stack[1]) |
| |
| site.name = fn.name |
| |
| // eslint-disable-next-line no-eval |
| 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 |
| } |