revenge-bundle-next/lib/patcher/src/hooks/instead.ts

116 lines
3.1 KiB
TypeScript
Raw Normal View History

2025-03-30 00:28:06 +07:00
import {
createPatchedFunctionProxy,
2025-04-03 23:00:51 +07:00
type FunctionProxyState,
type InsteadHookNode,
type PatchedFunctionProxyState,
2025-03-30 00:28:06 +07:00
patchedFunctionProxyHandler,
patchedFunctionProxyStates,
unproxy,
} from '../_internal'
2025-03-30 00:28:06 +07:00
import type {
AbstractNewable,
Callable,
FiniteDomain,
InsteadHook,
UnknownFunction,
} from '../types'
2025-03-30 00:28:06 +07:00
const insteadHookProxyHandler = {
...patchedFunctionProxyHandler,
apply<T extends Callable>(
hookNode: InsteadHookNode<T>,
receiver: ThisParameterType<T>,
args: Parameters<T>,
) {
2025-03-30 00:28:06 +07:00
const { next } = hookNode
return Reflect.apply(hookNode.hook, receiver, [
args,
next === undefined ? hookNode.target : next.proxy,
])
2025-03-30 00:28:06 +07:00
},
construct<T extends AbstractNewable<never, object>>(
hookNode: InsteadHookNode<T>,
args: ConstructorParameters<T>,
ctor: AbstractNewable,
) {
const { next } = hookNode
return Reflect.construct(
hookNode.hook,
[args, next === undefined ? hookNode.target : next.proxy],
ctor,
)
2025-03-30 00:28:06 +07:00
},
} as const satisfies Required<ProxyHandler<FunctionProxyState>>
function unpatchInstead<T extends UnknownFunction>(
state: PatchedFunctionProxyState<PropertyKey, T>,
hookNode: InsteadHookNode<T>,
) {
if (hookNode.unpatched) return
hookNode.unpatched = true
const { prev, next } = hookNode
if (prev === undefined) {
state.instead = next
if (next === undefined) {
if (state.before === undefined && state.after === undefined)
unproxy(state)
2025-03-30 00:28:06 +07:00
return
}
} else {
prev.next = next
hookNode.prev = undefined
if (next === undefined) return
}
next.prev = prev
hookNode.next = undefined
}
export function instead<
Parent extends Record<Key, UnknownFunction>,
Key extends PropertyKey,
>(
2025-03-30 00:28:06 +07:00
parent: Parent,
key: FiniteDomain<Key>,
hook: InsteadHook<Parent[Key]>,
): () => void
export function instead<Key extends PropertyKey, Value extends UnknownFunction>(
parent: Record<Key, Value>,
key: FiniteDomain<Key>,
hook: InsteadHook<Value>,
) {
const target = parent[key]
let state = patchedFunctionProxyStates.get(target)
// biome-ignore lint/complexity/useArrowFunction: We need a function that has a constructor
const hookNode = function () {}
// The handler makes the proxy behave as target.
hookNode.proxy = new Proxy<any>(hookNode, insteadHookProxyHandler) as Value
hookNode.target = target
hookNode.unpatched = false
hookNode.hook = hook
hookNode.prev = undefined
if (state !== undefined && state.parent === parent && state.key === key) {
const head = state.instead
hookNode.next = head
if (head !== undefined) head.prev = hookNode
state.instead = hookNode
} else {
hookNode.next = undefined
state = createPatchedFunctionProxy(
target,
parent,
key,
undefined,
hookNode,
undefined,
)
2025-03-30 00:28:06 +07:00
}
return unpatchInstead.bind(undefined, state, hookNode)
}