Skip to content

Commit f7d527c

Browse files
committed
fixup! review suggestions
1 parent fd5bb19 commit f7d527c

File tree

2 files changed

+93
-27
lines changed

2 files changed

+93
-27
lines changed

packages/no-trapping-shim/shim.js

-2
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,8 @@ import { ReflectPlus, ObjectPlus, ProxyPlus } from './src/no-trapping-pony.js';
33

44
globalThis.Reflect = ReflectPlus;
55

6-
// @ts-expect-error Something about the type of the Object constructor
76
globalThis.Object = ObjectPlus;
87
// eslint-disable-next-line no-extend-native
98
Object.prototype.constructor = ObjectPlus;
109

11-
// @ts-expect-error Something about the type of Proxy
1210
globalThis.Proxy = ProxyPlus;

packages/no-trapping-shim/src/no-trapping-pony.js

+93-25
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const OriginalObject = Object;
22
const OriginalReflect = Reflect;
33
const OriginalProxy = Proxy;
4-
const { freeze, defineProperty } = OriginalObject;
4+
const { freeze, defineProperty, hasOwn } = OriginalObject;
55
const { apply, construct, ownKeys } = OriginalReflect;
66

77
const noTrappingSet = new WeakSet();
@@ -144,55 +144,115 @@ const addExtras = (base, ...extrasArgs) => {
144144
}
145145
};
146146

147+
/** In the shim, `ReflectPlus` replaces the global `Reflect`. */
147148
const ReflectPlus = {};
148149
addExtras(ReflectPlus, OriginalReflect, extraReflectMethods);
149150
export { ReflectPlus };
150151

152+
/**
153+
* In the shim, `ObjectPlus` replaces the global `Object`.
154+
*
155+
* @type {ObjectConstructor}
156+
*/
157+
// @ts-expect-error TS does not know the rest of the type is added below
151158
const ObjectPlus = function Object(...args) {
152159
if (new.target) {
153160
return construct(OriginalObject, args, new.target);
154161
} else {
155162
return apply(OriginalObject, this, args);
156163
}
157164
};
165+
// @ts-expect-error We actually can assign to its `.prototype`.
158166
ObjectPlus.prototype = OriginalObject.prototype;
159167
addExtras(ObjectPlus, OriginalObject, extraObjectMethods);
160168
export { ObjectPlus };
161169

