Skip to content

Commit

Permalink
Merge pull request #35 from mbullington/edit-features
Browse files Browse the repository at this point in the history
Allow for array modifications, add inPlace formatting option.
  • Loading branch information
aeschli authored Jun 29, 2020
2 parents e38baa7 + 0d78fe6 commit b905205
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 24 deletions.
60 changes: 39 additions & 21 deletions src/impl/edit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export function removeProperty(text: string, path: JSONPath, formattingOptions:
return setProperty(text, path, void 0, formattingOptions);
}

export function setProperty(text: string, originalPath: JSONPath, value: any, formattingOptions: FormattingOptions, getInsertionIndex?: (properties: string[]) => number): Edit[] {
export function setProperty(text: string, originalPath: JSONPath, value: any, formattingOptions: FormattingOptions, getInsertionIndex?: (properties: string[]) => number, isArrayInsertion: boolean = false): Edit[] {
let path = originalPath.slice()
let errors: ParseError[] = [];
let root = parseTree(text, errors);
Expand Down Expand Up @@ -96,35 +96,53 @@ export function setProperty(text: string, originalPath: JSONPath, value: any, fo
edit = { offset: previous.offset + previous.length, length: 0, content: ',' + newProperty };
}
return withFormatting(text, edit, formattingOptions);
} else {
if (value === void 0 && parent.children.length >= 0) {
//Removal
let removalIndex = lastSegment;
let toRemove = parent.children[removalIndex];
let edit: Edit;
if (parent.children.length === 1) {
// only item
edit = { offset: parent.offset + 1, length: parent.length - 2, content: '' };
} else if (parent.children.length - 1 === removalIndex) {
// last item
let previous = parent.children[removalIndex - 1];
let offset = previous.offset + previous.length;
let parentEndOffset = parent.offset + parent.length;
edit = { offset, length: parentEndOffset - 2 - offset, content: '' };
} else {
edit = { offset: toRemove.offset, length: parent.children[removalIndex + 1].offset - toRemove.offset, content: '' };
}
return withFormatting(text, edit, formattingOptions);
} else if (value === void 0 && parent.children.length >= 0) {
// Removal
let removalIndex = lastSegment;
let toRemove = parent.children[removalIndex];
let edit: Edit;
if (parent.children.length === 1) {
// only item
edit = { offset: parent.offset + 1, length: parent.length - 2, content: '' };
} else if (parent.children.length - 1 === removalIndex) {
// last item
let previous = parent.children[removalIndex - 1];
let offset = previous.offset + previous.length;
let parentEndOffset = parent.offset + parent.length;
edit = { offset, length: parentEndOffset - 2 - offset, content: '' };
} else {
throw new Error('Array modification not supported yet');
edit = { offset: toRemove.offset, length: parent.children[removalIndex + 1].offset - toRemove.offset, content: '' };
}
return withFormatting(text, edit, formattingOptions);
} else if (value !== void 0) {
let edit: Edit;
const newProperty = `${JSON.stringify(value)}`;

if (!isArrayInsertion && parent.children.length > lastSegment) {
let toModify = parent.children[lastSegment];

edit = { offset: toModify.offset, length: toModify.length, content: newProperty }
} else if (parent.children.length === 0 || lastSegment === 0) {
edit = { offset: parent.offset + 1, length: 0, content: parent.children.length === 0 ? newProperty : newProperty + ',' };
} else {
const index = lastSegment > parent.children.length ? parent.children.length : lastSegment;
const previous = parent.children[index - 1];
edit = { offset: previous.offset + previous.length, length: 0, content: ',' + newProperty };
}

return withFormatting(text, edit, formattingOptions);
} else {
throw new Error(`Can not ${value === void 0 ? 'remove' : (isArrayInsertion ? 'insert' : 'modify')} Array index ${insertIndex} as length is not sufficient`);
}
} else {
throw new Error(`Can not add ${typeof lastSegment !== 'number' ? 'index' : 'property'} to parent of type ${parent.type}`);
}
}

