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. * @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 RequestError from './utils/RequestError';
import ScrollListener from './utils/ScrollListener'; import ScrollListener from './utils/ScrollListener';
import liveHumanTimes from './utils/liveHumanTimes'; import liveHumanTimes from './utils/liveHumanTimes';
import { extend } from './extend'; import { extend } from './extend.ts';
import Forum from './models/Forum'; import Forum from './models/Forum';
import User from './models/User'; 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 Session from './Session';
import Store from './Store'; import Store from './Store';
import BasicEditorDriver from './utils/BasicEditorDriver'; import BasicEditorDriver from './utils/BasicEditorDriver';

View File

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