Skip to content

Commit

Permalink
Add more ast editor functions
Browse files Browse the repository at this point in the history
  • Loading branch information
TwitchBronBron committed May 16, 2022
1 parent 804fda3 commit 0b7f357
Show file tree
Hide file tree
Showing 2 changed files with 190 additions and 34 deletions.
122 changes: 88 additions & 34 deletions src/astUtils/AstEditor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ import { LiteralExpression } from '../parser/Expression';
import { SourceNode } from 'source-map';

describe('AstEditor', () => {
let changer: AstEditor;
let editor: AstEditor;
let obj: ReturnType<typeof getTestObject>;

beforeEach(() => {
changer = new AstEditor();
editor = new AstEditor();
obj = getTestObject();
});

Expand Down Expand Up @@ -45,101 +45,101 @@ describe('AstEditor', () => {
it('applies single property change', () => {
expect(obj.name).to.eql('parent');

changer.setProperty(obj, 'name', 'jack');
editor.setProperty(obj, 'name', 'jack');
expect(obj.name).to.eql('jack');

changer.undoAll();
editor.undoAll();
expect(obj.name).to.eql('parent');
});

it('inserts at beginning of array', () => {
expect(obj.hobbies).to.eql(['gaming', 'reading', 'cycling']);

changer.addToArray(obj.hobbies, 0, 'climbing');
editor.addToArray(obj.hobbies, 0, 'climbing');
expect(obj.hobbies).to.eql(['climbing', 'gaming', 'reading', 'cycling']);

changer.undoAll();
editor.undoAll();
expect(obj.hobbies).to.eql(['gaming', 'reading', 'cycling']);
});

it('inserts at middle of array', () => {
expect(obj.hobbies).to.eql(['gaming', 'reading', 'cycling']);

changer.addToArray(obj.hobbies, 1, 'climbing');
editor.addToArray(obj.hobbies, 1, 'climbing');
expect(obj.hobbies).to.eql(['gaming', 'climbing', 'reading', 'cycling']);

changer.undoAll();
editor.undoAll();
expect(obj.hobbies).to.eql(['gaming', 'reading', 'cycling']);
});

it('changes the value at an array index', () => {
expect(obj.hobbies).to.eql(['gaming', 'reading', 'cycling']);

changer.setArrayValue(obj.hobbies, 1, 'sleeping');
editor.setArrayValue(obj.hobbies, 1, 'sleeping');
expect(obj.hobbies).to.eql(['gaming', 'sleeping', 'cycling']);

changer.undoAll();
editor.undoAll();
expect(obj.hobbies).to.eql(['gaming', 'reading', 'cycling']);
});

it('inserts at end of array', () => {
expect(obj.hobbies).to.eql(['gaming', 'reading', 'cycling']);

changer.addToArray(obj.hobbies, 3, 'climbing');
editor.addToArray(obj.hobbies, 3, 'climbing');
expect(obj.hobbies).to.eql(['gaming', 'reading', 'cycling', 'climbing']);

changer.undoAll();
editor.undoAll();
expect(obj.hobbies).to.eql(['gaming', 'reading', 'cycling']);
});

it('removes at beginning of array', () => {
expect(obj.hobbies).to.eql(['gaming', 'reading', 'cycling']);

changer.removeFromArray(obj.hobbies, 0);
editor.removeFromArray(obj.hobbies, 0);
expect(obj.hobbies).to.eql(['reading', 'cycling']);

changer.undoAll();
editor.undoAll();
expect(obj.hobbies).to.eql(['gaming', 'reading', 'cycling']);
});

it('removes at middle of array', () => {
expect(obj.hobbies).to.eql(['gaming', 'reading', 'cycling']);

changer.removeFromArray(obj.hobbies, 1);
editor.removeFromArray(obj.hobbies, 1);
expect(obj.hobbies).to.eql(['gaming', 'cycling']);

changer.undoAll();
editor.undoAll();
expect(obj.hobbies).to.eql(['gaming', 'reading', 'cycling']);
});

it('removes at middle of array', () => {
expect(obj.hobbies).to.eql(['gaming', 'reading', 'cycling']);

changer.removeFromArray(obj.hobbies, 2);
editor.removeFromArray(obj.hobbies, 2);
expect(obj.hobbies).to.eql(['gaming', 'reading']);

changer.undoAll();
editor.undoAll();
expect(obj.hobbies).to.eql(['gaming', 'reading', 'cycling']);
});

it('restores array after being removed', () => {
changer.removeFromArray(obj.hobbies, 0);
changer.setProperty(obj, 'hobbies', undefined);
editor.removeFromArray(obj.hobbies, 0);
editor.setProperty(obj, 'hobbies', undefined);
expect(obj.hobbies).to.be.undefined;
changer.undoAll();
editor.undoAll();
expect(obj.hobbies).to.eql(['gaming', 'reading', 'cycling']);
});

it('works for many changes', () => {
expect(obj).to.eql(getTestObject());
changer.setProperty(obj, 'name', 'bob');
changer.setProperty(obj.children[0], 'name', 'jimmy');
changer.addToArray(obj.children, obj.children.length, { name: 'sally', age: 1 });
changer.removeFromArray(obj.jobs, 1);
changer.removeFromArray(obj.hobbies, 0);
changer.removeFromArray(obj.hobbies, 0);
changer.removeFromArray(obj.hobbies, 0);
changer.setProperty(obj, 'hobbies', undefined);
editor.setProperty(obj, 'name', 'bob');
editor.setProperty(obj.children[0], 'name', 'jimmy');
editor.addToArray(obj.children, obj.children.length, { name: 'sally', age: 1 });
editor.removeFromArray(obj.jobs, 1);
editor.removeFromArray(obj.hobbies, 0);
editor.removeFromArray(obj.hobbies, 0);
editor.removeFromArray(obj.hobbies, 0);
editor.setProperty(obj, 'hobbies', undefined);

expect(obj).to.eql({
name: 'bob',
Expand All @@ -163,7 +163,7 @@ describe('AstEditor', () => {
}]
});

changer.undoAll();
editor.undoAll();
expect(obj).to.eql(getTestObject());
});

Expand All @@ -182,10 +182,10 @@ describe('AstEditor', () => {

expect(transpileToString(expression)).to.eql('original');

changer.overrideTranspileResult(expression, 'replaced');
editor.overrideTranspileResult(expression, 'replaced');
expect(transpileToString(expression)).to.eql('replaced');

changer.undoAll();
editor.undoAll();
expect(transpileToString(expression)).to.eql('original');
});

Expand All @@ -194,10 +194,64 @@ describe('AstEditor', () => {
range: util.createRange(1, 2, 3, 4)
} as any;
expect(expression.transpile).not.to.exist;
changer.overrideTranspileResult(expression, 'replaced');
editor.overrideTranspileResult(expression, 'replaced');
expect(transpileToString(expression)).to.eql('replaced');
changer.undoAll();
editor.undoAll();
expect(expression.transpile).not.to.exist;
});
});

it('arrayPush works', () => {
const array = [1, 2, 3];
editor.arrayPush(array, 4);
expect(array).to.eql([1, 2, 3, 4]);
editor.undoAll();
expect(array).to.eql([1, 2, 3]);
});

it('arrayPop works', () => {
const array = [1, 2, 3];
expect(
editor.arrayPop(array)
).to.eql(3);
expect(array).to.eql([1, 2]);
editor.undoAll();
expect(array).to.eql([1, 2, 3]);
});

it('arrayShift works', () => {
const array = [1, 2, 3];
expect(
editor.arrayShift(array)
).to.eql(1);
expect(array).to.eql([2, 3]);
editor.undoAll();
expect(array).to.eql([1, 2, 3]);
});

it('arrayUnshift works at beginning', () => {
const array = [1, 2, 3];
editor.arrayUnshift(array, -1, 0);
expect(array).to.eql([-1, 0, 1, 2, 3]);
editor.undoAll();
expect(array).to.eql([1, 2, 3]);
});

it('removeProperty removes existing property', () => {
const obj = {
name: 'bob'
};
editor.removeProperty(obj, 'name');
expect(obj).not.haveOwnProperty('name');
editor.undoAll();
expect(obj).haveOwnProperty('name');
});

it('removeProperty removes existing property', () => {
const obj = {};
editor.removeProperty(obj as any, 'name');
expect(obj).not.haveOwnProperty('name');
editor.undoAll();
expect(obj).not.haveOwnProperty('name');
});
});
102 changes: 102 additions & 0 deletions src/astUtils/AstEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ export class AstEditor {
change.apply();
}

