/ node_modules / depd / index.js
index.js
  1  /*!
  2   * depd
  3   * Copyright(c) 2014-2018 Douglas Christopher Wilson
  4   * MIT Licensed
  5   */
  6  
  7  /**
  8   * Module dependencies.
  9   */
 10  
 11  var relative = require('path').relative
 12  
 13  /**
 14   * Module exports.
 15   */
 16  
 17  module.exports = depd
 18  
 19  /**
 20   * Get the path to base files on.
 21   */
 22  
 23  var basePath = process.cwd()
 24  
 25  /**
 26   * Determine if namespace is contained in the string.
 27   */
 28  
 29  function 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  
 49  function 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  
 71  function 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  
 85  function 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  
103  function 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  
141  function 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  
153  function 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  
169  function 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  
185  function 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  
267  function 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  
288  function 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  
319  function 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  
346  function 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  
371  function 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  
381  function 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  
405  function prepareObjectStackTrace (obj, stack) {
406    return stack
407  }
408  
409  /**
410   * Return a wrapped function in a deprecation message.
411   */
412  
413  function 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  
439  function 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  
492  function 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  }