diff --git a/src/impl/edit.ts b/src/impl/edit.ts index 0f8c14b..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); @@ -96,28 +96,43 @@ 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}`); @@ -125,6 +140,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..a342860 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; } /** @@ -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. */ @@ -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); } /** diff --git a/src/test/edit.test.ts b/src/test/edit.test.ts index 08d6e04..51b1b20 100644 --- a/src/test/edit.test.ts +++ b/src/test/edit.test.ts @@ -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]'); @@ -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}'); + }); }); \ No newline at end of file