3 * Copyright(c) 2014-2015 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 val = str.split(/[ ,]+/)
34 namespace = String(namespace).toLowerCase()
36 for (var i = 0 ; i < val.length; i++) {
37 if (!(str = val[i])) continue;
39 // namespace contained
40 if (str === '*' || str.toLowerCase() === namespace) {
49 * Convert a data descriptor to accessor descriptor.
52 function convertDataDescriptorToAccessor(obj, prop, message) {
53 var descriptor = Object.getOwnPropertyDescriptor(obj, prop)
54 var value = descriptor.value
56 descriptor.get = function getter() { return value }
58 if (descriptor.writable) {
59 descriptor.set = function setter(val) { return value = val }
62 delete descriptor.value
63 delete descriptor.writable
65 Object.defineProperty(obj, prop, descriptor)
71 * Create arguments string to keep arity.
74 function createArgumentsString(arity) {
77 for (var i = 0; i < arity; i++) {
85 * Create stack string from stack.
88 function createStackString(stack) {
89 var str = this.name + ': ' + this.namespace
92 str += ' deprecated ' + this.message
95 for (var i = 0; i < stack.length; i++) {
96 str += '\n at ' + callSiteToString(stack[i])
103 * Create deprecate for namespace in caller.
106 function depd(namespace) {
108 throw new TypeError('argument namespace is required')
111 var stack = getStack()
112 var site = callSiteLocation(stack[1])
115 function deprecate(message) {
116 // call to self as log
117 log.call(deprecate, message)
120 deprecate._file = file
121 deprecate._ignored = isignored(namespace)
122 deprecate._namespace = namespace
123 deprecate._traced = istraced(namespace)
124 deprecate._warned = Object.create(null)
126 deprecate.function = wrapfunction
127 deprecate.property = wrapproperty
133 * Determine if namespace is ignored.
136 function isignored(namespace) {
137 /* istanbul ignore next: tested in a child processs */
138 if (process.noDeprecation) {
139 // --no-deprecation support
143 var str = process.env.NO_DEPRECATION || ''
146 return containsNamespace(str, namespace)
150 * Determine if namespace is traced.
153 function istraced(namespace) {
154 /* istanbul ignore next: tested in a child processs */
155 if (process.traceDeprecation) {
156 // --trace-deprecation support
160 var str = process.env.TRACE_DEPRECATION || ''
163 return containsNamespace(str, namespace)
167 * Display deprecation message.
170 function log(message, site) {
171 var haslisteners = eventListenerCount(process, 'deprecation') !== 0
173 // abort early if no destination
174 if (!haslisteners && this._ignored) {
183 var stack = getStack()
184 var file = this._file
188 callSite = callSiteLocation(stack[1])
189 callSite.name = site.name
194 site = callSiteLocation(stack[i])
198 // get caller of deprecated thing in relation to file
199 for (; i < stack.length; i++) {
200 caller = callSiteLocation(stack[i])
203 if (callFile === file) {
205 } else if (callFile === this._file) {
213 ? site.join(':') + '__' + caller.join(':')
216 if (key !== undefined && key in this._warned) {
221 this._warned[key] = true
223 // generate automatic message from call site
225 message = callSite === site || !callSite.name
226 ? defaultMessage(site)
227 : defaultMessage(callSite)
230 // emit deprecation if listeners exist
232 var err = DeprecationError(this._namespace, message, stack.slice(i))
233 process.emit('deprecation', err)
237 // format and write message
238 var format = process.stderr.isTTY
241 var msg = format.call(this, message, caller, stack.slice(i))
242 process.stderr.write(msg + '\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
308 + ' deprecated ' + msg
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)
404 var stack = getStack()
405 var site = callSiteLocation(stack[1])
409 var deprecatedfn = eval('(function (' + args + ') {\n'
411 + 'log.call(deprecate, message, site)\n'
412 + 'return fn.apply(this, arguments)\n'
419 * Wrap property in a deprecation message.
422 function wrapproperty(obj, prop, message) {
423 if (!obj || (typeof obj !== 'object' && typeof obj !== 'function')) {
424 throw new TypeError('argument obj must be object')
427 var descriptor = Object.getOwnPropertyDescriptor(obj, prop)
430 throw new TypeError('must call property on owner object')
433 if (!descriptor.configurable) {
434 throw new TypeError('property must be configurable')
438 var stack = getStack()
439 var site = callSiteLocation(stack[1])
444 // convert data descriptor
445 if ('value' in descriptor) {
446 descriptor = convertDataDescriptorToAccessor(obj, prop, message)
449 var get = descriptor.get
450 var set = descriptor.set
453 if (typeof get === 'function') {
454 descriptor.get = function getter() {
455 log.call(deprecate, message, site)
456 return get.apply(this, arguments)
461 if (typeof set === 'function') {
462 descriptor.set = function setter() {
463 log.call(deprecate, message, site)
464 return set.apply(this, arguments)
468 Object.defineProperty(obj, prop, descriptor)
472 * Create DeprecationError for deprecation
475 function DeprecationError(namespace, message, stack) {
476 var error = new Error()
479 Object.defineProperty(error, 'constructor', {
480 value: DeprecationError
483 Object.defineProperty(error, 'message', {
490 Object.defineProperty(error, 'name', {
493 value: 'DeprecationError',
497 Object.defineProperty(error, 'namespace', {
504 Object.defineProperty(error, 'stack', {
508 if (stackString !== undefined) {
512 // prepare stack trace
513 return stackString = createStackString.call(this, stack)
515 set: function setter(val) {