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