Skip to content

Commit

Permalink
New: Proxify meta traps, mutation property tracing, new traps
Browse files Browse the repository at this point in the history
WIP: getOwnPropertyDescriptor accessors, read-only props, triggeredByFunction stack tracing
  • Loading branch information
evelynhathaway committed May 3, 2020
1 parent dac339c commit 9fce41e
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 59 deletions.
53 changes: 34 additions & 19 deletions plugin/__tests__/proxy.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,48 @@
const proxify = require('../proxy');
const proxify = require("../proxy");


describe("Proxy util", () => {
let foo;
let target;
beforeEach(() => {
foo = proxify(
Object.assign(Object.create({
protoTest: "old"
}), {
test: "old",
foo: {
test: "old"
const nakedTarget = Object.assign(
Object.create({protoProp: "old"}),
{
prop: "old",
obj: {
prop: "old"
},
classy: class {
constructor() {}
}
}), {
deep: true,
prototype: true
}
);
Object.defineProperty(nakedTarget, "accessor", {
set() {},
get() {return {};}
});
target = proxify(
nakedTarget,
{deep: true, prototype: true}
);
})
test("to throw when assigning a prop", async () => {
expect(() => {
foo.test = "New"
}).toThrow();
target.prop = "New";
}).toThrow(); // TODO: assert exact error
});
});

// foo.test = "new"; // Errors
// delete foo.test; // Errors
// foo.foo.test = "new"; // Errors when `deep`
// Object.getPrototypeOf(foo).test = "foo"; // Errors when `prototype`
// Object.setPrototypeOf(foo, {}); // Errors when `prototype`

// new target.classy(); // TODO: SHOULD NOT ERROR (read-only prop issues)
// target.accessor = "new"; // Errors
// delete target.prop; // Errors
// target.obj.prop = "new"; // Errors when `deep`
// Object.defineProperty(target, "prop", {value: "new"}); // Errors
// Object.getPrototypeOf(target).protoProp = "new"; // Errors when `prototype`
// Object.getOwnPropertyDescriptor(target, "obj").value.prop = "new"; // Errors when `deep`;
// Object.getOwnPropertyDescriptor(target, "accessor"); // TODO: SHOULD NOT ERROR (read-only prop issues)
// Object.getOwnPropertyDescriptor(target, "accessor").set("new"); // TODO: make error (accessor apply trap proxying)
// Object.getOwnPropertyDescriptor(target, "accessor").set.prop = "new"; // TODO: make error (accessor apply trap proxying)
// Object.getOwnPropertyDescriptor(target, "accessor").get().prop = "new"; // TODO: make error (accessor apply trap proxying)
// Object.setPrototypeOf(target, {}); // Errors when `prototype`
// Object.preventExtensions(target.prop); // Errors
106 changes: 66 additions & 40 deletions plugin/proxy.js
Original file line number Diff line number Diff line change
@@ -1,49 +1,75 @@
const proxify = (object, options = {}) => {
const {
deep = false, prototype = false
const proxify = (target, options = {}) => {
// Early return for non-objects
if (!(target instanceof Object)) return target;

// Options
const {deep = false, prototype = false} = options;

// Naming properties for mutation tracing in errors
let {
name = (typeof target.name === "string" && target.name),
path = "target"
} = options;
if (!(object instanceof Object)) return object;
if (name !== "undefined" && name !== false) path += `.${name}`;

const triggeredByFunction = true; // TODO: this is for stack trace shit
// If the proxy trap was triggered by the function to test
// TODO: implement, possibly make optional?
const triggeredByFunction = true;

return new Proxy(object, {
// Other proxy traps have other edgecases
// Proxy handler
const handler = {
// Accessor edge case traps
getOwnPropertyDescriptor(target, prop) {
const descriptor = old = Reflect.getOwnPropertyDescriptor(...arguments);
if (!descriptor) return;
const isValueDesc = "value" in descriptor;

/*
Get - Deep *lennyface*
*/
// TODO: This has a BUNCH of edgecases involving getting/setting from the result of the desc.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor
getOwnPropertyDescriptor() {
const realDescriptor = Reflect.getOwnPropertyDescriptor(...arguments);
return deep ? proxify(realDescriptor, options) : realDescriptor;
},
getPrototypeOf() {
const realPrototypeOf = Reflect.getPrototypeOf(...arguments);
return prototype ? proxify(realPrototypeOf, options) : realPrototypeOf;
},
get() {
const realGet = Reflect.get(...arguments);
return deep ? proxify(realGet, options) : realGet;
if (deep) {
if (isValueDesc) {
descriptor.value = proxify(descriptor.value, {...options, path, name: prop});
} else {
// descriptor.set = proxify(descriptor.set, {...options, path, name: prop}); // TODO: apply traps
// descriptor.get = proxify(descriptor.get, {...options, path, name: prop}); // TODO: apply traps
}
} else if (!isValueDesc) {
// descriptor.set = descriptor.set && new Proxy(descriptor.set, descriptorSetHandler); // TODO: apply traps
}

return descriptor; // TODO: make able to return read-only props
},
};

// Getting traps for deep mutation assertions
const addDeepGetTrap = (trap) => {
handler[trap] = function (target, prop) {
if (trap === "getPrototypeOf") prop = "__proto__";
const real = Reflect[trap](...arguments);
return proxify(real, {...options, path, name: prop});
};
};
deep && addDeepGetTrap("get"); // Covered by getOwnPropertyDescriptor, but is more specific // TODO: interfering when read-olny
prototype && addDeepGetTrap("getPrototypeOf");

/*
Set - Errors *sadface*
*/
set() {
if (triggeredByFunction) throw new Error('Darn!');
return Reflect.set(...arguments);
},
setPrototypeOf() {
if (prototype && triggeredByFunction) throw new Error('Darn!');
return Reflect.setPrototypeOf(...arguments);
},
deleteProperty() {
if (triggeredByFunction) throw new Error('Darn!');
return Reflect.deleteProperty(...arguments);
}
});
// Mutation traps for erroring
const addSetTrap = (trap) => {
handler[trap] = function (target, prop) {
// Naming properties for mutation tracing in errors
if (trap !== "preventExtensions") {
if (trap === "setPrototypeOf") prop = "__proto__";
path += `.${prop}`;
}

if (triggeredByFunction) throw new Error(`Mutation assertion failed. \`${trap}\` trap triggered on \`${path}\`.`);
return Reflect[trap](...arguments);
};
};
addSetTrap("set"); // Covered by defineProperty, but is more specific
addSetTrap("defineProperty");
addSetTrap("deleteProperty");
prototype && addSetTrap("setPrototypeOf");
addSetTrap("preventExtensions");

return new Proxy(target, handler);
};

module.exports = proxify;
module.exports = proxify;

0 comments on commit 9fce41e

Please sign in to comment.