function withFormatting(text: string, edit: Edit, formattingOptions: FormattingOptions): Edit[] {
if (formattingOptions.inPlace) {
return [{ ...edit }]
}
// apply the edit
let newText = applyEdit(text, edit);

Expand Down
12 changes: 11 additions & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,11 @@ export interface FormattingOptions {
* The default 'end of line' character. If not set, '\n' is used as default.
*/
eol?: string;
/**
* If true, changes within {@function format} will not be formatted and their original formatting will be preserved.
* Useful for cutting down on computational time for large files.
*/
inPlace?: boolean;
}

/**
Expand All @@ -348,6 +353,11 @@ export interface ModificationOptions {
* Formatting options
*/
formattingOptions: FormattingOptions;
/**
* Default false. If `JSONPath` refers to an index of an array and {@property isArrayInsertion} is `true`, then
* {@function modify} will insert a new item at that location instead of overwriting its contents.
*/
isArrayInsertion?: boolean;
/**
* Optional function to define the insertion index given an existing list of properties.
*/
Expand All @@ -370,7 +380,7 @@ export interface ModificationOptions {
* To apply edits to an input, you can use `applyEdits`.
*/
export function modify(text: string, path: JSONPath, value: any, options: ModificationOptions): Edit[] {
return edit.setProperty(text, path, value, options.formattingOptions, options.getInsertionIndex);
return edit.setProperty(text, path, value, options.formattingOptions, options.getInsertionIndex, options.isArrayInsertion);
}

/**
Expand Down
59 changes: 57 additions & 2 deletions src/test/edit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,13 +121,59 @@ suite('JSON - edits', () => {
assertEdit(content, edits, '{\n "x": "y"\n}');
});

test('insert item to empty array', () => {
test('set item', () => {
let content = '{\n "x": [1, 2, 3],\n "y": 0\n}'

let edits = setProperty(content, ['x', 0], 6, formatterOptions);
assertEdit(content, edits, '{\n "x": [6, 2, 3],\n "y": 0\n}');

edits = setProperty(content, ['x', 1], 5, formatterOptions);
assertEdit(content, edits, '{\n "x": [1, 5, 3],\n "y": 0\n}');

edits = setProperty(content, ['x', 2], 4, formatterOptions);
assertEdit(content, edits, '{\n "x": [1, 2, 4],\n "y": 0\n}');

edits = setProperty(content, ['x', 3], 3, formatterOptions)
assertEdit(content, edits, '{\n "x": [\n 1,\n 2,\n 3,\n 3\n ],\n "y": 0\n}');
});

test('insert item at 0; isArrayInsertion = true', () => {
let content = '[\n 2,\n 3\n]';
let edits = setProperty(content, [0], 1, formatterOptions, undefined, true);
assertEdit(content, edits, '[\n 1,\n 2,\n 3\n]');
});

test('insert item at 0 in empty array', () => {
let content = '[\n]';
let edits = setProperty(content, [0], 1, formatterOptions);
assertEdit(content, edits, '[\n 1\n]');
});

test('insert item at an index; isArrayInsertion = true', () => {
let content = '[\n 1,\n 3\n]';
let edits = setProperty(content, [1], 2, formatterOptions, undefined, true);
assertEdit(content, edits, '[\n 1,\n 2,\n 3\n]');
});

test('insert item at an index in empty array', () => {
let content = '[\n]';
let edits = setProperty(content, [1], 1, formatterOptions);
assertEdit(content, edits, '[\n 1\n]');
});

test('insert item at end index', () => {
let content = '[\n 1,\n 2\n]';
let edits = setProperty(content, [2], 3, formatterOptions);
assertEdit(content, edits, '[\n 1,\n 2,\n 3\n]');
});

test('insert item at end to empty array', () => {
let content = '[\n]';
let edits = setProperty(content, [-1], 'bar', formatterOptions);
assertEdit(content, edits, '[\n "bar"\n]');
});

test('insert item', () => {
test('insert item at end', () => {
let content = '[\n 1,\n 2\n]';
let edits = setProperty(content, [-1], 'bar', formatterOptions);
assertEdit(content, edits, '[\n 1,\n 2,\n "bar"\n]');
Expand Down Expand Up @@ -163,4 +209,13 @@ suite('JSON - edits', () => {
assertEdit(content, edits, '// This is a comment\n[\n 1,\n "foo"\n]');
});

test('set property w/ in-place formatting options', () => {
let content = '{\n "x": [1, 2, 3],\n "y": 0\n}'

let edits = setProperty(content, ['x', 0], { a: 1, b: 2 }, formatterOptions);
assertEdit(content, edits, '{\n "x": [{\n "a": 1,\n "b": 2\n }, 2, 3],\n "y": 0\n}');

edits = setProperty(content, ['x', 0], { a: 1, b: 2 }, { ...formatterOptions, inPlace: true });
assertEdit(content, edits, '{\n "x": [{"a":1,"b":2}, 2, 3],\n "y": 0\n}');
});
});

0 comments on commit b905205

Please sign in to comment.