From 1a695dee2c8cea5bc1e4038b29ef031960fa3236 Mon Sep 17 00:00:00 2001 From: David Sevilla Martin <6401250+datitisev@users.noreply.github.com> Date: Mon, 8 Nov 2021 16:52:13 -0500 Subject: [PATCH] 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 --- framework/core/js/src/@types/global.d.ts | 18 ++++++++ framework/core/js/src/common/Application.tsx | 2 +- framework/core/js/src/common/compat.js | 2 +- .../js/src/common/{extend.js => extend.ts} | 42 +++++++++++-------- .../core/js/src/common/utils/proxifyCompat.ts | 10 +++-- 5 files changed, 51 insertions(+), 23 deletions(-) rename framework/core/js/src/common/{extend.js => extend.ts} (66%) diff --git a/framework/core/js/src/@types/global.d.ts b/framework/core/js/src/@types/global.d.ts index 82019b82d..bffadbcc7 100644 --- a/framework/core/js/src/@types/global.d.ts +++ b/framework/core/js/src/@types/global.d.ts @@ -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 = { + [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 = KeysOfType[keyof Type]; + /** * @deprecated Please import `app` from a namespace instead of using it as a global variable. * diff --git a/framework/core/js/src/common/Application.tsx b/framework/core/js/src/common/Application.tsx index 3f22c3c38..210f41a70 100644 --- a/framework/core/js/src/common/Application.tsx +++ b/framework/core/js/src/common/Application.tsx @@ -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'; diff --git a/framework/core/js/src/common/compat.js b/framework/core/js/src/common/compat.js index cc615ae38..92e9fd41a 100644 --- a/framework/core/js/src/common/compat.js +++ b/framework/core/js/src/common/compat.js @@ -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'; diff --git a/framework/core/js/src/common/extend.js b/framework/core/js/src/common/extend.ts similarity index 66% rename from framework/core/js/src/common/extend.js rename to framework/core/js/src/common/extend.ts index 5a29411c6..a9b8b97b5 100644 --- a/framework/core/js/src/common/extend.js +++ b/framework/core/js/src/common/extend.ts @@ -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>( + object: T, + methods: K | K[], + callback: (this: T, val: ReturnType, ...args: Parameters) => 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) { 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>( + object: T, + methods: K | K[], + newMethod: (this: T, orig: T[K], ...args: Parameters) => 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) { + return newMethod.apply(this, [original.bind(this), ...args]); + } as T[K]; Object.assign(object[method], original); }); diff --git a/framework/core/js/src/common/utils/proxifyCompat.ts b/framework/core/js/src/common/utils/proxifyCompat.ts index 8ac7f05c9..36766a4f1 100644 --- a/framework/core/js/src/common/utils/proxifyCompat.ts +++ b/framework/core/js/src/common/utils/proxifyCompat.ts @@ -1,10 +1,12 @@ -export default (compat: { [key: string]: any }, namespace: string) => { +export default function proxifyCompat(compat: Record, 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, '')], }); -}; +}