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
284 // make useful anonymous name
286 funcName = '<anonymous@' + formatLocation(site) + '>'
289 var context = callSite.getThis()
290 var typeName = context && callSite.getTypeName()
292 // ignore useless type name
293 if (typeName === 'Object') {
297 // make useful type name
298 if (typeName === 'Function') {
299 typeName = context.name || typeName
302 return typeName && callSite.getMethodName()
303 ? typeName + '.' + funcName
308 * Format deprecation message without color.
311 function formatPlain(msg, caller, stack) {
312 var timestamp = new Date().toUTCString()
314 var formatted = timestamp
315 + ' ' + this._namespace
316 + ' deprecated ' + msg
320 for (var i = 0; i < stack.length; i++) {
321 formatted += '\n at ' + callSiteToString(stack[i])
328 formatted += ' at ' + formatLocation(caller)
335 * Format deprecation message with color.
338 function formatColor(msg, caller, stack) {
339 var formatted = '\x1b[36;1m' + this._namespace + '\x1b[22;39m' // bold cyan
340 + ' \x1b[33;1mdeprecated\x1b[22;39m' // bold yellow
341 + ' \x1b[0m' + msg + '\x1b[39m' // reset
345 for (var i = 0; i < stack.length; i++) {
346 formatted += '\n \x1b[36mat ' + callSiteToString(stack[i]) + '\x1b[39m' // cyan
353 formatted += ' \x1b[36m' + formatLocation(caller) + '\x1b[39m' // cyan
360 * Format call site location.
363 function formatLocation(callSite) {
364 return relative(basePath, callSite[0])
370 * Get the stack as array of call sites.
373 function getStack() {
374 var limit = Error.stackTraceLimit
376 var prep = Error.prepareStackTrace
378 Error.prepareStackTrace = prepareObjectStackTrace
379 Error.stackTraceLimit = Math.max(10, limit)
382 Error.captureStackTrace(obj)
384 // slice this function off the top
385 var stack = obj.stack.slice(1)
387 Error.prepareStackTrace = prep
388 Error.stackTraceLimit = limit
394 * Capture call site stack from v8.
397 function prepareObjectStackTrace(obj, stack) {
402 * Return a wrapped function in a deprecation message.
405 function wrapfunction(fn, message) {
406 if (typeof fn !== 'function') {
407 throw new TypeError('argument fn must be a function')
410 var args = createArgumentsString(fn.length)
412 var stack = getStack()
413 var site = callSiteLocation(stack[1])
417 var deprecatedfn = eval('(function (' + args + ') {\n'
419 + 'log.call(deprecate, message, site)\n'
420 + 'return fn.apply(this, arguments)\n'
427 * Wrap property in a deprecation message.
430 function wrapproperty(obj, prop, message) {
431 if (!obj || (typeof obj !== 'object' && typeof obj !== 'function')) {
432 throw new TypeError('argument obj must be object')
435 var descriptor = Object.getOwnPropertyDescriptor(obj, prop)
438 throw new TypeError('must call property on owner object')
441 if (!descriptor.configurable) {
442 throw new TypeError('property must be configurable')
446 var stack = getStack()
447 var site = callSiteLocation(stack[1])
452 // convert data descriptor
453 if ('value' in descriptor) {
454 descriptor = convertDataDescriptorToAccessor(obj, prop, message)
457 var get = descriptor.get
458 var set = descriptor.set
461 if (typeof get === 'function') {
462 descriptor.get = function getter() {
463 log.call(deprecate, message, site)
464 return get.apply(this, arguments)
469 if (typeof set === 'function') {
470 descriptor.set = function setter() {
471 log.call(deprecate, message, site)
472 return set.apply(this, arguments)
476 Object.defineProperty(obj, prop, descriptor)
480 * Create DeprecationError for deprecation
483 function DeprecationError(namespace, message, stack) {
484 var error = new Error()
487 Object.defineProperty(error, 'constructor', {
488 value: DeprecationError
491 Object.defineProperty(error, 'message', {
498 Object.defineProperty(error, 'name', {
501 value: 'DeprecationError',
505 Object.defineProperty(error, 'namespace', {
512 Object.defineProperty(error, 'stack', {
516 if (stackString !== undefined) {
520 // prepare stack trace
521 return stackString = createStackString.call(this, stack)
523 set: function setter(val) {