/**
* Remove a property from an object
*/
public removeProperty<T, K extends keyof T>(obj: T, key: K) {
const change = new RemovePropertyChange(obj, key);
this.changes.push(change);
change.apply();
}

/**
* Set custom text that will be emitted during transpile instead of the original text.
*/
Expand All @@ -40,6 +49,42 @@ export class AstEditor {
change.apply();
}

/**
* Push one or more values to the end of an array
*/
public arrayPush<T, TItems extends T = T>(array: T[], ...newValues: TItems[]) {
const change = new ArrayPushChange(array, newValues);
this.changes.push(change);
change.apply();
}

/**
* Pop an item from the end of the array
*/
public arrayPop<T extends any[]>(array: T) {
const result = array[array.length - 1];
this.removeFromArray(array, array.length - 1);
return result;
}

/**
* Removes the first element from an array and returns that removed element. This method changes the length of the array.
*/
public arrayShift<T extends any[]>(array: T) {
const result = array[0];
this.removeFromArray(array, 0);
return result;
}

/**
* Adds one or more elements to the beginning of an array and returns the new length of the array.
*/
public arrayUnshift<T extends any[], TItems extends T = T>(array: T, ...items: TItems) {
const change = new ArrayUnshiftChange(array, items);
this.changes.push(change);
change.apply();
}

