3 * Copyright(c) 2014-2017 Douglas Christopher Wilson
11 var callSiteToString = require('./lib/compat').callSiteToString
12 var eventListenerCount = require('./lib/compat').eventListenerCount
13 var relative = require('path').relative
22 * Get the path to base files on.
25 var basePath = process.cwd()
28 * Determine if namespace is contained in the string.
31 function containsNamespace (str, namespace) {
32 var vals = str.split(/[ ,]+/)
33 var ns = String(namespace).toLowerCase()
35 for (var i = 0; i < vals.length; i++) {
38 // namespace contained
39 if (val && (val === '*' || val.toLowerCase() === ns)) {
48 * Convert a data descriptor to accessor descriptor.
51 function convertDataDescriptorToAccessor (obj, prop, message) {
52 var descriptor = Object.getOwnPropertyDescriptor(obj, prop)
53 var value = descriptor.value
55 descriptor.get = function getter () { return value }
57 if (descriptor.writable) {
58 descriptor.set = function setter (val) { return (value = val) }
61 delete descriptor.value
62 delete descriptor.writable
64 Object.defineProperty(obj, prop, descriptor)
70 * Create arguments string to keep arity.
73 function createArgumentsString (arity) {
76 for (var i = 0; i < arity; i++) {
84 * Create stack string from stack.
87 function createStackString (stack) {
88 var str = this.name + ': ' + this.namespace
91 str += ' deprecated ' + this.message
94 for (var i = 0; i < stack.length; i++) {
95 str += '\n at ' + callSiteToString(stack[i])
102 * Create deprecate for namespace in caller.
105 function depd (namespace) {
107 throw new TypeError('argument namespace is required')
110 var stack = getStack()
111 var site = callSiteLocation(stack[1])
114 function deprecate (message) {
115 // call to self as log
116 log.call(deprecate, message)
119 deprecate._file = file
120 deprecate._ignored = isignored(namespace)
121 deprecate._namespace = namespace
122 deprecate._traced = istraced(namespace)
123 deprecate._warned = Object.create(null)
125 deprecate.function = wrapfunction
126 deprecate.property = wrapproperty
132 * Determine if namespace is ignored.
135 function isignored (namespace) {
136 /* istanbul ignore next: tested in a child processs */
137 if (process.noDeprecation) {
138 // --no-deprecation support
142 var str = process.env.NO_DEPRECATION || ''
145 return containsNamespace(str, namespace)
149 * Determine if namespace is traced.
152 function istraced (namespace) {
153 /* istanbul ignore next: tested in a child processs */
154 if (process.traceDeprecation) {
155 // --trace-deprecation support
159 var str = process.env.TRACE_DEPRECATION || ''
162 return containsNamespace(str, namespace)
166 * Display deprecation message.
169 function log (message, site) {
170 var haslisteners = eventListenerCount(process, 'deprecation') !== 0
172 // abort early if no destination
173 if (!haslisteners && this._ignored) {
183 var stack = getStack()
184 var file = this._file
189 callSite = callSiteLocation(stack[1])
190 callSite.name = depSite.name
195 depSite = callSiteLocation(stack[i])
199 // get caller of deprecated thing in relation to file
200 for (; i < stack.length; i++) {
201 caller = callSiteLocation(stack[i])
204 if (callFile === file) {
206 } else if (callFile === this._file) {
214 ? depSite.join(':') + '__' + caller.join(':')
217 if (key !== undefined && key in this._warned) {
222 this._warned[key] = true
224 // generate automatic message from call site
227 msg = callSite === depSite || !callSite.name
228 ? defaultMessage(depSite)
229 : defaultMessage(callSite)
232 // emit deprecation if listeners exist
234 var err = DeprecationError(this._namespace, msg, stack.slice(i))
235 process.emit('deprecation', err)
239 // format and write message
240 var format = process.stderr.isTTY
243 var output = format.call(this, msg, caller, stack.slice(i))
244 process.stderr.write(output + '\n', 'utf8')
248 * Get call site location as array.
251 function callSiteLocation (callSite) {
252 var file = callSite.getFileName() || '<anonymous>'
253 var line = callSite.getLineNumber()
254 var colm = callSite.getColumnNumber()
256 if (callSite.isEval()) {
257 file = callSite.getEvalOrigin() + ', ' + file
260 var site = [file, line, colm]
262 site.callSite = callSite
263 site.name = callSite.getFunctionName()
269 * Generate a default message from the site.
272 function defaultMessage (site) {
273 var callSite = site.callSite
274 var funcName = site.name
276 // make useful anonymous name
278 funcName = '<anonymous@' + formatLocation(site) + '>'
281 var context = callSite.getThis()
282 var typeName = context && callSite.getTypeName()
284 // ignore useless type name
285 if (typeName === 'Object') {
289 // make useful type name
290 if (typeName === 'Function') {
291 typeName = context.name || typeName
294 return typeName && callSite.getMethodName()
295 ? typeName + '.' + funcName
300 * Format deprecation message without color.
303 function formatPlain (msg, caller, stack) {
304 var timestamp = new Date().toUTCString()
306 var formatted = timestamp +
307 ' ' + this._namespace +
312 for (var i = 0; i < stack.length; i++) {
313 formatted += '\n at ' + callSiteToString(stack[i])
320 formatted += ' at ' + formatLocation(caller)
327 * Format deprecation message with color.
330 function formatColor (msg, caller, stack) {
331 var formatted = '\x1b[36;1m' + this._namespace + '\x1b[22;39m' + // bold cyan
332 ' \x1b[33;1mdeprecated\x1b[22;39m' + // bold yellow
333 ' \x1b[0m' + msg + '\x1b[39m' // reset
337 for (var i = 0; i < stack.length; i++) {
338 formatted += '\n \x1b[36mat ' + callSiteToString(stack[i]) + '\x1b[39m' // cyan
345 formatted += ' \x1b[36m' + formatLocation(caller) + '\x1b[39m' // cyan
352 * Format call site location.
355 function formatLocation (callSite) {
356 return relative(basePath, callSite[0]) +
362 * Get the stack as array of call sites.
365 function getStack () {
366 var limit = Error.stackTraceLimit
368 var prep = Error.prepareStackTrace
370 Error.prepareStackTrace = prepareObjectStackTrace
371 Error.stackTraceLimit = Math.max(10, limit)
374 Error.captureStackTrace(obj)
376 // slice this function off the top
377 var stack = obj.stack.slice(1)
379 Error.prepareStackTrace = prep
380 Error.stackTraceLimit = limit
386 * Capture call site stack from v8.
389 function prepareObjectStackTrace (obj, stack) {
394 * Return a wrapped function in a deprecation message.
397 function wrapfunction (fn, message) {
398 if (typeof fn !== 'function') {
399 throw new TypeError('argument fn must be a function')
402 var args = createArgumentsString(fn.length)
403 var deprecate = this // eslint-disable-line no-unused-vars
404 var stack = getStack()
405 var site = callSiteLocation(stack[1])
409 // eslint-disable-next-line no-eval
410 var deprecatedfn = eval('(function (' + args + ') {\n' +
412 'log.call(deprecate, message, site)\n' +
413 'return fn.apply(this, arguments)\n' +
420 * Wrap property in a deprecation message.
423 function wrapproperty (obj, prop, message) {
424 if (!obj || (typeof obj !== 'object' && typeof obj !== 'function')) {
425 throw new TypeError('argument obj must be object')
428 var descriptor = Object.getOwnPropertyDescriptor(obj, prop)
431 throw new TypeError('must call property on owner object')
434 if (!descriptor.configurable) {
435 throw new TypeError('property must be configurable')
439 var stack = getStack()
440 var site = callSiteLocation(stack[1])
445 // convert data descriptor
446 if ('value' in descriptor) {
447 descriptor = convertDataDescriptorToAccessor(obj, prop, message)
450 var get = descriptor.get
451 var set = descriptor.set
454 if (typeof get === 'function') {
455 descriptor.get = function getter () {
456 log.call(deprecate, message, site)
457 return get.apply(this, arguments)
462 if (typeof set === 'function') {
463 descriptor.set = function setter () {
464 log.call(deprecate, message, site)
465 return set.apply(this, arguments)
469 Object.defineProperty(obj, prop, descriptor)
473 * Create DeprecationError for deprecation
476 function DeprecationError (namespace, message, stack) {
477 var error = new Error()
480 Object.defineProperty(error, 'constructor', {
481 value: DeprecationError
484 Object.defineProperty(error, 'message', {
491 Object.defineProperty(error, 'name', {
494 value: 'DeprecationError',
498 Object.defineProperty(error, 'namespace', {
505 Object.defineProperty(error, 'stack', {
509 if (stackString !== undefined) {
513 // prepare stack trace
514 return (stackString = createStackString.call(this, stack))
516 set: function setter (val) {