deprecate.ts 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. type DeprecationHandler = (message: string) => void;
  2. let deprecationHandler: DeprecationHandler | null = null;
  3. export function warnOnce (oldName: string, newName?: string) {
  4. return warnOnceMessage(newName
  5. ? `'${oldName}' is deprecated and will be removed. Please use '${newName}' instead.`
  6. : `'${oldName}' is deprecated and will be removed.`);
  7. }
  8. export function warnOnceMessage (msg: string) {
  9. let warned = false;
  10. return () => {
  11. if (!warned && !process.noDeprecation) {
  12. warned = true;
  13. log(msg);
  14. }
  15. };
  16. }
  17. export function setHandler (handler: DeprecationHandler | null): void {
  18. deprecationHandler = handler;
  19. }
  20. export function getHandler (): DeprecationHandler | null {
  21. return deprecationHandler;
  22. }
  23. export function warn (oldName: string, newName: string): void {
  24. if (!process.noDeprecation) {
  25. log(`'${oldName}' is deprecated. Use '${newName}' instead.`);
  26. }
  27. }
  28. export function log (message: string): void {
  29. if (typeof deprecationHandler === 'function') {
  30. deprecationHandler(message);
  31. } else if (process.throwDeprecation) {
  32. throw new Error(message);
  33. } else if (process.traceDeprecation) {
  34. return console.trace(message);
  35. } else {
  36. return console.warn(`(electron) ${message}`);
  37. }
  38. }
  39. // remove a function with no replacement
  40. export function removeFunction<T extends Function> (fn: T, removedName: string): T {
  41. if (!fn) { throw new Error(`'${removedName} function' is invalid or does not exist.`); }
  42. // wrap the deprecated function to warn user
  43. const warn = warnOnce(`${fn.name} function`);
  44. return function (this: any) {
  45. warn();
  46. fn.apply(this, arguments);
  47. } as unknown as typeof fn;
  48. }
  49. // change the name of a function
  50. export function renameFunction<T extends Function> (fn: T, newName: string): T {
  51. const warn = warnOnce(`${fn.name} function`, `${newName} function`);
  52. return function (this: any) {
  53. warn();
  54. return fn.apply(this, arguments);
  55. } as unknown as typeof fn;
  56. }
  57. // change the name of an event
  58. export function event (emitter: NodeJS.EventEmitter, oldName: string, newName: string, transformer: (...args: any[]) => any[] | undefined = (...args) => args) {
  59. const warn = newName.startsWith('-') /* internal event */
  60. ? warnOnce(`${oldName} event`)
  61. : warnOnce(`${oldName} event`, `${newName} event`);
  62. return emitter.on(newName, function (this: NodeJS.EventEmitter, ...args) {
  63. if (this.listenerCount(oldName) !== 0) {
  64. warn();
  65. const transformedArgs = transformer(...args);
  66. if (transformedArgs) {
  67. this.emit(oldName, ...transformedArgs);
  68. }
  69. }
  70. });
  71. }
  72. // remove a property with no replacement
  73. export function removeProperty<T extends Object, K extends (keyof T & string)>(object: T, removedName: K, onlyForValues?: any[]): T {
  74. // if the property's already been removed, warn about it
  75. // eslint-disable-next-line no-proto
  76. const info = Object.getOwnPropertyDescriptor((object as any).__proto__, removedName);
  77. if (!info) {
  78. log(`Unable to remove property '${removedName}' from an object that lacks it.`);
  79. return object;
  80. }
  81. if (!info.get || !info.set) {
  82. log(`Unable to remove property '${removedName}' from an object does not have a getter / setter`);
  83. return object;
  84. }
  85. // wrap the deprecated property in an accessor to warn
  86. const warn = warnOnce(removedName);
  87. return Object.defineProperty(object, removedName, {
  88. configurable: true,
  89. get: () => {
  90. warn();
  91. return info.get!.call(object);
  92. },
  93. set: newVal => {
  94. if (!onlyForValues || onlyForValues.includes(newVal)) {
  95. warn();
  96. }
  97. return info.set!.call(object, newVal);
  98. }
  99. });
  100. }
  101. // change the name of a property
  102. export function renameProperty<T extends Object, K extends (keyof T & string)>(object: T, oldName: string, newName: K): T {
  103. const warn = warnOnce(oldName, newName);
  104. // if the new property isn't there yet,
  105. // inject it and warn about it
  106. if ((oldName in object) && !(newName in object)) {
  107. warn();
  108. object[newName] = (object as any)[oldName];
  109. }
  110. // wrap the deprecated property in an accessor to warn
  111. // and redirect to the new property
  112. return Object.defineProperty(object, oldName, {
  113. get: () => {
  114. warn();
  115. return object[newName];
  116. },
  117. set: value => {
  118. warn();
  119. object[newName] = value;
  120. }
  121. });
  122. }
  123. export function moveAPI<T extends Function> (fn: T, oldUsage: string, newUsage: string): T {
  124. const warn = warnOnce(oldUsage, newUsage);
  125. return function (this: any) {
  126. warn();
  127. return fn.apply(this, arguments);
  128. } as unknown as typeof fn;
  129. }