index.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. /*!
  2. * depd
  3. * Copyright(c) 2014 Douglas Christopher Wilson
  4. * MIT Licensed
  5. */
  6. /**
  7. * Module dependencies.
  8. */
  9. var EventEmitter = require('events').EventEmitter
  10. var relative = require('path').relative
  11. /**
  12. * Module exports.
  13. */
  14. module.exports = depd
  15. /**
  16. * Get the path to base files on.
  17. */
  18. var basePath = process.cwd()
  19. /**
  20. * Get listener count on event emitter.
  21. */
  22. /*istanbul ignore next*/
  23. var eventListenerCount = EventEmitter.listenerCount
  24. || function (emitter, type) { return emitter.listeners(type).length }
  25. /**
  26. * Convert a data descriptor to accessor descriptor.
  27. */
  28. function convertDataDescriptorToAccessor(obj, prop, message) {
  29. var descriptor = Object.getOwnPropertyDescriptor(obj, prop)
  30. var value = descriptor.value
  31. descriptor.get = function getter() { return value }
  32. if (descriptor.writable) {
  33. descriptor.set = function setter(val) { return value = val }
  34. }
  35. delete descriptor.value
  36. delete descriptor.writable
  37. Object.defineProperty(obj, prop, descriptor)
  38. return descriptor
  39. }
  40. /**
  41. * Create arguments string to keep arity.
  42. */
  43. function createArgumentsString(arity) {
  44. var str = ''
  45. for (var i = 0; i < arity; i++) {
  46. str += ', arg' + i
  47. }
  48. return str.substr(2)
  49. }
  50. /**
  51. * Create stack string from stack.
  52. */
  53. function createStackString(stack) {
  54. var str = this.name + ': ' + this.namespace
  55. if (this.message) {
  56. str += ' deprecated ' + this.message
  57. }
  58. for (var i = 0; i < stack.length; i++) {
  59. str += '\n at ' + stack[i].toString()
  60. }
  61. return str
  62. }
  63. /**
  64. * Create deprecate for namespace in caller.
  65. */
  66. function depd(namespace) {
  67. if (!namespace) {
  68. throw new TypeError('argument namespace is required')
  69. }
  70. var stack = getStack()
  71. var site = callSiteLocation(stack[1])
  72. var file = site[0]
  73. function deprecate(message) {
  74. // call to self as log
  75. log.call(deprecate, message)
  76. }
  77. deprecate._file = file
  78. deprecate._ignored = isignored(namespace)
  79. deprecate._namespace = namespace
  80. deprecate._warned = Object.create(null)
  81. deprecate.function = wrapfunction
  82. deprecate.property = wrapproperty
  83. return deprecate
  84. }
  85. /**
  86. * Determine if namespace is ignored.
  87. */
  88. function isignored(namespace) {
  89. var str = process.env.NO_DEPRECATION || ''
  90. var val = str.split(/[ ,]+/)
  91. namespace = String(namespace).toLowerCase()
  92. for (var i = 0 ; i < val.length; i++) {
  93. if (!(str = val[i])) continue;
  94. // namespace ignored
  95. if (str === '*' || str.toLowerCase() === namespace) {
  96. return true
  97. }
  98. }
  99. return false
  100. }
  101. /**
  102. * Display deprecation message.
  103. */
  104. function log(message, site) {
  105. var haslisteners = eventListenerCount(process, 'deprecation') !== 0
  106. // abort early if no destination
  107. if (!haslisteners && this._ignored) {
  108. return
  109. }
  110. var caller
  111. var callSite
  112. var i = 0
  113. var seen = false
  114. var stack = getStack()
  115. var file = this._file
  116. if (site) {
  117. // provided site
  118. callSite = callSiteLocation(stack[1])
  119. callSite.name = site.name
  120. file = callSite[0]
  121. } else {
  122. // get call site
  123. i = 2
  124. site = callSiteLocation(stack[i])
  125. callSite = site
  126. }
  127. // get caller of deprecated thing in relation to file
  128. for (; i < stack.length; i++) {
  129. caller = callSiteLocation(stack[i])
  130. if (caller[0] === file) {
  131. seen = true
  132. } else if (seen) {
  133. break
  134. }
  135. }
  136. var key = caller
  137. ? site.join(':') + '__' + caller.join(':')
  138. : undefined
  139. if (key !== undefined && key in this._warned) {
  140. // already warned
  141. return
  142. }
  143. this._warned[key] = true
  144. // generate automatic message from call site
  145. if (!message) {
  146. message = callSite === site || !callSite.name
  147. ? defaultMessage(site)
  148. : defaultMessage(callSite)
  149. }
  150. // emit deprecation if listeners exist
  151. if (haslisteners) {
  152. var err = DeprecationError(this._namespace, message, stack.slice(i))
  153. process.emit('deprecation', err)
  154. return
  155. }
  156. // format and write message
  157. var format = process.stderr.isTTY
  158. ? formatColor
  159. : formatPlain
  160. var msg = format.call(this, message, caller)
  161. process.stderr.write(msg + '\n', 'utf8')
  162. return
  163. }
  164. /**
  165. * Get call site location as array.
  166. */
  167. function callSiteLocation(callSite) {
  168. var file = callSite.getFileName() || '<anonymous>'
  169. var line = callSite.getLineNumber()
  170. var colm = callSite.getColumnNumber()
  171. if (callSite.isEval()) {
  172. file = callSite.getEvalOrigin() + ', ' + file
  173. }
  174. var site = [file, line, colm]
  175. site.callSite = callSite
  176. site.name = callSite.getFunctionName()
  177. return site
  178. }
  179. /**
  180. * Generate a default message from the site.
  181. */
  182. function defaultMessage(site) {
  183. var callSite = site.callSite
  184. var funcName = site.name
  185. // make useful anonymous name
  186. if (!funcName) {
  187. funcName = '<anonymous@' + formatLocation(site) + '>'
  188. }
  189. return callSite.getMethodName()
  190. ? callSite.getTypeName() + '.' + funcName
  191. : funcName
  192. }
  193. /**
  194. * Format deprecation message without color.
  195. */
  196. function formatPlain(msg, caller) {
  197. var timestamp = new Date().toUTCString()
  198. var formatted = timestamp
  199. + ' ' + this._namespace
  200. + ' deprecated ' + msg
  201. if (caller) {
  202. formatted += ' at ' + formatLocation(caller)
  203. }
  204. return formatted
  205. }
  206. /**
  207. * Format deprecation message with color.
  208. */
  209. function formatColor(msg, caller) {
  210. var formatted = '\x1b[36;1m' + this._namespace + '\x1b[22;39m' // bold cyan
  211. + ' \x1b[33;1mdeprecated\x1b[22;39m' // bold yellow
  212. + ' \x1b[90m' + msg + '\x1b[39m' // grey
  213. if (caller) {
  214. formatted += ' \x1b[36m' + formatLocation(caller) + '\x1b[39m' // cyan
  215. }
  216. return formatted
  217. }
  218. /**
  219. * Format call site location.
  220. */
  221. function formatLocation(callSite) {
  222. return relative(basePath, callSite[0])
  223. + ':' + callSite[1]
  224. + ':' + callSite[2]
  225. }
  226. /**
  227. * Get the stack as array of call sites.
  228. */
  229. function getStack() {
  230. var obj = {}
  231. var prep = Error.prepareStackTrace
  232. Error.prepareStackTrace = prepareObjectStackTrace
  233. Error.captureStackTrace(obj, getStack)
  234. var stack = obj.stack
  235. Error.prepareStackTrace = prep
  236. return stack
  237. }
  238. /**
  239. * Capture call site stack from v8.
  240. */
  241. function prepareObjectStackTrace(obj, stack) {
  242. return stack
  243. }
  244. /**
  245. * Return a wrapped function in a deprecation message.
  246. */
  247. function wrapfunction(fn, message) {
  248. if (typeof fn !== 'function') {
  249. throw new TypeError('argument fn must be a function')
  250. }
  251. var args = createArgumentsString(fn.length)
  252. var deprecate = this
  253. var stack = getStack()
  254. var site = callSiteLocation(stack[1])
  255. site.name = fn.name
  256. var deprecatedfn = eval('(function (' + args + ') {\n'
  257. + 'log.call(deprecate, message, site)\n'
  258. + 'return fn.apply(this, arguments)\n'
  259. + '})')
  260. return deprecatedfn
  261. }
  262. /**
  263. * Wrap property in a deprecation message.
  264. */
  265. function wrapproperty(obj, prop, message) {
  266. if (!obj || typeof obj !== 'object') {
  267. throw new TypeError('argument obj must be object')
  268. }
  269. var descriptor = Object.getOwnPropertyDescriptor(obj, prop)
  270. if (!descriptor) {
  271. throw new TypeError('must call property on owner object')
  272. }
  273. if (!descriptor.configurable) {
  274. throw new TypeError('property must be configurable')
  275. }
  276. var deprecate = this
  277. var stack = getStack()
  278. var site = callSiteLocation(stack[1])
  279. // set site name
  280. site.name = prop
  281. // convert data descriptor
  282. if ('value' in descriptor) {
  283. descriptor = convertDataDescriptorToAccessor(obj, prop, message)
  284. }
  285. var get = descriptor.get
  286. var set = descriptor.set
  287. // wrap getter
  288. if (typeof get === 'function') {
  289. descriptor.get = function getter() {
  290. log.call(deprecate, message, site)
  291. return get.apply(this, arguments)
  292. }
  293. }
  294. // wrap setter
  295. if (typeof set === 'function') {
  296. descriptor.set = function setter() {
  297. log.call(deprecate, message, site)
  298. return set.apply(this, arguments)
  299. }
  300. }
  301. Object.defineProperty(obj, prop, descriptor)
  302. }
  303. /**
  304. * Create DeprecationError for deprecation
  305. */
  306. function DeprecationError(namespace, message, stack) {
  307. var error = new Error()
  308. var stackString
  309. Object.defineProperty(error, 'constructor', {
  310. value: DeprecationError
  311. })
  312. Object.defineProperty(error, 'message', {
  313. configurable: true,
  314. enumerable: false,
  315. value: message,
  316. writable: true
  317. })
  318. Object.defineProperty(error, 'name', {
  319. enumerable: false,
  320. configurable: true,
  321. value: 'DeprecationError',
  322. writable: true
  323. })
  324. Object.defineProperty(error, 'namespace', {
  325. configurable: true,
  326. enumerable: false,
  327. value: namespace,
  328. writable: true
  329. })
  330. Object.defineProperty(error, 'stack', {
  331. configurable: true,
  332. enumerable: false,
  333. get: function () {
  334. if (stackString !== undefined) {
  335. return stackString
  336. }
  337. // prepare stack trace
  338. return stackString = createStackString.call(this, stack)
  339. },
  340. set: function setter(val) {
  341. stackString = val
  342. }
  343. })
  344. return error
  345. }