2021-08-26 20:12:42 +02:00
|
|
|
|
/**
|
|
|
|
|
* @source https://github.com/flarum/core/blob/master/js/src/common/extend.js
|
|
|
|
|
*/
|
|
|
|
|
|
2022-01-06 15:45:35 +01:00
|
|
|
|
/**
|
|
|
|
|
* Type that returns an array of all keys of a provided object that are of the provided type,
|
|
|
|
|
* or a subtype of the type.
|
|
|
|
|
*/
|
|
|
|
|
declare type KeysOfType<Type extends object, Match> = {
|
|
|
|
|
[Key in keyof Type]-?: Type[Key] extends Match ? Key : never;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Type that matches one of the keys of an object that is of the provided
|
|
|
|
|
* type, or a subtype of it.
|
|
|
|
|
*/
|
|
|
|
|
declare type KeyOfType<Type extends object, Match> = KeysOfType<Type, Match>[keyof Type];
|
|
|
|
|
|
2021-08-26 20:12:42 +02:00
|
|
|
|
/**
|
|
|
|
|
* Extend an object's method by running its output through a mutating callback
|
|
|
|
|
* every time it is called.
|
|
|
|
|
*
|
|
|
|
|
* The callback accepts the method's return value and should perform any
|
|
|
|
|
* mutations directly on this value. For this reason, this function will not be
|
2022-01-06 15:45:35 +01:00
|
|
|
|
* effective on methods, which return scalar values (numbers, strings, booleans).
|
2021-08-26 20:12:42 +02:00
|
|
|
|
*
|
2022-01-06 15:45:35 +01:00
|
|
|
|
* Care should be taken to extend the correct object — usually, a class'
|
2021-08-26 20:12:42 +02:00
|
|
|
|
* prototype will be the desired target of extension, not the class itself.
|
|
|
|
|
*
|
|
|
|
|
* @example <caption>Example usage of extending one method.</caption>
|
|
|
|
|
* extend(Discussion.prototype, 'badges', function(badges) {
|
|
|
|
|
* // do something with `badges`
|
|
|
|
|
* });
|
|
|
|
|
*
|
|
|
|
|
* @example <caption>Example usage of extending multiple methods.</caption>
|
|
|
|
|
* extend(IndexPage.prototype, ['oncreate', 'onupdate'], function(vnode) {
|
|
|
|
|
* // something that needs to be run on creation and update
|
|
|
|
|
* });
|
|
|
|
|
*
|
2022-01-06 15:45:35 +01:00
|
|
|
|
* @param object The object that owns the method
|
|
|
|
|
* @param methods The name or names of the method(s) to extend
|
|
|
|
|
* @param callback A callback which mutates the method's output
|
2021-08-26 20:12:42 +02:00
|
|
|
|
*/
|
2022-01-06 15:45:35 +01:00
|
|
|
|
export function extend<T extends Record<string, any>, K extends KeyOfType<T, Function>>(
|
|
|
|
|
object: T,
|
|
|
|
|
methods: K | K[],
|
|
|
|
|
callback: (this: T, value: ReturnType<T[K]>, ...arguments_: Parameters<T[K]>) => void
|
|
|
|
|
) {
|
2021-08-26 20:12:42 +02:00
|
|
|
|
const allMethods = Array.isArray(methods) ? methods : [methods];
|
|
|
|
|
|
2021-09-07 13:37:18 +02:00
|
|
|
|
for (const method of allMethods) {
|
2022-01-06 15:45:35 +01:00
|
|
|
|
const original: Function | undefined = object[method];
|
2021-08-26 20:12:42 +02:00
|
|
|
|
|
2022-01-06 15:45:35 +01:00
|
|
|
|
object[method] = function (this: T, ...arguments_: Parameters<T[K]>): any {
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
2021-09-07 13:37:18 +02:00
|
|
|
|
const value = original ? original.apply(this, arguments_) : undefined;
|
2021-08-26 20:12:42 +02:00
|
|
|
|
|
2021-09-07 13:37:18 +02:00
|
|
|
|
Reflect.apply(callback, this, [value, ...arguments_]);
|
2021-08-26 20:12:42 +02:00
|
|
|
|
|
|
|
|
|
return value;
|
2022-01-06 15:45:35 +01:00
|
|
|
|
} as T[K];
|
2021-08-26 20:12:42 +02:00
|
|
|
|
|
2022-01-06 15:45:35 +01:00
|
|
|
|
Object.assign(object[method], original);
|
2021-09-07 13:37:18 +02:00
|
|
|
|
}
|
2021-08-26 20:12:42 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Override an object's method by replacing it with a new function, so that the
|
|
|
|
|
* new function will be run every time the object's method is called.
|
|
|
|
|
*
|
|
|
|
|
* The replacement function accepts the original method as its first argument,
|
|
|
|
|
* which is like a call to `super`. Any arguments passed to the original method
|
|
|
|
|
* are also passed to the replacement.
|
|
|
|
|
*
|
|
|
|
|
* Care should be taken to extend the correct object – in most cases, a class'
|
|
|
|
|
* prototype will be the desired target of extension, not the class itself.
|
|
|
|
|
*
|
|
|
|
|
* @example <caption>Example usage of overriding one method.</caption>
|
|
|
|
|
* override(Discussion.prototype, 'badges', function(original) {
|
|
|
|
|
* const badges = original();
|
|
|
|
|
* // do something with badges
|
|
|
|
|
* return badges;
|
|
|
|
|
* });
|
|
|
|
|
*
|
|
|
|
|
* @example <caption>Example usage of overriding multiple methods.</caption>
|
|
|
|
|
* extend(Discussion.prototype, ['oncreate', 'onupdate'], function(original, vnode) {
|
|
|
|
|
* // something that needs to be run on creation and update
|
|
|
|
|
* });
|
|
|
|
|
*
|
2022-01-06 15:45:35 +01:00
|
|
|
|
* @param object The object that owns the method
|
|
|
|
|
* @param methods The name or names of the method(s) to override
|
|
|
|
|
* @param newMethod The method to replace it with
|
2021-08-26 20:12:42 +02:00
|
|
|
|
*/
|
2022-01-06 15:45:35 +01:00
|
|
|
|
export function override<T extends Record<any, any>, K extends KeyOfType<T, Function>>(
|
|
|
|
|
object: T,
|
|
|
|
|
methods: K | K[],
|
|
|
|
|
newMethod: (this: T, orig: T[K], ...arguments_: Parameters<T[K]>) => void
|
|
|
|
|
) {
|
2021-08-26 20:12:42 +02:00
|
|
|
|
const allMethods = Array.isArray(methods) ? methods : [methods];
|
|
|
|
|
|
2021-09-07 13:37:18 +02:00
|
|
|
|
for (const method of allMethods) {
|
2022-01-06 15:45:35 +01:00
|
|
|
|
const original: Function = object[method];
|
2021-08-26 20:12:42 +02:00
|
|
|
|
|
2022-01-06 15:45:35 +01:00
|
|
|
|
object[method] = function (this: T, ...arguments_: Parameters<T[K]>): any {
|
2021-09-07 13:37:18 +02:00
|
|
|
|
return Reflect.apply(newMethod, this, [original.bind(this), ...arguments_]);
|
2022-01-06 15:45:35 +01:00
|
|
|
|
} as T[K];
|
2021-08-26 20:12:42 +02:00
|
|
|
|
|
|
|
|
|
Object.assign(object[method], original);
|
2021-09-07 13:37:18 +02:00
|
|
|
|
}
|
2021-08-26 20:12:42 +02:00
|
|
|
|
}
|