Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow for array modifications, add inPlace formatting option. #35

Merged
merged 2 commits into from
Jun 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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}');
});
});