1 | /*---------------------------------------------------------------------------------------------
|
---|
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
|
---|
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
|
---|
4 | *--------------------------------------------------------------------------------------------*/
|
---|
5 | 'use strict';
|
---|
6 | import { format, isEOL } from './format';
|
---|
7 | import { parseTree, findNodeAtLocation } from './parser';
|
---|
8 | export function removeProperty(text, path, options) {
|
---|
9 | return setProperty(text, path, void 0, options);
|
---|
10 | }
|
---|
11 | export function setProperty(text, originalPath, value, options) {
|
---|
12 | var _a;
|
---|
13 | var path = originalPath.slice();
|
---|
14 | var errors = [];
|
---|
15 | var root = parseTree(text, errors);
|
---|
16 | var parent = void 0;
|
---|
17 | var lastSegment = void 0;
|
---|
18 | while (path.length > 0) {
|
---|
19 | lastSegment = path.pop();
|
---|
20 | parent = findNodeAtLocation(root, path);
|
---|
21 | if (parent === void 0 && value !== void 0) {
|
---|
22 | if (typeof lastSegment === 'string') {
|
---|
23 | value = (_a = {}, _a[lastSegment] = value, _a);
|
---|
24 | }
|
---|
25 | else {
|
---|
26 | value = [value];
|
---|
27 | }
|
---|
28 | }
|
---|
29 | else {
|
---|
30 | break;
|
---|
31 | }
|
---|
32 | }
|
---|
33 | if (!parent) {
|
---|
34 | // empty document
|
---|
35 | if (value === void 0) { // delete
|
---|
36 | throw new Error('Can not delete in empty document');
|
---|
37 | }
|
---|
38 | return withFormatting(text, { offset: root ? root.offset : 0, length: root ? root.length : 0, content: JSON.stringify(value) }, options);
|
---|
39 | }
|
---|
40 | else if (parent.type === 'object' && typeof lastSegment === 'string' && Array.isArray(parent.children)) {
|
---|
41 | var existing = findNodeAtLocation(parent, [lastSegment]);
|
---|
42 | if (existing !== void 0) {
|
---|
43 | if (value === void 0) { // delete
|
---|
44 | if (!existing.parent) {
|
---|
45 | throw new Error('Malformed AST');
|
---|
46 | }
|
---|
47 | var propertyIndex = parent.children.indexOf(existing.parent);
|
---|
48 | var removeBegin = void 0;
|
---|
49 | var removeEnd = existing.parent.offset + existing.parent.length;
|
---|
50 | if (propertyIndex > 0) {
|
---|
51 | // remove the comma of the previous node
|
---|
52 | var previous = parent.children[propertyIndex - 1];
|
---|
53 | removeBegin = previous.offset + previous.length;
|
---|
54 | }
|
---|
55 | else {
|
---|
56 | removeBegin = parent.offset + 1;
|
---|
57 | if (parent.children.length > 1) {
|
---|
58 | // remove the comma of the next node
|
---|
59 | var next = parent.children[1];
|
---|
60 | removeEnd = next.offset;
|
---|
61 | }
|
---|
62 | }
|
---|
63 | return withFormatting(text, { offset: removeBegin, length: removeEnd - removeBegin, content: '' }, options);
|
---|
64 | }
|
---|
65 | else {
|
---|
66 | // set value of existing property
|
---|
67 | return withFormatting(text, { offset: existing.offset, length: existing.length, content: JSON.stringify(value) }, options);
|
---|
68 | }
|
---|
69 | }
|
---|
70 | else {
|
---|
71 | if (value === void 0) { // delete
|
---|
72 | return []; // property does not exist, nothing to do
|
---|
73 | }
|
---|
74 | var newProperty = JSON.stringify(lastSegment) + ": " + JSON.stringify(value);
|
---|
75 | var index = options.getInsertionIndex ? options.getInsertionIndex(parent.children.map(function (p) { return p.children[0].value; })) : parent.children.length;
|
---|
76 | var edit = void 0;
|
---|
77 | if (index > 0) {
|
---|
78 | var previous = parent.children[index - 1];
|
---|
79 | edit = { offset: previous.offset + previous.length, length: 0, content: ',' + newProperty };
|
---|
80 | }
|
---|
81 | else if (parent.children.length === 0) {
|
---|
82 | edit = { offset: parent.offset + 1, length: 0, content: newProperty };
|
---|
83 | }
|
---|
84 | else {
|
---|
85 | edit = { offset: parent.offset + 1, length: 0, content: newProperty + ',' };
|
---|
86 | }
|
---|
87 | return withFormatting(text, edit, options);
|
---|
88 | }
|
---|
89 | }
|
---|
90 | else if (parent.type === 'array' && typeof lastSegment === 'number' && Array.isArray(parent.children)) {
|
---|
91 | var insertIndex = lastSegment;
|
---|
92 | if (insertIndex === -1) {
|
---|
93 | // Insert
|
---|
94 | var newProperty = "" + JSON.stringify(value);
|
---|
95 | var edit = void 0;
|
---|
96 | if (parent.children.length === 0) {
|
---|
97 | edit = { offset: parent.offset + 1, length: 0, content: newProperty };
|
---|
98 | }
|
---|
99 | else {
|
---|
100 | var previous = parent.children[parent.children.length - 1];
|
---|
101 | edit = { offset: previous.offset + previous.length, length: 0, content: ',' + newProperty };
|
---|
102 | }
|
---|
103 | return withFormatting(text, edit, options);
|
---|
104 | }
|
---|
105 | else if (value === void 0 && parent.children.length >= 0) {
|
---|
106 | // Removal
|
---|
107 | var removalIndex = lastSegment;
|
---|
108 | var toRemove = parent.children[removalIndex];
|
---|
109 | var edit = void 0;
|
---|
110 | if (parent.children.length === 1) {
|
---|
111 | // only item
|
---|
112 | edit = { offset: parent.offset + 1, length: parent.length - 2, content: '' };
|
---|
113 | }
|
---|
114 | else if (parent.children.length - 1 === removalIndex) {
|
---|
115 | // last item
|
---|
116 | var previous = parent.children[removalIndex - 1];
|
---|
117 | var offset = previous.offset + previous.length;
|
---|
118 | var parentEndOffset = parent.offset + parent.length;
|
---|
119 | edit = { offset: offset, length: parentEndOffset - 2 - offset, content: '' };
|
---|
120 | }
|
---|
121 | else {
|
---|
122 | edit = { offset: toRemove.offset, length: parent.children[removalIndex + 1].offset - toRemove.offset, content: '' };
|
---|
123 | }
|
---|
124 | return withFormatting(text, edit, options);
|
---|
125 | }
|
---|
126 | else if (value !== void 0) {
|
---|
127 | var edit = void 0;
|
---|
128 | var newProperty = "" + JSON.stringify(value);
|
---|
129 | if (!options.isArrayInsertion && parent.children.length > lastSegment) {
|
---|
130 | var toModify = parent.children[lastSegment];
|
---|
131 | edit = { offset: toModify.offset, length: toModify.length, content: newProperty };
|
---|
132 | }
|
---|
133 | else if (parent.children.length === 0 || lastSegment === 0) {
|
---|
134 | edit = { offset: parent.offset + 1, length: 0, content: parent.children.length === 0 ? newProperty : newProperty + ',' };
|
---|
135 | }
|
---|
136 | else {
|
---|
137 | var index = lastSegment > parent.children.length ? parent.children.length : lastSegment;
|
---|
138 | var previous = parent.children[index - 1];
|
---|
139 | edit = { offset: previous.offset + previous.length, length: 0, content: ',' + newProperty };
|
---|
140 | }
|
---|
141 | return withFormatting(text, edit, options);
|
---|
142 | }
|
---|
143 | else {
|
---|
144 | throw new Error("Can not " + (value === void 0 ? 'remove' : (options.isArrayInsertion ? 'insert' : 'modify')) + " Array index " + insertIndex + " as length is not sufficient");
|
---|
145 | }
|
---|
146 | }
|
---|
147 | else {
|
---|
148 | throw new Error("Can not add " + (typeof lastSegment !== 'number' ? 'index' : 'property') + " to parent of type " + parent.type);
|
---|
149 | }
|
---|
150 | }
|
---|
151 | function withFormatting(text, edit, options) {
|
---|
152 | if (!options.formattingOptions) {
|
---|
153 | return [edit];
|
---|
154 | }
|
---|
155 | // apply the edit
|
---|
156 | var newText = applyEdit(text, edit);
|
---|
157 | // format the new text
|
---|
158 | var begin = edit.offset;
|
---|
159 | var end = edit.offset + edit.content.length;
|
---|
160 | if (edit.length === 0 || edit.content.length === 0) { // insert or remove
|
---|
161 | while (begin > 0 && !isEOL(newText, begin - 1)) {
|
---|
162 | begin--;
|
---|
163 | }
|
---|
164 | while (end < newText.length && !isEOL(newText, end)) {
|
---|
165 | end++;
|
---|
166 | }
|
---|
167 | }
|
---|
168 | var edits = format(newText, { offset: begin, length: end - begin }, options.formattingOptions);
|
---|
169 | // apply the formatting edits and track the begin and end offsets of the changes
|
---|
170 | for (var i = edits.length - 1; i >= 0; i--) {
|
---|
171 | var edit_1 = edits[i];
|
---|
172 | newText = applyEdit(newText, edit_1);
|
---|
173 | begin = Math.min(begin, edit_1.offset);
|
---|
174 | end = Math.max(end, edit_1.offset + edit_1.length);
|
---|
175 | end += edit_1.content.length - edit_1.length;
|
---|
176 | }
|
---|
177 | // create a single edit with all changes
|
---|
178 | var editLength = text.length - (newText.length - end) - begin;
|
---|
179 | return [{ offset: begin, length: editLength, content: newText.substring(begin, end) }];
|
---|
180 | }
|
---|
181 | export function applyEdit(text, edit) {
|
---|
182 | return text.substring(0, edit.offset) + edit.content + text.substring(edit.offset + edit.length);
|
---|
183 | }
|
---|
184 | export function isWS(text, offset) {
|
---|
185 | return '\r\n \t'.indexOf(text.charAt(offset)) !== -1;
|
---|
186 | }
|
---|