3 * Copyright(c) 2014 Douglas Christopher Wilson
11 var callSiteToString = require('./lib/compat').callSiteToString
12 var EventEmitter = require('events').EventEmitter
13 var relative = require('path').relative
22 * Get the path to base files on.
25 var basePath = process.cwd()
28 * Get listener count on event emitter.
31 /*istanbul ignore next*/
32 var eventListenerCount = EventEmitter.listenerCount
33 || function (emitter, type) { return emitter.listeners(type).length }
36 * Determine if namespace is contained in the string.
39 function containsNamespace(str, namespace) {
40 var val = str.split(/[ ,]+/)
42 namespace = String(namespace).toLowerCase()
44 for (var i = 0 ; i < val.length; i++) {
45 if (!(str = val[i])) continue;
47 // namespace contained
48 if (str === '*' || str.toLowerCase() === namespace) {
57 * Convert a data descriptor to accessor descriptor.
60 function convertDataDescriptorToAccessor(obj, prop, message) {
61 var descriptor = Object.getOwnPropertyDescriptor(obj, prop)
62 var value = descriptor.value
64 descriptor.get = function getter() { return value }
66 if (descriptor.writable) {
67 descriptor.set = function setter(val) { return value = val }
70 delete descriptor.value
71 delete descriptor.writable
73 Object.defineProperty(obj, prop, descriptor)
79 * Create arguments string to keep arity.
82 function createArgumentsString(arity) {
85 for (var i = 0; i < arity; i++) {
93 * Create stack string from stack.
96 function createStackString(stack) {
97 var str = this.name + ': ' + this.namespace
100 str += ' deprecated ' + this.message
103 for (var i = 0; i < stack.length; i++) {
104 str += '\n at ' + callSiteToString(stack[i])
111 * Create deprecate for namespace in caller.
114 function depd(namespace) {
116 throw new TypeError('argument namespace is required')
119 var stack = getStack()
120 var site = callSiteLocation(stack[1])
123 function deprecate(message) {
124 // call to self as log
125 log.call(deprecate, message)
128 deprecate._file = file
129 deprecate._ignored = isignored(namespace)
130 deprecate._namespace = namespace
131 deprecate._traced = istraced(namespace)
132 deprecate._warned = Object.create(null)
134 deprecate.function = wrapfunction
135 deprecate.property = wrapproperty
141 * Determine if namespace is ignored.
144 function isignored(namespace) {
145 /* istanbul ignore next: tested in a child processs */
146 if (process.noDeprecation) {
147 // --no-deprecation support
151 var str = process.env.NO_DEPRECATION || ''
154 return containsNamespace(str, namespace)
158 * Determine if namespace is traced.
161 function istraced(namespace) {
162 /* istanbul ignore next: tested in a child processs */
163 if (process.traceDeprecation) {
164 // --trace-deprecation support
168 var str = process.env.TRACE_DEPRECATION || ''
171 return containsNamespace(str, namespace)
175 * Display deprecation message.
178 function log(message, site) {
179 var haslisteners = eventListenerCount(process, 'deprecation') !== 0
181 // abort early if no destination
182 if (!haslisteners && this._ignored) {
191 var stack = getStack()
192 var file = this._file
196 callSite = callSiteLocation(stack[1])
197 callSite.name = site.name
202 site = callSiteLocation(stack[i])
206 // get caller of deprecated thing in relation to file
207 for (; i < stack.length; i++) {
208 caller = callSiteLocation(stack[i])
211 if (callFile === file) {
213 } else if (callFile === this._file) {
221 ? site.join(':') + '__' + caller.join(':')
224 if (key !== undefined && key in this._warned) {
229 this._warned[key] = true
231 // generate automatic message from call site
233 message = callSite === site || !callSite.name
234 ? defaultMessage(site)
235 : defaultMessage(callSite)
238 // emit deprecation if listeners exist
240 var err = DeprecationError(this._namespace, message, stack.slice(i))
241 process.emit('deprecation', err)
245 // format and write message
246 var format = process.stderr.isTTY
249 var msg = format.call(this, message, caller, stack.slice(i))
250 process.stderr.write(msg + '\n', 'utf8')
256 * Get call site location as array.
259 function callSiteLocation(callSite) {
260 var file = callSite.getFileName() || '<anonymous>'
261 var line = callSite.getLineNumber()
262 var colm = callSite.getColumnNumber()
264 if (callSite.isEval()) {
265 file = callSite.getEvalOrigin() + ', ' + file
268 var site = [file, line, colm]
270 site.callSite = callSite
271 site.name = callSite.getFunctionName()
277 * Generate a default message from the site.
280 function defaultMessage(site) {
281 var callSite = site.callSite
282 var funcName = site.name
283 var typeName = callSite.getTypeName()
285 // make useful anonymous name
287 funcName = '<anonymous@' + formatLocation(site) + '>'
290 // make useful type name
291 if (typeName === 'Function') {
292 typeName = callSite.getThis().name || typeName
295 return callSite.getMethodName()
296 ? typeName + '.' + funcName
301 * Format deprecation message without color.
304 function formatPlain(msg, caller, stack) {
305 var timestamp = new Date().toUTCString()
307 var formatted = timestamp
308 + ' ' + this._namespace
309 + ' deprecated ' + msg
313 for (var i = 0; i < stack.length; i++) {
314 formatted += '\n at ' + callSiteToString(stack[i])
321 formatted += ' at ' + formatLocation(caller)
328 * Format deprecation message with color.
331 function formatColor(msg, caller, stack) {
332 var formatted = '\x1b[36;1m' + this._namespace + '\x1b[22;39m' // bold cyan
333 + ' \x1b[33;1mdeprecated\x1b[22;39m' // bold yellow
334 + ' \x1b[0m' + msg + '\x1b[39m' // reset
338 for (var i = 0; i < stack.length; i++) {
339 formatted += '\n \x1b[36mat ' + callSiteToString(stack[i]) + '\x1b[39m' // cyan
346 formatted += ' \x1b[36m' + formatLocation(caller) + '\x1b[39m' // cyan
353 * Format call site location.
356 function formatLocation(callSite) {
357 return relative(basePath, callSite[0])
363 * Get the stack as array of call sites.
366 function getStack() {
367 var limit = Error.stackTraceLimit
369 var prep = Error.prepareStackTrace
371 Error.prepareStackTrace = prepareObjectStackTrace
372 Error.stackTraceLimit = Math.max(10, limit)
375 Error.captureStackTrace(obj)
377 // slice this function off the top
378 var stack = obj.stack.slice(1)
380 Error.prepareStackTrace = prep
381 Error.stackTraceLimit = limit
387 * Capture call site stack from v8.
390 function prepareObjectStackTrace(obj, stack) {
395 * Return a wrapped function in a deprecation message.
398 function wrapfunction(fn, message) {
399 if (typeof fn !== 'function') {
400 throw new TypeError('argument fn must be a function')
403 var args = createArgumentsString(fn.length)
405 var stack = getStack()
406 var site = callSiteLocation(stack[1])
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) {