Skip to content

Instantly share code, notes, and snippets.

@fostyfost
Forked from imdfl/property-descriptor-stack.ts
Created September 15, 2023 16:59
Show Gist options
  • Save fostyfost/970298aec2792dd4cd589b407d388189 to your computer and use it in GitHub Desktop.
Save fostyfost/970298aec2792dd4cd589b407d388189 to your computer and use it in GitHub Desktop.
Change property descriptors on an object with an undo stack
interface IPropertyDescriptorStack {
/**
* set the property descriptor of the member prop on the target object
* Fails silently, returning false
* @param props
*/
push(props: Partial<PropertyDescriptor>): boolean;
/**
* reset the property descriptor of the member prop on the target object, either to
* the state it was before the last set, or to the initial state, resetting the state stack.
* Does nothing if the stack is empty
* Fails silently, returning false
* @param toStart
*/
pop(toStart?: boolean): boolean;
}
class PropertyDescriptorStack implements IPropertyDescriptorStack {
private readonly descriptors: PropertyDescriptor[] = [];
constructor(private readonly target: Object, private readonly prop: string) {
if (!target || typeof prop !== "string") { // your choice to define ""
throw new Error("PropertySaver: no object or property");
}
}
public push(props: Partial<PropertyDescriptor>): boolean {
this.saveDescriptor(this.target, this.prop);
try {
Object.defineProperty(this.target, this.prop, {
...props,
configurable: true,
});
return true;
}
catch (e) {
console.error(`Error setting property ${this.prop} on ${this.target}`);
return false;
}
}
public pop(toStart?: boolean): boolean {
const ind = toStart ? 0 : this.descriptors.length - 1;
const descriptor = this.descriptors[ind];
if (!descriptor) {
return false;
}
this.descriptors.splice(ind, this.descriptors.length - ind);
try {
Object.defineProperty(this.target, this.prop, descriptor);
return true;
}
catch (e) {
console.error(`Error resetting property ${this.prop} on ${this.target}`);
return false;
}
}
/**
* Saves the current descriptor of the property in the object in the descriptors stack.
* The descriptor is taken either from the object or from the closest prototype that has this prop.
* If none is found, a new descriptor is generated with the current value.
* @param target
* @param prop
* @returns The found descriptor
*/
private saveDescriptor(target: object, prop: string): PropertyDescriptor {
let ds: PropertyDescriptor | null = null;
for (let o: any = target, ds: PropertyDescriptor = null; o; o = Object.getPrototypeOf(o)) {
ds = Object.getOwnPropertyDescriptor(o, prop);
if (ds) {
break;
}
}
ds = ds || {
configurable: true,
writable: true,
value: target[prop],
enumerable: true
}
this.descriptors.push(ds);
return ds;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment