UNPKG

10.9 kBJavaScriptView Raw
1/*!
2 * depd
3 * Copyright(c) 2014-2018 Douglas Christopher Wilson
4 * MIT Licensed
5 */
6
7/**
8 * Module dependencies.
9 */
10
11var relative = require('path').relative
12
13/**
14 * Module exports.
15 */
16
17module.exports = depd
18
19/**
20 * Get the path to base files on.
21 */
22
23var basePath = process.cwd()
24
25/**
26 * Determine if namespace is contained in the string.
27 */
28
29function containsNamespace (str, namespace) {
30 var vals = str.split(/[ ,]+/)
31 var ns = String(namespace).toLowerCase()
32
33 for (var i = 0; i < vals.length; i++) {
34 var val = vals[i]
35
36 // namespace contained
37 if (val && (val === '*' || val.toLowerCase() === ns)) {
38 return true
39 }
40 }
41
42 return false
43}
44
45/**
46 * Convert a data descriptor to accessor descriptor.
47 */
48
49function convertDataDescriptorToAccessor (obj, prop, message) {
50 var descriptor = Object.getOwnPropertyDescriptor(obj, prop)
51 var value = descriptor.value
52
53 descriptor.get = function getter () { return value }
54
55 if (descriptor.writable) {
56 descriptor.set = function setter (val) { return (value = val) }
57 }
58
59 delete descriptor.value
60 delete descriptor.writable
61
62 Object.defineProperty(obj, prop, descriptor)
63
64 return descriptor
65}
66
67/**
68 * Create arguments string to keep arity.
69 */
70
71function createArgumentsString (arity) {
72 var str = ''
73
74 for (var i = 0; i < arity; i++) {
75 str += ', arg' + i
76 }
77
78 return str.substr(2)
79}
80
81/**
82 * Create stack string from stack.
83 */
84
85function createStackString (stack) {
86 var str = this.name + ': ' + this.namespace
87
88 if (this.message) {
89 str += ' deprecated ' + this.message
90 }
91
92 for (var i = 0; i < stack.length; i++) {
93 str += '\n at ' + stack[i].toString()
94 }
95
96 return str
97}
98
99/**
100 * Create deprecate for namespace in caller.
101 */
102
103function depd (namespace) {
104 if (!namespace) {
105 throw new TypeError('argument namespace is required')
106 }
107
108 var stack = getStack()
109 var site = callSiteLocation(stack[1])
110 var file = site[0]
111
112 function deprecate (message) {
113 // call to self as log
114 log.call(deprecate, message)
115 }
116
117 deprecate._file = file
118 deprecate._ignored = isignored(namespace)
119 deprecate._namespace = namespace
120 deprecate._traced = istraced(namespace)
121 deprecate._warned = Object.create(null)
122
123 deprecate.function = wrapfunction
124 deprecate.property = wrapproperty
125
126 return deprecate
127}
128
129/**
130 * Determine if event emitter has listeners of a given type.
131 *
132 * The way to do this check is done three different ways in Node.js >= 0.8
133 * so this consolidates them into a minimal set using instance methods.
134 *
135 * @param {EventEmitter} emitter
136 * @param {string} type
137 * @returns {boolean}
138 * @private
139 */
140
141function eehaslisteners (emitter, type) {
142 var count = typeof emitter.listenerCount !== 'function'
143 ? emitter.listeners(type).length
144 : emitter.listenerCount(type)
145
146 return count > 0
147}
148
149/**
150 * Determine if namespace is ignored.
151 */
152
153function isignored (namespace) {
154 if (process.noDeprecation) {
155 // --no-deprecation support
156 return true
157 }
158
159 var str = process.env.NO_DEPRECATION || ''
160
161 // namespace ignored
162 return containsNamespace(str, namespace)
163}
164
165/**
166 * Determine if namespace is traced.
167 */
168
169function istraced (namespace) {
170 if (process.traceDeprecation) {
171 // --trace-deprecation support
172 return true
173 }
174
175 var str = process.env.TRACE_DEPRECATION || ''
176
177 // namespace traced
178 return containsNamespace(str, namespace)
179}
180
181/**
182 * Display deprecation message.
183 */
184
185function log (message, site) {
186 var haslisteners = eehaslisteners(process, 'deprecation')
187
188 // abort early if no destination
189 if (!haslisteners && this._ignored) {
190 return
191 }
192
193 var caller
194 var callFile
195 var callSite
196 var depSite
197 var i = 0
198 var seen = false
199 var stack = getStack()
200 var file = this._file
201
202 if (site) {
203 // provided site
204 depSite = site
205 callSite = callSiteLocation(stack[1])
206 callSite.name = depSite.name
207 file = callSite[0]
208 } else {
209 // get call site
210 i = 2
211 depSite = callSiteLocation(stack[i])
212 callSite = depSite
213 }
214
215 // get caller of deprecated thing in relation to file
216 for (; i < stack.length; i++) {
217 caller = callSiteLocation(stack[i])
218 callFile = caller[0]
219
220 if (callFile === file) {
221 seen = true
222 } else if (callFile === this._file) {
223 file = this._file
224 } else if (seen) {
225 break
226 }
227 }
228
229 var key = caller
230 ? depSite.join(':') + '__' + caller.join(':')
231 : undefined
232
233 if (key !== undefined && key in this._warned) {
234 // already warned
235 return
236 }
237
238 this._warned[key] = true
239
240 // generate automatic message from call site
241 var msg = message
242 if (!msg) {
243 msg = callSite === depSite || !callSite.name
244 ? defaultMessage(depSite)
245 : defaultMessage(callSite)
246 }
247
248 // emit deprecation if listeners exist
249 if (haslisteners) {
250 var err = DeprecationError(this._namespace, msg, stack.slice(i))
251 process.emit('deprecation', err)
252 return
253 }
254
255 // format and write message
256 var format = process.stderr.isTTY
257 ? formatColor
258 : formatPlain
259 var output = format.call(this, msg, caller, stack.slice(i))
260 process.stderr.write(output + '\n', 'utf8')
261}
262
263/**
264 * Get call site location as array.
265 */
266
267function callSiteLocation (callSite) {
268 var file = callSite.getFileName() || '<anonymous>'
269 var line = callSite.getLineNumber()
270 var colm = callSite.getColumnNumber()
271
272 if (callSite.isEval()) {
273 file = callSite.getEvalOrigin() + ', ' + file
274 }
275
276 var site = [file, line, colm]
277
278 site.callSite = callSite
279 site.name = callSite.getFunctionName()
280
281 return site
282}
283
284/**
285 * Generate a default message from the site.
286 */
287
288function defaultMessage (site) {
289 var callSite = site.callSite
290 var funcName = site.name
291
292 // make useful anonymous name
293 if (!funcName) {
294 funcName = '<anonymous@' + formatLocation(site) + '>'
295 }
296
297 var context = callSite.getThis()
298 var typeName = context && callSite.getTypeName()
299
300 // ignore useless type name
301 if (typeName === 'Object') {
302 typeName = undefined
303 }
304
305 // make useful type name
306 if (typeName === 'Function') {
307 typeName = context.name || typeName
308 }
309
310 return typeName && callSite.getMethodName()
311 ? typeName + '.' + funcName
312 : funcName
313}
314
315/**
316 * Format deprecation message without color.
317 */
318
319function formatPlain (msg, caller, stack) {
320 var timestamp = new Date().toUTCString()
321
322 var formatted = timestamp +
323 ' ' + this._namespace +
324 ' deprecated ' + msg
325
326 // add stack trace
327 if (this._traced) {
328 for (var i = 0; i < stack.length; i++) {
329 formatted += '\n at ' + stack[i].toString()
330 }
331
332 return formatted
333 }
334
335 if (caller) {
336 formatted += ' at ' + formatLocation(caller)
337 }
338
339 return formatted
340}
341
342/**
343 * Format deprecation message with color.
344 */
345
346function formatColor (msg, caller, stack) {
347 var formatted = '\x1b[36;1m' + this._namespace + '\x1b[22;39m' + // bold cyan
348 ' \x1b[33;1mdeprecated\x1b[22;39m' + // bold yellow
349 ' \x1b[0m' + msg + '\x1b[39m' // reset
350
351 // add stack trace
352 if (this._traced) {
353 for (var i = 0; i < stack.length; i++) {
354 formatted += '\n \x1b[36mat ' + stack[i].toString() + '\x1b[39m' // cyan
355 }
356
357 return formatted
358 }
359
360 if (caller) {
361 formatted += ' \x1b[36m' + formatLocation(caller) + '\x1b[39m' // cyan
362 }
363
364 return formatted
365}
366
367/**
368 * Format call site location.
369 */
370
371function formatLocation (callSite) {
372 return relative(basePath, callSite[0]) +
373 ':' + callSite[1] +
374 ':' + callSite[2]
375}
376
377/**
378 * Get the stack as array of call sites.
379 */
380
381function getStack () {
382 var limit = Error.stackTraceLimit
383 var obj = {}
384 var prep = Error.prepareStackTrace
385
386 Error.prepareStackTrace = prepareObjectStackTrace
387 Error.stackTraceLimit = Math.max(10, limit)
388
389 // capture the stack
390 Error.captureStackTrace(obj)
391
392 // slice this function off the top
393 var stack = obj.stack.slice(1)
394
395 Error.prepareStackTrace = prep
396 Error.stackTraceLimit = limit
397
398 return stack
399}
400
401/**
402 * Capture call site stack from v8.
403 */
404
405function prepareObjectStackTrace (obj, stack) {
406 return stack
407}
408
409/**
410 * Return a wrapped function in a deprecation message.
411 */
412
413function wrapfunction (fn, message) {
414 if (typeof fn !== 'function') {
415 throw new TypeError('argument fn must be a function')
416 }
417
418 var args = createArgumentsString(fn.length)
419 var stack = getStack()
420 var site = callSiteLocation(stack[1])
421
422 site.name = fn.name
423
424 // eslint-disable-next-line no-new-func
425 var deprecatedfn = new Function('fn', 'log', 'deprecate', 'message', 'site',
426 '"use strict"\n' +
427 'return function (' + args + ') {' +
428 'log.call(deprecate, message, site)\n' +
429 'return fn.apply(this, arguments)\n' +
430 '}')(fn, log, this, message, site)
431
432 return deprecatedfn
433}
434
435/**
436 * Wrap property in a deprecation message.
437 */
438
439function wrapproperty (obj, prop, message) {
440 if (!obj || (typeof obj !== 'object' && typeof obj !== 'function')) {
441 throw new TypeError('argument obj must be object')
442 }
443
444 var descriptor = Object.getOwnPropertyDescriptor(obj, prop)
445
446 if (!descriptor) {
447 throw new TypeError('must call property on owner object')
448 }
449
450 if (!descriptor.configurable) {
451 throw new TypeError('property must be configurable')
452 }
453
454 var deprecate = this
455 var stack = getStack()
456 var site = callSiteLocation(stack[1])
457
458 // set site name
459 site.name = prop
460
461 // convert data descriptor
462 if ('value' in descriptor) {
463 descriptor = convertDataDescriptorToAccessor(obj, prop, message)
464 }
465
466 var get = descriptor.get
467 var set = descriptor.set
468
469 // wrap getter
470 if (typeof get === 'function') {
471 descriptor.get = function getter () {
472 log.call(deprecate, message, site)
473 return get.apply(this, arguments)
474 }
475 }
476
477 // wrap setter
478 if (typeof set === 'function') {
479 descriptor.set = function setter () {
480 log.call(deprecate, message, site)
481 return set.apply(this, arguments)
482 }
483 }
484
485 Object.defineProperty(obj, prop, descriptor)
486}
487
488/**
489 * Create DeprecationError for deprecation
490 */
491
492function DeprecationError (namespace, message, stack) {
493 var error = new Error()
494 var stackString
495
496 Object.defineProperty(error, 'constructor', {
497 value: DeprecationError
498 })
499
500 Object.defineProperty(error, 'message', {
501 configurable: true,
502 enumerable: false,
503 value: message,
504 writable: true
505 })
506
507 Object.defineProperty(error, 'name', {
508 enumerable: false,
509 configurable: true,
510 value: 'DeprecationError',
511 writable: true
512 })
513
514 Object.defineProperty(error, 'namespace', {
515 configurable: true,
516 enumerable: false,
517 value: namespace,
518 writable: true
519 })
520
521 Object.defineProperty(error, 'stack', {
522 configurable: true,
523 enumerable: false,
524 get: function () {
525 if (stackString !== undefined) {
526 return stackString
527 }
528
529 // prepare stack trace
530 return (stackString = createStackString.call(this, stack))
531 },
532 set: function setter (val) {
533 stackString = val
534 }
535 })
536
537 return error
538}