/**
* Change the value of an item in an array at the specified index
*/
Expand Down Expand Up @@ -91,6 +136,32 @@ class EditPropertyChange<T, K extends keyof T> implements Change {
}
}

class RemovePropertyChange<T, K extends keyof T> implements Change {
constructor(
private obj: T,
private propertyName: K
) { }

private originalValue: T[K];
/**
* To keep the object completely pure, this tracks whether the property existed
* at all before applying the change (even if set to undefined).
*/
private keyExistedBeforeChange: boolean;

public apply() {
this.keyExistedBeforeChange = this.obj.hasOwnProperty(this.propertyName);
this.originalValue = this.obj[this.propertyName];
delete this.obj[this.propertyName];
}

public undo() {
if (this.keyExistedBeforeChange) {
this.obj[this.propertyName] = this.originalValue;
}
}
}

class AddToArrayChange<T extends any[]> implements Change {
constructor(
private array: T,
Expand Down Expand Up @@ -126,3 +197,34 @@ class RemoveFromArrayChange<T extends any[]> implements Change {
this.array.splice(this.index, 0, this.originalValue);
}
}

class ArrayPushChange<T extends any[], TItems extends T = T> implements Change {
constructor(
private array: T,
private newValues: TItems
) { }

public apply() {
this.array.push(...this.newValues);
}

public undo() {
this.array.splice(this.array.length - this.newValues.length, this.newValues.length);
}
}

class ArrayUnshiftChange<T extends any[]> implements Change {
constructor(
private array: T,
private newValues: T
) { }

public apply() {
this.array.unshift(...this.newValues);
}

public undo() {
this.array.splice(0, this.newValues.length);
}
}

0 comments on commit 0b7f357

Please sign in to comment.