From 54d7cfd1e46340fd5c00131e81d88655afb8ec1e Mon Sep 17 00:00:00 2001 From: Michael Bullington Date: Tue, 9 Jun 2020 00:47:26 -0400 Subject: [PATCH 1/2] Add inPlace formatting option & array mods --- src/impl/edit.ts | 46 ++++++++++++++++++++++++------------------- src/main.ts | 5 +++++ src/test/edit.test.ts | 30 ++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 20 deletions(-) diff --git a/src/impl/edit.ts b/src/impl/edit.ts index 0f8c14b..18d1f63 100644 --- a/src/impl/edit.ts +++ b/src/impl/edit.ts @@ -96,28 +96,31 @@ 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 && parent.children.length > lastSegment) { + let modifyIndex = lastSegment; + let toModify = parent.children[modifyIndex]; + let newProperty = `${JSON.stringify(value)}`; + return withFormatting(text, { offset: toModify.offset, length: toModify.length, content: newProperty }, formattingOptions); + } else { + throw new Error(`Can not ${value === void 0 ? 'remove' : '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}`); @@ -125,6 +128,9 @@ export function setProperty(text: string, originalPath: JSONPath, value: any, fo } function withFormatting(text: string, edit: Edit, formattingOptions: FormattingOptions): Edit[] { + if (formattingOptions.inPlace) { + return [{ ...edit }] + } // apply the edit let newText = applyEdit(text, edit); diff --git a/src/main.ts b/src/main.ts index b58b5ad..4c3b464 100644 --- a/src/main.ts +++ b/src/main.ts @@ -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; } /** diff --git a/src/test/edit.test.ts b/src/test/edit.test.ts index 08d6e04..b2f0837 100644 --- a/src/test/edit.test.ts +++ b/src/test/edit.test.ts @@ -121,6 +121,27 @@ suite('JSON - edits', () => { assertEdit(content, edits, '{\n "x": "y"\n}'); }); + 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}'); + + let message = "allowed setProperty to set array item outside of bounds"; + try { + setProperty(content, ['x', 3], 3, formatterOptions) + throw new Error(message); + } catch(e) { + assert(e && e.message !== message); + } + }); + test('insert item to empty array', () => { let content = '[\n]'; let edits = setProperty(content, [-1], 'bar', formatterOptions); @@ -163,4 +184,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}'); + }); }); \ No newline at end of file From 0d78fe627861a4c019c0ab1199e71a4fe22972d5 Mon Sep 17 00:00:00 2001 From: Michael Bullington Date: Wed, 10 Jun 2020 21:32:50 -0400 Subject: [PATCH 2/2] add support for isArrayInsertion --- src/impl/edit.ts | 26 +++++++++++++++++++------- src/main.ts | 7 ++++++- src/test/edit.test.ts | 43 ++++++++++++++++++++++++++++++++++--------- 3 files changed, 59 insertions(+), 17 deletions(-) diff --git a/src/impl/edit.ts b/src/impl/edit.ts index 18d1f63..023dc2d 100644 --- a/src/impl/edit.ts +++ b/src/impl/edit.ts @@ -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); @@ -114,13 +114,25 @@ export function setProperty(text: string, originalPath: JSONPath, value: any, fo 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 > lastSegment) { - let modifyIndex = lastSegment; - let toModify = parent.children[modifyIndex]; - let newProperty = `${JSON.stringify(value)}`; - return withFormatting(text, { offset: toModify.offset, length: toModify.length, content: newProperty }, 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' : 'modify'} Array index ${insertIndex} as length is not sufficient`); + 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}`); diff --git a/src/main.ts b/src/main.ts index 4c3b464..a342860 100644 --- a/src/main.ts +++ b/src/main.ts @@ -353,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. */ @@ -375,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); } /** diff --git a/src/test/edit.test.ts b/src/test/edit.test.ts index b2f0837..51b1b20 100644 --- a/src/test/edit.test.ts +++ b/src/test/edit.test.ts @@ -133,22 +133,47 @@ suite('JSON - edits', () => { edits = setProperty(content, ['x', 2], 4, formatterOptions); assertEdit(content, edits, '{\n "x": [1, 2, 4],\n "y": 0\n}'); - let message = "allowed setProperty to set array item outside of bounds"; - try { - setProperty(content, ['x', 3], 3, formatterOptions) - throw new Error(message); - } catch(e) { - assert(e && e.message !== message); - } + 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 to empty array', () => { + 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]');