Skip to content

Commit 71a86b7

Browse files
committed
fixup! core: add wrapMethod/unwrapMethod util
1 parent 4adacd5 commit 71a86b7

File tree

2 files changed

+35
-9
lines changed

2 files changed

+35
-9
lines changed

packages/core/src/utils/object.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,14 @@ export function markFunctionWrapped(wrapped: WrappedFunction, original: WrappedF
8383

8484
/**
8585
* Wrap a method on an object by name, only if it is not already wrapped.
86+
*
87+
* Note: to set the wrapped method as a non-enumerable property, pass
88+
* false as the `enumerable` argument. This could be detected, but only
89+
* by either walking up the prototype chain, or iterating over all fields
90+
* in a `for(in)` loop. Neither are an acceptable performance impact for
91+
* the rare case where we might patch a non-enumerable method.
8692
*/
87-
export function wrapMethod<O extends {}, T extends string & keyof O>(obj: O, field: T, wrapped: WrappedFunction): void {
93+
export function wrapMethod<O extends {}, T extends string & keyof O>(obj: O, field: T, wrapped: WrappedFunction, enumerable: boolean = true): void {
8894
const original = obj[field];
8995
if (typeof original !== 'function') {
9096
throw new Error(`Cannot wrap method: ${field} is not a function`);
@@ -93,7 +99,12 @@ export function wrapMethod<O extends {}, T extends string & keyof O>(obj: O, fie
9399
throw new Error(`Attempting to wrap method ${field} multiple times`);
94100
}
95101
markFunctionWrapped(wrapped, original);
96-
addNonEnumerableProperty(obj, field, wrapped);
102+
Object.defineProperty(obj, field, {
103+
writable: true,
104+
configurable: true,
105+
enumerable,
106+
value: wrapped,
107+
})
97108
}
98109

99110
/**

packages/core/test/lib/utils/object.test.ts

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -459,18 +459,33 @@ describe('markFunctionWrapped', () => {
459459

460460
describe('wrapMethod', () => {
461461
it('can wrap a method on an object and unwrap it later', () => {
462-
const wrapped = () => {};
463-
const original = () => {};
464-
const obj = { m: original };
465-
wrapMethod(obj, 'm', wrapped);
466-
expect(obj.m).toBe(wrapped);
467-
expect((obj.m as WrappedFunction).__sentry_original__).toBe(original);
462+
const wrappedEnumerable = () => {};
463+
const originalEnumerable = () => {};
464+
const wrappedNotEnumerable = () => {};
465+
const originalNotEnumerable = () => {};
466+
const obj: Record<string, unknown> = {
467+
enumerable: originalEnumerable,
468+
};
469+
Object.defineProperty(obj, 'notEnumerable', {
470+
writable: true,
471+
configurable: true,
472+
enumerable: false,
473+
value: originalNotEnumerable,
474+
});
475+
wrapMethod(obj, 'notEnumerable', wrappedNotEnumerable, false);
476+
wrapMethod(obj, 'enumerable', wrappedEnumerable);
477+
// does not change enumerability
478+
expect(Object.keys(obj)).toStrictEqual(['enumerable']);
479+
expect(obj.notEnumerable).toBe(wrappedNotEnumerable);
480+
expect((obj.notEnumerable as WrappedFunction).__sentry_original__).toBe(originalNotEnumerable);
481+
expect(obj.enumerable).toBe(wrappedEnumerable);
482+
expect((obj.enumerable as WrappedFunction).__sentry_original__).toBe(originalEnumerable);
468483
});
469484

470485
it('throws if misused', () => {
471486
const wrapped = () => {};
472487
const original = () => {};
473-
const obj = { m: original };
488+
const obj = { get m() { return original } };
474489
wrapMethod(obj, 'm', wrapped);
475490
expect(() => {
476491
//@ts-expect-error verify type checking prevents this mistake

0 commit comments

Comments
 (0)