162-
const makeMetaHandler = handler =>
163-
freeze({
164-
get(_, trapName, _receiver) {
165-
return freeze((target, ...rest) => {
166-
if (
167-
isNoTrappingInternal(target, true) ||
168-
handler[trapName] === undefined
169-
) {
170-
return ReflectPlus[trapName](target, ...rest);
171-
} else {
172-
return handler[trapName](target, ...rest);
170+
const metaHandler = freeze({
171+
get(_, trapName, handlerPlus) {
172+
/**
173+
* The `trapPlus` method is an enhanced version of
174+
* `originalHandler[trapName]`. If the handlerPlus has no own `trapName`
175+
* property, then the `get` of the metaHandler is called, which returns
176+
* the `trapPlus`, which is then called as the trap of the returned
177+
* proxyPlus. When so called, it installs an own `handlerPlus[trapName]`
178+
* which is either `undefined` or this same `trapPlus`, to avoid further
179+
* need to meta-handle that `handlerPlus[trapName]`.
180+
*
181+
* @param {any} target
182+
* @param {any[]} rest
183+
*/
184+
const trapPlus = freeze((target, ...rest) => {
185+
if (isNoTrappingInternal(target, true)) {
186+
defineProperty(handlerPlus, trapName, {
187+
value: undefined,
188+
writable: false,
189+
enumerable: true,
190+
configurable: false,
191+
});
192+
} else {
193+
if (!hasOwn(handlerPlus, trapName)) {
194+
defineProperty(handlerPlus, trapName, {
195+
value: trapPlus,
196+
writable: false,
197+
enumerable: true,
198+
configurable: true,
199+
});
173200
}
174-
});
175-
},
176-
});
201+
const { originalHandler } = handlerPlus;
202+
const trap = originalHandler[trapName];
203+
if (trap !== undefined) {
204+
// Note that whether `trap === undefined` can change dynamically,
205+
// so we do not install an own `handlerPlus[trapName] === undefined`
206+
// for that case. We still install or preserve an own
207+
// `handlerPlus[trapName] === trapPlus` until the target is
208+
// seen to be non-trapping.
209+
return apply(trap, originalHandler, [target, ...rest]);
210+
}
211+
}
212+
return ReflectPlus[trapName](target, ...rest);
213+
});
214+
return trapPlus;
215+
},
216+
});
177217

178-
const makeSafeHandler = handler =>
179-
new OriginalProxy({}, makeMetaHandler(handler));
218+
/**
219+
* A handlerPlus starts as a fresh empty object that inherits from a proxy
220+
* whose handler is the shared generic metaHandler.
221+
* Thus, the metaHandler's `get` method is called only when the
222+
* `handlerPlus` does not have a property overriding that `trapName`.
223+
* In that case, the metaHandler's `get` is called with its `receiver`
224+
* being the `handlerPlus`.
225+
*
226+
* @param {ProxyHandler<any>} originalHandler
227+
* @returns {ProxyHandler<any> & {
228+
* isNoTrapping: (target: any) => boolean,
229+
* suppressTrapping: (target: any) => boolean,
230+
* originalHandler: ProxyHandler<any>
231+
* }}
232+
*/
233+
const makeHandlerPlus = originalHandler => ({
234+
// @ts-expect-error TS does not know what this __proto__ is doing
235+
__proto__: new OriginalProxy({}, metaHandler),
236+
// relies on there never being a trap named `originalHandler`.
237+
originalHandler,
238+
});
180239

181240
/**
182-
* In the shim, `ProxyPlus` should replace the global `Proxy`.
241+
* In the shim, `ProxyPlus` replaces the global `Proxy`.
183242
*
184-
* @param {any} target
185-
* @param {object} handler
243+
* @type {ProxyConstructor}
186244
*/
245+
// @ts-expect-error We reject non-new calls in the body
187246
const ProxyPlus = function Proxy(target, handler) {
247+
// @ts-expect-error Yes, we mean to compare these.
188248
if (new.target !== ProxyPlus) {
189249
if (new.target === undefined) {
190250
throw TypeError('Proxy constructor requires "new"');
191251
}
192252
throw TypeError('Safe Proxy shim does not support subclassing');
193253
}
194-
const safeHandler = makeSafeHandler(handler);
195-
const proxy = new OriginalProxy(target, safeHandler);
254+
const handlerPlus = makeHandlerPlus(handler);
255+
const proxy = new OriginalProxy(target, handlerPlus);
196256
proxyHandlerMap.set(proxy, [target, handler]);
197257
return proxy;
198258
};
@@ -201,10 +261,18 @@ const ProxyPlus = function Proxy(target, handler) {
201261
// `ProxyPlus.prototype` to `undefined`
202262
ProxyPlus.prototype = undefined;
203263
ProxyPlus.revocable = (target, handler) => {
204-
const safeHandler = makeSafeHandler(handler);
205-
const { proxy, revoke } = OriginalProxy.revocable(target, safeHandler);
264+
const handlerPlus = makeHandlerPlus(handler);
265+
const { proxy, revoke } = OriginalProxy.revocable(target, handlerPlus);
206266
proxyHandlerMap.set(proxy, [target, handler]);
207-
return { proxy, revoke };
267+
return {
268+
proxy,
269+
revoke() {
270+
if (isNoTrappingInternal(target, true)) {
271+
throw TypeError('Cannot revoke non-trapping proxy');
272+
}
273+
revoke();
274+
},
275+
};
208276
};
209277

210278
export { ProxyPlus };

0 commit comments

Comments
 (0)