feat+refactor(metro&ui/proxy): print more detailed errors

This commit is contained in:
amsyarasyiq 2024-05-25 07:33:30 +08:00
parent 28b02ae60a
commit abf89e3695
5 changed files with 53 additions and 39 deletions

View file

@ -1,4 +1,4 @@
import { getFindProxyInfo } from "@metro/proxy";
import { getFindContext } from "@metro/proxy";
import {
after as _after,
before as _before,
@ -10,7 +10,7 @@ type ProxyPatchParameters<T, P> = [func: string, parent: [P, (t: P) => any], ...
function shim<T extends (func: string, parent: any, ...args: ParametersAfterOneArgs<T>) => any>(fn: T) {
function shimmed(this: any, ...args: Parameters<T>) {
const proxyInfo = getFindProxyInfo(args[1]);
const proxyInfo = getFindContext(args[1]);
if (proxyInfo && !proxyInfo.cache && proxyInfo.indexed) {
let cancel = false;
let unpatch = () => cancel = true;
@ -27,7 +27,7 @@ function shim<T extends (func: string, parent: any, ...args: ParametersAfterOneA
shimmed.proxy = function proxyPatch<P>(this: any, ...args: ProxyPatchParameters<T, P>) {
const [target, resolve] = args[1];
const proxyInfo = getFindProxyInfo(target);
const proxyInfo = getFindContext(target);
if (!proxyInfo) {
args[1] = resolve(target);
return shimmed(...args as unknown as Parameters<T>);

View file

@ -1,54 +1,68 @@
const factorySymbol = Symbol.for("bunny.lazy.factory");
const cacheSymbol = Symbol.for("bunny.lazy.cache");
/* eslint-disable func-call-spacing */
const unconfigurable = ["arguments", "caller", "prototype"];
const isUnconfigurable = (key: PropertyKey) => typeof key === "string" && unconfigurable.includes(key);
// eslint-disable-next-line func-call-spacing
const proxyToFactoryMap = new WeakMap<any, () => any>();
const dummyToFactoryMap = new WeakMap<any, () => any>();
const lazyHandler: ProxyHandler<any> = {
...Object.fromEntries(Object.getOwnPropertyNames(Reflect).map(fnName => {
return [fnName, (target: any, ...args: any[]) => {
const resolved = dummyToFactoryMap.get(target)!();
if (!resolved) throw new Error(`Trying to ${fnName} of ${typeof resolved}`);
// @ts-expect-error
return Reflect[fnName](resolved, ...args);
}];
})),
ownKeys: target => {
const cacheKeys = Reflect.ownKeys(target[factorySymbol]());
unconfigurable.forEach(key => isUnconfigurable(key) && cacheKeys.push(key));
const resolved = dummyToFactoryMap.get(target)!();
if (!resolved) throw new Error(`Trying to ownKeys of ${typeof resolved}`);
const cacheKeys = Reflect.ownKeys(dummyToFactoryMap.get(target)!());
unconfigurable.forEach(key => !cacheKeys.includes(key) && cacheKeys.push(key));
return cacheKeys;
},
getOwnPropertyDescriptor: (target, p) => {
const resolved = dummyToFactoryMap.get(target)!();
if (!resolved) throw new Error(`Trying to getOwnPropertyDescriptor of ${typeof resolved}`);
if (isUnconfigurable(p)) return Reflect.getOwnPropertyDescriptor(target, p);
const descriptor = Reflect.getOwnPropertyDescriptor(target[factorySymbol](), p);
const descriptor = Reflect.getOwnPropertyDescriptor(dummyToFactoryMap.get(target)!(), p);
if (descriptor) Object.defineProperty(target, p, descriptor);
return descriptor;
}
},
};
// Mirror all Reflect methods
Object.getOwnPropertyNames(Reflect).forEach(fnName => {
// @ts-ignore
lazyHandler[fnName] ??= (target: any, ...args: any[]) => {
// @ts-ignore
return Reflect[fnName](target[factorySymbol](), ...args);
};
});
/**
* Lazy proxy that will only call the factory function when needed (when a property is accessed)
* @param factory Factory function to create the object
* @param fallback A fallback value to return if the factory returns undefined
* @param asFunction Mock the proxy as a function
* @returns A proxy that will call the factory function only when needed
* @example const ChannelStore = proxyLazy(() => findByProps("getChannelId"));
*/
export function proxyLazy<T>(factory: () => T, asFunction = true): T {
let cache: T;
const dummy = asFunction ? function () { } as any : {};
dummy[factorySymbol] = () => {
return dummy[cacheSymbol] ??= factory();
};
const proxyFactory = () => cache ??= factory();
const proxy = new Proxy(dummy, lazyHandler) as T;
proxyToFactoryMap.set(proxy, dummy[factorySymbol]);
proxyToFactoryMap.set(proxy, proxyFactory);
dummyToFactoryMap.set(dummy, proxyFactory);
return proxy;
}
/**
* Lazily destructure an object with all the properties being lazified. This assumes all the properties are either an object or a function
* @param factory Factory function which resolves to the object (and caches it)
* @param asFunction Mock the proxy as a function
* @example
*
* const { uuid4 } = lazyDestructure(() => findByProps("uuid4"))
* uuid4; // <- is a lazy proxy!
*/
export function lazyDestructure<T extends Record<PropertyKey, unknown>>(factory: () => T, asFunction = false): T {
const proxiedObject = proxyLazy(factory, asFunction);

View file

@ -5,9 +5,9 @@ import { findExports } from "./finders";
import { metroModules, subscribeModule } from "./modules";
import type { FilterFn } from "./types";
const proxyInfoMap = new WeakMap<{}, FindProxyInfo<any[]>>();
const proxyContextMap = new WeakMap<any, FindProxyContext<any[]>>();
interface FindProxyInfo<A extends unknown[]> {
interface FindProxyContext<A extends unknown[]> {
filter: FilterFn<A>;
indexed: boolean;
moduleId: number | undefined;
@ -24,8 +24,8 @@ function getIndexedSingleFind<A extends unknown[]>(filter: FilterFn<A>) {
return id ? Number(id) : void 0;
}
function subscribeModuleForFind(proxy: any, callback: (exports: any) => void) {
const info = getFindProxyInfo(proxy);
function subscribeModuleOfFind(proxy: any, callback: (exports: any) => void) {
const info = getFindContext(proxy);
if (!info) throw new Error("Subscribing a module for non-proxy-find");
if (!info.indexed) throw new Error("Attempting to subscribe to a non-indexed find");
@ -34,37 +34,39 @@ function subscribeModuleForFind(proxy: any, callback: (exports: any) => void) {
});
}
export function getFindProxyInfo<A extends unknown[]>(proxy: any): FindProxyInfo<A> | void {
return proxyInfoMap.get(proxy) as unknown as FindProxyInfo<A>;
export function getFindContext<A extends unknown[]>(proxy: any): FindProxyContext<A> | void {
return proxyContextMap.get(proxy) as unknown as FindProxyContext<A>;
}
export function createFindProxy<A extends unknown[]>(filter: FilterFn<A>) {
let cache: any = undefined;
const moduleId = getIndexedSingleFind(filter);
const info: FindProxyInfo<A> = {
const context: FindProxyContext<A> = {
filter,
indexed: !!moduleId,
moduleId,
getExports(cb: (exports: any) => void) {
if (!moduleId || metroModules[moduleId]?.isInitialized) {
return cb(findExports(filter)), () => { };
return cb(this.unproxy()), () => { };
}
return this.subscribe(cb);
},
subscribe(cb: (exports: any) => void) {
return subscribeModuleForFind(proxy, cb);
return subscribeModuleOfFind(proxy, cb);
},
get cache() {
return cache;
},
unproxy() {
return cache ??= findExports(filter);
cache ??= findExports(filter);
if (!cache) throw new Error(`${filter.uniq} is ${typeof cache}! (id ${context.moduleId ?? "unknown"})`);
return cache;
}
};
const proxy = proxyLazy(() => cache ??= findExports(filter));
proxyInfoMap.set(proxy, info as FindProxyInfo<any>);
const proxy = proxyLazy(() => context.unproxy());
proxyContextMap.set(proxy, context as FindProxyContext<any>);
return proxy;
}

View file

@ -138,7 +138,6 @@ export type FilterCheckDef<A extends unknown[]> = (
export interface FilterFn<A extends unknown[]> {
(m: any, id: number, defaultCheck: boolean): boolean;
filter: FilterCheckDef<A>;
args: A;
raw: boolean;
uniq: string;
}

View file

@ -12,7 +12,6 @@ export function createFilterDefinition<A extends unknown[]>(
return Object.assign(func, {
filter: fn,
raw,
args,
uniq: [
raw && "raw::",
uniqMaker(args)