diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d6afc0..b083b1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,14 @@ +2.3.0 2020-07-03 +================== + * new API `ModificationOptions.isArrayInsertion`: If `JSONPath` refers to an index of an array and `isArrayInsertion` is `true`, then `modify` will insert a new item at that location instead of overwriting its contents. + * `ModificationOptions.formattingOptions` is now optional. If not set, newly inserted content will be not be formatted. + + 2.2.0 2019-10-25 ================== - * added *ParseOptions.allowEmptyContent*. Default is `false`. - * New API *getNodeType*: Returns the type of a value returned by parse. - * parse: Fix issue with empty property name + * added `ParseOptions.allowEmptyContent`. Default is `false`. + * new API `getNodeType`: Returns the type of a value returned by parse. + * `parse`: Fix issue with empty property name 2.1.0 2019-03-29 ================== @@ -10,9 +16,9 @@ 2.0.0 2018-04-12 ================== - * renamed Node.columnOffset to Node.colonOffset - * new API getNodePath: Gets the JSON path of the given JSON DOM node - * new API findNodeAtOffset: Finds the most inner node at the given offset. If includeRightBound is set, also finds nodes that end at the given offset. + * renamed `Node.columnOffset` to `Node.colonOffset` + * new API `getNodePath`: Gets the JSON path of the given JSON DOM node + * new API `findNodeAtOffset`: Finds the most inner node at the given offset. If `includeRightBound` is set, also finds nodes that end at the given offset. 1.0.3 2018-03-07 ================== @@ -20,26 +26,26 @@ 1.0.2 2018-03-05 ================== - * added the *visit.onComment* API, reported when comments are allowed. - * added the *ParseErrorCode.InvalidCommentToken* enum value, reported when comments are disallowed. + * added the `visit.onComment` API, reported when comments are allowed. + * added the `ParseErrorCode.InvalidCommentToken` enum value, reported when comments are disallowed. 1.0.1 ================== - * added the *format* API: computes edits to format a JSON document. - * added the *modify* API: computes edits to insert, remove or replace a property or value in a JSON document. - * added the *allyEdits* API: applies edits to a document + * added the `format` API: computes edits to format a JSON document. + * added the `modify` API: computes edits to insert, remove or replace a property or value in a JSON document. + * added the `allyEdits` API: applies edits to a document 1.0.0 ================== - * remove nls dependency (remove getParseErrorMessage) + * remove nls dependency (remove `getParseErrorMessage`) 0.4.2 / 2017-05-05 ================== - * added ParseError.offset & ParseError.length + * added `ParseError.offset` & `ParseError.length` 0.4.1 / 2017-04-02 ================== - * added ParseOptions.allowTrailingComma + * added `ParseOptions.allowTrailingComma` 0.4.0 / 2017-02-23 ================== diff --git a/src/impl/edit.ts b/src/impl/edit.ts index 023dc2d..b6e71ad 100644 --- a/src/impl/edit.ts +++ b/src/impl/edit.ts @@ -4,15 +4,15 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { Edit, FormattingOptions, ParseError, Node, JSONPath, Segment } from '../main'; +import { Edit, ParseError, Node, JSONPath, Segment, ModificationOptions } from '../main'; import { format, isEOL } from './format'; import { parseTree, findNodeAtLocation } from './parser'; -export function removeProperty(text: string, path: JSONPath, formattingOptions: FormattingOptions): Edit[] { - return setProperty(text, path, void 0, formattingOptions); +export function removeProperty(text: string, path: JSONPath, options: ModificationOptions): Edit[] { + return setProperty(text, path, void 0, options); } -export function setProperty(text: string, originalPath: JSONPath, value: any, formattingOptions: FormattingOptions, getInsertionIndex?: (properties: string[]) => number, isArrayInsertion: boolean = false): Edit[] { +export function setProperty(text: string, originalPath: JSONPath, value: any, options: ModificationOptions): Edit[] { let path = originalPath.slice() let errors: ParseError[] = []; let root = parseTree(text, errors); @@ -38,7 +38,7 @@ export function setProperty(text: string, originalPath: JSONPath, value: any, fo if (value === void 0) { // delete throw new Error('Can not delete in empty document'); } - return withFormatting(text, { offset: root ? root.offset : 0, length: root ? root.length : 0, content: JSON.stringify(value) }, formattingOptions); + return withFormatting(text, { offset: root ? root.offset : 0, length: root ? root.length : 0, content: JSON.stringify(value) }, options); } else if (parent.type === 'object' && typeof lastSegment === 'string' && Array.isArray(parent.children)) { let existing = findNodeAtLocation(parent, [lastSegment]); if (existing !== void 0) { @@ -61,17 +61,17 @@ export function setProperty(text: string, originalPath: JSONPath, value: any, fo removeEnd = next.offset; } } - return withFormatting(text, { offset: removeBegin, length: removeEnd - removeBegin, content: '' }, formattingOptions); + return withFormatting(text, { offset: removeBegin, length: removeEnd - removeBegin, content: '' }, options); } else { // set value of existing property - return withFormatting(text, { offset: existing.offset, length: existing.length, content: JSON.stringify(value) }, formattingOptions); + return withFormatting(text, { offset: existing.offset, length: existing.length, content: JSON.stringify(value) }, options); } } else { if (value === void 0) { // delete return []; // property does not exist, nothing to do } let newProperty = `${JSON.stringify(lastSegment)}: ${JSON.stringify(value)}`; - let index = getInsertionIndex ? getInsertionIndex(parent.children.map(p => p.children![0].value)) : parent.children.length; + let index = options.getInsertionIndex ? options.getInsertionIndex(parent.children.map(p => p.children![0].value)) : parent.children.length; let edit: Edit; if (index > 0) { let previous = parent.children[index - 1]; @@ -81,7 +81,7 @@ export function setProperty(text: string, originalPath: JSONPath, value: any, fo } else { edit = { offset: parent.offset + 1, length: 0, content: newProperty + ',' }; } - return withFormatting(text, edit, formattingOptions); + return withFormatting(text, edit, options); } } else if (parent.type === 'array' && typeof lastSegment === 'number' && Array.isArray(parent.children)) { let insertIndex = lastSegment; @@ -95,7 +95,7 @@ export function setProperty(text: string, originalPath: JSONPath, value: any, fo let previous = parent.children[parent.children.length - 1]; edit = { offset: previous.offset + previous.length, length: 0, content: ',' + newProperty }; } - return withFormatting(text, edit, formattingOptions); + return withFormatting(text, edit, options); } else if (value === void 0 && parent.children.length >= 0) { // Removal let removalIndex = lastSegment; @@ -113,12 +113,12 @@ export function setProperty(text: string, originalPath: JSONPath, value: any, fo } else { edit = { offset: toRemove.offset, length: parent.children[removalIndex + 1].offset - toRemove.offset, content: '' }; } - return withFormatting(text, edit, formattingOptions); + return withFormatting(text, edit, options); } else if (value !== void 0) { let edit: Edit; const newProperty = `${JSON.stringify(value)}`; - if (!isArrayInsertion && parent.children.length > lastSegment) { + if (!options.isArrayInsertion && parent.children.length > lastSegment) { let toModify = parent.children[lastSegment]; edit = { offset: toModify.offset, length: toModify.length, content: newProperty } @@ -130,18 +130,18 @@ 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); + return withFormatting(text, edit, options); } else { - throw new Error(`Can not ${value === void 0 ? 'remove' : (isArrayInsertion ? 'insert' : 'modify')} Array index ${insertIndex} as length is not sufficient`); + throw new Error(`Can not ${value === void 0 ? 'remove' : (options.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 }] +function withFormatting(text: string, edit: Edit, options: ModificationOptions): Edit[] { + if (!options.formattingOptions) { + return [edit] } // apply the edit let newText = applyEdit(text, edit); @@ -158,7 +158,7 @@ function withFormatting(text: string, edit: Edit, formattingOptions: FormattingO } } - let edits = format(newText, { offset: begin, length: end - begin }, formattingOptions); + let edits = format(newText, { offset: begin, length: end - begin }, options.formattingOptions); // apply the formatting edits and track the begin and end offsets of the changes for (let i = edits.length - 1; i >= 0; i--) { diff --git a/src/main.ts b/src/main.ts index a342860..6ab9ef4 100644 --- a/src/main.ts +++ b/src/main.ts @@ -322,11 +322,6 @@ 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; } /** @@ -350,9 +345,9 @@ export function format(documentText: string, range: Range | undefined, options: */ export interface ModificationOptions { /** - * Formatting options + * Formatting options. If undefined, the newly inserted code will be inserted unformatted. */ - formattingOptions: FormattingOptions; + 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. @@ -380,7 +375,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, options.isArrayInsertion); + return edit.setProperty(text, path, value, options); } /** diff --git a/src/test/edit.test.ts b/src/test/edit.test.ts index 51b1b20..7a8e450 100644 --- a/src/test/edit.test.ts +++ b/src/test/edit.test.ts @@ -5,7 +5,7 @@ 'use strict'; import * as assert from 'assert'; -import { FormattingOptions, Edit } from '../main'; +import { FormattingOptions, Edit, ModificationOptions } from '../main'; import { setProperty, removeProperty } from '../impl/edit'; suite('JSON - edits', () => { @@ -24,198 +24,202 @@ suite('JSON - edits', () => { assert.equal(content, expected); } - let formatterOptions: FormattingOptions = { + let formattingOptions: FormattingOptions = { insertSpaces: true, tabSize: 2, eol: '\n' }; + let options: ModificationOptions = { + formattingOptions + } + test('set property', () => { let content = '{\n "x": "y"\n}'; - let edits = setProperty(content, ['x'], 'bar', formatterOptions); + let edits = setProperty(content, ['x'], 'bar', options); assertEdit(content, edits, '{\n "x": "bar"\n}'); content = 'true'; - edits = setProperty(content, [], 'bar', formatterOptions); + edits = setProperty(content, [], 'bar', options); assertEdit(content, edits, '"bar"'); content = '{\n "x": "y"\n}'; - edits = setProperty(content, ['x'], { key: true }, formatterOptions); + edits = setProperty(content, ['x'], { key: true }, options); assertEdit(content, edits, '{\n "x": {\n "key": true\n }\n}'); content = '{\n "a": "b", "x": "y"\n}'; - edits = setProperty(content, ['a'], null, formatterOptions); + edits = setProperty(content, ['a'], null, options); assertEdit(content, edits, '{\n "a": null, "x": "y"\n}'); }); test('insert property', () => { let content = '{}'; - let edits = setProperty(content, ['foo'], 'bar', formatterOptions); + let edits = setProperty(content, ['foo'], 'bar', options); assertEdit(content, edits, '{\n "foo": "bar"\n}'); - edits = setProperty(content, ['foo', 'foo2'], 'bar', formatterOptions); + edits = setProperty(content, ['foo', 'foo2'], 'bar', options); assertEdit(content, edits, '{\n "foo": {\n "foo2": "bar"\n }\n}'); content = '{\n}'; - edits = setProperty(content, ['foo'], 'bar', formatterOptions); + edits = setProperty(content, ['foo'], 'bar', options); assertEdit(content, edits, '{\n "foo": "bar"\n}'); content = ' {\n }'; - edits = setProperty(content, ['foo'], 'bar', formatterOptions); + edits = setProperty(content, ['foo'], 'bar', options); assertEdit(content, edits, ' {\n "foo": "bar"\n }'); content = '{\n "x": "y"\n}'; - edits = setProperty(content, ['foo'], 'bar', formatterOptions); + edits = setProperty(content, ['foo'], 'bar', options); assertEdit(content, edits, '{\n "x": "y",\n "foo": "bar"\n}'); content = '{\n "x": "y"\n}'; - edits = setProperty(content, ['e'], 'null', formatterOptions); + edits = setProperty(content, ['e'], 'null', options); assertEdit(content, edits, '{\n "x": "y",\n "e": "null"\n}'); - edits = setProperty(content, ['x'], 'bar', formatterOptions); + edits = setProperty(content, ['x'], 'bar', options); assertEdit(content, edits, '{\n "x": "bar"\n}'); content = '{\n "x": {\n "a": 1,\n "b": true\n }\n}\n'; - edits = setProperty(content, ['x'], 'bar', formatterOptions); + edits = setProperty(content, ['x'], 'bar', options); assertEdit(content, edits, '{\n "x": "bar"\n}\n'); - edits = setProperty(content, ['x', 'b'], 'bar', formatterOptions); + edits = setProperty(content, ['x', 'b'], 'bar', options); assertEdit(content, edits, '{\n "x": {\n "a": 1,\n "b": "bar"\n }\n}\n'); - edits = setProperty(content, ['x', 'c'], 'bar', formatterOptions, () => 0); + edits = setProperty(content, ['x', 'c'], 'bar', { formattingOptions, getInsertionIndex: () => 0 }); assertEdit(content, edits, '{\n "x": {\n "c": "bar",\n "a": 1,\n "b": true\n }\n}\n'); - edits = setProperty(content, ['x', 'c'], 'bar', formatterOptions, () => 1); + edits = setProperty(content, ['x', 'c'], 'bar', { formattingOptions, getInsertionIndex: () => 1 }); assertEdit(content, edits, '{\n "x": {\n "a": 1,\n "c": "bar",\n "b": true\n }\n}\n'); - edits = setProperty(content, ['x', 'c'], 'bar', formatterOptions, () => 2); + edits = setProperty(content, ['x', 'c'], 'bar', { formattingOptions, getInsertionIndex: () => 2 }); assertEdit(content, edits, '{\n "x": {\n "a": 1,\n "b": true,\n "c": "bar"\n }\n}\n'); - edits = setProperty(content, ['c'], 'bar', formatterOptions); + edits = setProperty(content, ['c'], 'bar', options); assertEdit(content, edits, '{\n "x": {\n "a": 1,\n "b": true\n },\n "c": "bar"\n}\n'); content = '{\n "a": [\n {\n } \n ] \n}'; - edits = setProperty(content, ['foo'], 'bar', formatterOptions); + edits = setProperty(content, ['foo'], 'bar', options); assertEdit(content, edits, '{\n "a": [\n {\n } \n ],\n "foo": "bar"\n}'); content = ''; - edits = setProperty(content, ['foo', 0], 'bar', formatterOptions); + edits = setProperty(content, ['foo', 0], 'bar', options); assertEdit(content, edits, '{\n "foo": [\n "bar"\n ]\n}'); content = '//comment'; - edits = setProperty(content, ['foo', 0], 'bar', formatterOptions); + edits = setProperty(content, ['foo', 0], 'bar', options); assertEdit(content, edits, '{\n "foo": [\n "bar"\n ]\n} //comment'); }); test('remove property', () => { let content = '{\n "x": "y"\n}'; - let edits = removeProperty(content, ['x'], formatterOptions); + let edits = removeProperty(content, ['x'], options); assertEdit(content, edits, '{\n}'); content = '{\n "x": "y", "a": []\n}'; - edits = removeProperty(content, ['x'], formatterOptions); + edits = removeProperty(content, ['x'], options); assertEdit(content, edits, '{\n "a": []\n}'); content = '{\n "x": "y", "a": []\n}'; - edits = removeProperty(content, ['a'], formatterOptions); + edits = removeProperty(content, ['a'], options); 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); + let edits = setProperty(content, ['x', 0], 6, options); assertEdit(content, edits, '{\n "x": [6, 2, 3],\n "y": 0\n}'); - edits = setProperty(content, ['x', 1], 5, formatterOptions); + edits = setProperty(content, ['x', 1], 5, options); assertEdit(content, edits, '{\n "x": [1, 5, 3],\n "y": 0\n}'); - edits = setProperty(content, ['x', 2], 4, formatterOptions); + edits = setProperty(content, ['x', 2], 4, options); assertEdit(content, edits, '{\n "x": [1, 2, 4],\n "y": 0\n}'); - edits = setProperty(content, ['x', 3], 3, formatterOptions) + edits = setProperty(content, ['x', 3], 3, options) 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); + let edits = setProperty(content, [0], 1, { formattingOptions, isArrayInsertion: 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); + let edits = setProperty(content, [0], 1, options); 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); + let edits = setProperty(content, [1], 2, { formattingOptions, isArrayInsertion: 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); + let edits = setProperty(content, [1], 1, options); 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); + let edits = setProperty(content, [2], 3, options); 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); + let edits = setProperty(content, [-1], 'bar', options); assertEdit(content, edits, '[\n "bar"\n]'); }); test('insert item at end', () => { let content = '[\n 1,\n 2\n]'; - let edits = setProperty(content, [-1], 'bar', formatterOptions); + let edits = setProperty(content, [-1], 'bar', options); assertEdit(content, edits, '[\n 1,\n 2,\n "bar"\n]'); }); test('remove item in array with one item', () => { let content = '[\n 1\n]'; - let edits = setProperty(content, [0], void 0, formatterOptions); + let edits = setProperty(content, [0], void 0, options); assertEdit(content, edits, '[]'); }); test('remove item in the middle of the array', () => { let content = '[\n 1,\n 2,\n 3\n]'; - let edits = setProperty(content, [1], void 0, formatterOptions); + let edits = setProperty(content, [1], void 0, options); assertEdit(content, edits, '[\n 1,\n 3\n]'); }); test('remove last item in the array', () => { let content = '[\n 1,\n 2,\n "bar"\n]'; - let edits = setProperty(content, [2], void 0, formatterOptions); + let edits = setProperty(content, [2], void 0, options); assertEdit(content, edits, '[\n 1,\n 2\n]'); }); test('remove last item in the array if ends with comma', () => { let content = '[\n 1,\n "foo",\n "bar",\n]'; - let edits = setProperty(content, [2], void 0, formatterOptions); + let edits = setProperty(content, [2], void 0, options); assertEdit(content, edits, '[\n 1,\n "foo"\n]'); }); test('remove last item in the array if there is a comment in the beginning', () => { let content = '// This is a comment\n[\n 1,\n "foo",\n "bar"\n]'; - let edits = setProperty(content, [2], void 0, formatterOptions); + let edits = setProperty(content, [2], void 0, options); assertEdit(content, edits, '// This is a comment\n[\n 1,\n "foo"\n]'); }); - test('set property w/ in-place formatting options', () => { + test('set property without formatting', () => { let content = '{\n "x": [1, 2, 3],\n "y": 0\n}' - let edits = setProperty(content, ['x', 0], { a: 1, b: 2 }, formatterOptions); + let edits = setProperty(content, ['x', 0], { a: 1, b: 2 }, { formattingOptions }); 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 }); + edits = setProperty(content, ['x', 0], { a: 1, b: 2 }, { formattingOptions: undefined }); assertEdit(content, edits, '{\n "x": [{"a":1,"b":2}, 2, 3],\n "y": 0\n}'); }); }); \ No newline at end of file