index.js 10 KB

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