index.js 11 KB

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