Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert extend util to TypeScript #2928

Merged
merged 18 commits into from
Nov 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions js/src/@types/global.d.ts
Original file line number Diff line number Diff line change
@@ -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.
*
Expand Down
2 changes: 1 addition & 1 deletion js/src/common/Application.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
2 changes: 1 addition & 1 deletion js/src/common/compat.js
Original file line number Diff line number Diff line change
@@ -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';
Expand Down
42 changes: 25 additions & 17 deletions js/src/common/extend.js → js/src/common/extend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
davwheat marked this conversation as resolved.
Show resolved Hide resolved
) {
const allMethods = Array.isArray(methods) ? methods : [methods];

allMethods.forEach((method) => {
const original = object[method];
allMethods.forEach((method: K) => {
const original: Function | undefined = object[method];
davwheat marked this conversation as resolved.
Show resolved Hide resolved

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);
});
Expand Down Expand Up @@ -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);
});
Expand Down
10 changes: 6 additions & 4 deletions js/src/common/utils/proxifyCompat.ts
Original file line number Diff line number Diff line change
@@ -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, '')],
});
};
}