Convert extend util to TypeScript (#2928)

* Allow using file extension in core compat imports

Necessary for extend imports to have proper typings as we also have an unrelated extend/index.js file

* Add .ts file extension to extend imports for typings

* Fix changes to proxifyCompat regex breaking non-core import paths

* Move utility types to global types

Co-authored-by: David Wheatley <hi@davwheat.dev>
This commit is contained in:
David Sevilla Martin 2021-11-08 16:52:13 -05:00 committed by GitHub
parent a07171cf1f
commit 1a695dee2c
5 changed files with 51 additions and 23 deletions

View File

@ -1,3 +1,21 @@
/**
* UTILITY TYPES
*/
/**
* Type that returns an array of all keys of a provided object that are of
* 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];
/**
* @deprecated Please import `app` from a namespace instead of using it as a global variable.
*

View File

@ -14,7 +14,7 @@ import mapRoutes from './utils/mapRoutes';
import RequestError from './utils/RequestError';
import ScrollListener from './utils/ScrollListener';
import liveHumanTimes from './utils/liveHumanTimes';
import { extend } from './extend';
import { extend } from './extend.ts';
import Forum from './models/Forum';
import User from './models/User';

View File

@ -1,4 +1,4 @@
import * as extend from './extend';
import * as extend from './extend.ts';
import Session from './Session';
import Store from './Store';
import BasicEditorDriver from './utils/BasicEditorDriver';

View File

@ -19,23 +19,27 @@
* // something that needs to be run on creation and update
* });
*
* @param {object} object The object that owns the method
* @param {string|string[]} methods The name or names of the method(s) to extend
* @param {function} callback A callback which mutates the method's output
* @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
*/
export function extend(object, methods, callback) {
export function extend<T extends object, K extends KeyOfType<T, Function>>(
object: T,
methods: K | K[],
callback: (this: T, val: ReturnType<T[K]>, ...args: Parameters<T[K]>) => void
) {
const allMethods = Array.isArray(methods) ? methods : [methods];
allMethods.forEach((method) => {
const original = object[method];
allMethods.forEach((method: K) => {
const original: Function | undefined = object[method];
object[method] = function (...args) {
object[method] = function (this: T, ...args: Parameters<T[K]>) {
const value = original ? original.apply(this, args) : undefined;
callback.apply(this, [value].concat(args));
callback.apply(this, [value, ...args]);
return value;
};
} as T[K];
Object.assign(object[method], original);
});
@ -64,19 +68,23 @@ export function extend(object, methods, callback) {
* // something that needs to be run on creation and update
* });
*
* @param {object} object The object that owns the method
* @param {string|string[]} method The name or names of the method(s) to override
* @param {function} newMethod The method to replace it with
* @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
*/
export function override(object, methods, newMethod) {
export function override<T extends object, K extends KeyOfType<T, Function>>(
object: T,
methods: K | K[],
newMethod: (this: T, orig: T[K], ...args: Parameters<T[K]>) => void
) {
const allMethods = Array.isArray(methods) ? methods : [methods];
allMethods.forEach((method) => {
const original = object[method];
const original: Function = object[method];
object[method] = function (...args) {
return newMethod.apply(this, [original.bind(this)].concat(args));
};
object[method] = function (this: T, ...args: Parameters<T[K]>) {
return newMethod.apply(this, [original.bind(this), ...args]);
} as T[K];
Object.assign(object[method], original);
});

View File

@ -1,10 +1,12 @@
export default (compat: { [key: string]: any }, namespace: string) => {
export default function proxifyCompat(compat: Record<string, unknown>, namespace: string) {
// regex to replace common/ and NAMESPACE/ for core & core extensions
// and remove .js, .ts and .tsx extensions
// e.g. admin/utils/extract --> utils/extract
// e.g. tags/common/utils/sortTags --> tags/utils/sortTags
const regex = new RegExp(`(\\w+\\/)?(${namespace}|common)\\/`);
const regex = new RegExp(String.raw`(\w+\/)?(${namespace}|common)\/`);
const fileExt = /(\.js|\.tsx?)$/;
return new Proxy(compat, {
get: (obj, prop: string) => obj[prop] || obj[prop.replace(regex, '$1')],
get: (obj, prop: string) => obj[prop] || obj[prop.replace(regex, '$1').replace(fileExt, '')],
});
};
}