jsonc-effect 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ast.js +255 -0
- package/equality.js +166 -0
- package/errors.js +218 -0
- package/format.js +453 -0
- package/index.d.ts +1933 -1983
- package/index.js +11 -1454
- package/package.json +50 -56
- package/parse.js +548 -0
- package/scanner.js +350 -0
- package/schema-integration.js +178 -0
- package/schemas.js +325 -0
- package/tsdoc-metadata.json +11 -11
- package/visitor.js +355 -0
package/format.js
ADDED
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
import { JsoncModificationError } from "./errors.js";
|
|
2
|
+
import { createScanner } from "./scanner.js";
|
|
3
|
+
import { Effect, Function } from "effect";
|
|
4
|
+
|
|
5
|
+
//#region src/format.ts
|
|
6
|
+
/**
|
|
7
|
+
* JSONC formatting, modification, and edit application.
|
|
8
|
+
*
|
|
9
|
+
* All functions compute edits (not mutations) — a natural fit
|
|
10
|
+
* for Effect's functional style.
|
|
11
|
+
*
|
|
12
|
+
* @packageDocumentation
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Compute formatting edits for a JSONC document.
|
|
16
|
+
*
|
|
17
|
+
* Returns an array of {@link JsoncEdit} objects describing text replacements
|
|
18
|
+
* that, when applied, produce a well-formatted document. The input text is
|
|
19
|
+
* never mutated — apply the returned edits with {@link applyEdits}.
|
|
20
|
+
*
|
|
21
|
+
* @param text - The JSONC source text to format.
|
|
22
|
+
* @param range - Optional sub-range to format. When provided, only edits
|
|
23
|
+
* within the range are returned.
|
|
24
|
+
* @param options - Partial {@link JsoncFormattingOptions} controlling indent
|
|
25
|
+
* size, tabs vs. spaces, EOL style, and final-newline insertion.
|
|
26
|
+
* @returns An `Effect` that succeeds with a read-only array of
|
|
27
|
+
* {@link JsoncEdit} objects.
|
|
28
|
+
*
|
|
29
|
+
* @remarks
|
|
30
|
+
* This function is non-mutating by design. It computes a minimal set of
|
|
31
|
+
* whitespace edits; structural changes (inserting or removing properties)
|
|
32
|
+
* are handled by {@link modify}. The `range` parameter allows formatting a
|
|
33
|
+
* subset of the document without touching surrounding text.
|
|
34
|
+
*
|
|
35
|
+
* @see {@link applyEdits} to apply the returned edits to the source text.
|
|
36
|
+
* @see {@link formatAndApply} for a one-step format-and-apply convenience.
|
|
37
|
+
* @see {@link JsoncFormattingOptions} for available formatting controls.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* import { Effect } from "effect";
|
|
42
|
+
* import type { JsoncEdit } from "jsonc-effect";
|
|
43
|
+
* import { format, applyEdits } from "jsonc-effect";
|
|
44
|
+
*
|
|
45
|
+
* const input = '{"a":1, "b" :2}';
|
|
46
|
+
* const edits: ReadonlyArray<JsoncEdit> = Effect.runSync(format(input));
|
|
47
|
+
* const formatted: string = Effect.runSync(applyEdits(input, edits));
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* @privateRemarks
|
|
51
|
+
* Uses {@link createScanner} internally to tokenize the input and compute
|
|
52
|
+
* whitespace adjustments between consecutive non-trivia tokens.
|
|
53
|
+
*
|
|
54
|
+
* @public
|
|
55
|
+
*/
|
|
56
|
+
const format = (text, range, options) => Effect.sync(() => formatImpl(text, range, options));
|
|
57
|
+
/**
|
|
58
|
+
* Apply an array of text edits to JSONC source text.
|
|
59
|
+
*
|
|
60
|
+
* This is a {@link https://effect.website/docs/function-dual | Function.dual}
|
|
61
|
+
* that supports both data-first and data-last (pipeline) usage.
|
|
62
|
+
*
|
|
63
|
+
* @param text - The original JSONC source text.
|
|
64
|
+
* @param edits - A read-only array of {@link JsoncEdit} objects, typically
|
|
65
|
+
* produced by {@link format} or {@link modify}.
|
|
66
|
+
* @returns An `Effect` that succeeds with the edited string.
|
|
67
|
+
*
|
|
68
|
+
* @remarks
|
|
69
|
+
* Edits are sorted in reverse offset order before application so that
|
|
70
|
+
* earlier edits do not shift the offsets of later ones. The original `edits`
|
|
71
|
+
* array is not mutated.
|
|
72
|
+
*
|
|
73
|
+
* @see {@link format} to compute formatting edits.
|
|
74
|
+
* @see {@link modify} to compute structural edits (insert, replace, remove).
|
|
75
|
+
*
|
|
76
|
+
* @example Data-first usage
|
|
77
|
+
* ```ts
|
|
78
|
+
* import { Effect } from "effect";
|
|
79
|
+
* import { format, applyEdits } from "jsonc-effect";
|
|
80
|
+
*
|
|
81
|
+
* const input = '{"a":1}';
|
|
82
|
+
* const edits = Effect.runSync(format(input));
|
|
83
|
+
* const result: string = Effect.runSync(applyEdits(input, edits));
|
|
84
|
+
* ```
|
|
85
|
+
*
|
|
86
|
+
* @example Pipeline with modify
|
|
87
|
+
* ```ts
|
|
88
|
+
* import { Effect, pipe } from "effect";
|
|
89
|
+
* import { modify, applyEdits } from "jsonc-effect";
|
|
90
|
+
*
|
|
91
|
+
* const input = '{ "a": 1 }';
|
|
92
|
+
* const result = pipe(
|
|
93
|
+
* input,
|
|
94
|
+
* modify(["a"], 42),
|
|
95
|
+
* Effect.flatMap((edits) => applyEdits(input, edits)),
|
|
96
|
+
* Effect.runSync,
|
|
97
|
+
* );
|
|
98
|
+
* ```
|
|
99
|
+
*
|
|
100
|
+
* @public
|
|
101
|
+
*/
|
|
102
|
+
const applyEdits = Function.dual(2, (text, edits) => Effect.sync(() => {
|
|
103
|
+
const sorted = [...edits].sort((a, b) => b.offset - a.offset);
|
|
104
|
+
let result = text;
|
|
105
|
+
for (const edit of sorted) result = result.substring(0, edit.offset) + edit.content + result.substring(edit.offset + edit.length);
|
|
106
|
+
return result;
|
|
107
|
+
}));
|
|
108
|
+
/**
|
|
109
|
+
* Format a JSONC document in one step by computing edits and immediately
|
|
110
|
+
* applying them.
|
|
111
|
+
*
|
|
112
|
+
* This is a convenience that combines {@link format} and {@link applyEdits}
|
|
113
|
+
* into a single call.
|
|
114
|
+
*
|
|
115
|
+
* @param text - The JSONC source text to format.
|
|
116
|
+
* @param range - Optional sub-range to restrict formatting to.
|
|
117
|
+
* @param options - Partial {@link JsoncFormattingOptions} controlling indent
|
|
118
|
+
* size, tabs vs. spaces, EOL style, and final-newline insertion.
|
|
119
|
+
* @returns An `Effect` that succeeds with the fully formatted string.
|
|
120
|
+
*
|
|
121
|
+
* @see {@link format} to obtain the edits without applying them.
|
|
122
|
+
* @see {@link applyEdits} to apply edits separately.
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* ```ts
|
|
126
|
+
* import { Effect } from "effect";
|
|
127
|
+
* import { formatAndApply } from "jsonc-effect";
|
|
128
|
+
*
|
|
129
|
+
* const input = '{"a":1, "b" :2}';
|
|
130
|
+
* const formatted: string = Effect.runSync(formatAndApply(input));
|
|
131
|
+
* ```
|
|
132
|
+
*
|
|
133
|
+
* @public
|
|
134
|
+
*/
|
|
135
|
+
const formatAndApply = (text, range, options) => format(text, range, options).pipe(Effect.flatMap((edits) => applyEdits(text, edits)));
|
|
136
|
+
/**
|
|
137
|
+
* Compute edits to insert, replace, or remove a value at a JSON path.
|
|
138
|
+
*
|
|
139
|
+
* This is a {@link https://effect.website/docs/function-dual | Function.dual}
|
|
140
|
+
* that supports both data-first and data-last (pipeline) usage.
|
|
141
|
+
*
|
|
142
|
+
* @param text - The JSONC source text to modify.
|
|
143
|
+
* @param path - A {@link (JsoncPath:type)} (array of string keys and numeric indices)
|
|
144
|
+
* identifying the target location in the JSON structure.
|
|
145
|
+
* @param value - The value to set. Pass `undefined` to remove the
|
|
146
|
+
* property or array element at the given path.
|
|
147
|
+
* @param options - Optional object with `formattingOptions` controlling
|
|
148
|
+
* indent size, tabs vs. spaces, and EOL style for generated text.
|
|
149
|
+
* @returns An `Effect` that succeeds with a read-only array of
|
|
150
|
+
* {@link JsoncEdit} objects, or fails with a
|
|
151
|
+
* {@link JsoncModificationError} if the path cannot be navigated.
|
|
152
|
+
*
|
|
153
|
+
* @remarks
|
|
154
|
+
* Setting `value` to `undefined` removes the targeted property or element,
|
|
155
|
+
* including its surrounding comma. When inserting a new property into an
|
|
156
|
+
* object, it is appended after the last existing property.
|
|
157
|
+
*
|
|
158
|
+
* @see {@link applyEdits} to apply the returned edits to the source text.
|
|
159
|
+
* @see {@link JsoncModificationError} for the error type on navigation failure.
|
|
160
|
+
*
|
|
161
|
+
* @example Update an existing property
|
|
162
|
+
* ```ts
|
|
163
|
+
* import { Effect } from "effect";
|
|
164
|
+
* import { modify, applyEdits } from "jsonc-effect";
|
|
165
|
+
*
|
|
166
|
+
* const input = '{ "a": 1 }';
|
|
167
|
+
* const edits = Effect.runSync(modify(input, ["a"], 2));
|
|
168
|
+
* const result: string = Effect.runSync(applyEdits(input, edits));
|
|
169
|
+
* ```
|
|
170
|
+
*
|
|
171
|
+
* @example Insert a new property
|
|
172
|
+
* ```ts
|
|
173
|
+
* import { Effect } from "effect";
|
|
174
|
+
* import { modify, applyEdits } from "jsonc-effect";
|
|
175
|
+
*
|
|
176
|
+
* const input = '{ "a": 1 }';
|
|
177
|
+
* const edits = Effect.runSync(modify(input, ["b"], "hello"));
|
|
178
|
+
* const result: string = Effect.runSync(applyEdits(input, edits));
|
|
179
|
+
* ```
|
|
180
|
+
*
|
|
181
|
+
* @example Remove a property
|
|
182
|
+
* ```ts
|
|
183
|
+
* import { Effect } from "effect";
|
|
184
|
+
* import { modify, applyEdits } from "jsonc-effect";
|
|
185
|
+
*
|
|
186
|
+
* const input = '{ "a": 1, "b": 2 }';
|
|
187
|
+
* const edits = Effect.runSync(modify(input, ["a"], undefined));
|
|
188
|
+
* const result: string = Effect.runSync(applyEdits(input, edits));
|
|
189
|
+
* ```
|
|
190
|
+
*
|
|
191
|
+
* @example Pipeline (data-last) usage
|
|
192
|
+
* ```ts
|
|
193
|
+
* import { Effect, pipe } from "effect";
|
|
194
|
+
* import { modify, applyEdits } from "jsonc-effect";
|
|
195
|
+
*
|
|
196
|
+
* const input = '{ "a": 1 }';
|
|
197
|
+
* const result = pipe(
|
|
198
|
+
* input,
|
|
199
|
+
* modify(["a"], 42),
|
|
200
|
+
* Effect.flatMap((edits) => applyEdits(input, edits)),
|
|
201
|
+
* Effect.runSync,
|
|
202
|
+
* );
|
|
203
|
+
* ```
|
|
204
|
+
*
|
|
205
|
+
* @privateRemarks
|
|
206
|
+
* Uses its own scanner-based navigation to locate the target path rather
|
|
207
|
+
* than building a full AST via `parseTree`. This keeps the implementation
|
|
208
|
+
* lightweight and avoids an intermediate allocation.
|
|
209
|
+
*
|
|
210
|
+
* @public
|
|
211
|
+
*/
|
|
212
|
+
const modify = Function.dual((args) => typeof args[0] === "string" && Array.isArray(args[1]), (text, path, value, options) => Effect.try({
|
|
213
|
+
try: () => modifyImpl(text, path, value, options?.formattingOptions),
|
|
214
|
+
catch: (e) => new JsoncModificationError({
|
|
215
|
+
path,
|
|
216
|
+
reason: String(e)
|
|
217
|
+
})
|
|
218
|
+
}));
|
|
219
|
+
function formatImpl(text, range, options) {
|
|
220
|
+
const opts = {
|
|
221
|
+
tabSize: options?.tabSize ?? 2,
|
|
222
|
+
insertSpaces: options?.insertSpaces ?? true,
|
|
223
|
+
eol: options?.eol ?? "\n",
|
|
224
|
+
insertFinalNewline: options?.insertFinalNewline ?? false,
|
|
225
|
+
keepLines: options?.keepLines ?? false
|
|
226
|
+
};
|
|
227
|
+
const indentUnit = opts.insertSpaces ? " ".repeat(opts.tabSize) : " ";
|
|
228
|
+
const edits = [];
|
|
229
|
+
const scanner = createScanner(text, false);
|
|
230
|
+
const rangeStart = range?.offset ?? 0;
|
|
231
|
+
const rangeEnd = range ? range.offset + range.length : text.length;
|
|
232
|
+
let depth = 0;
|
|
233
|
+
let prevTokenEnd = -1;
|
|
234
|
+
let prevToken = "Unknown";
|
|
235
|
+
let firstToken = true;
|
|
236
|
+
function makeIndent(d) {
|
|
237
|
+
return indentUnit.repeat(d);
|
|
238
|
+
}
|
|
239
|
+
function addEdit(offset, length, content) {
|
|
240
|
+
if (offset >= rangeStart && offset + length <= rangeEnd) {
|
|
241
|
+
if (text.substring(offset, offset + length) !== content) edits.push({
|
|
242
|
+
offset,
|
|
243
|
+
length,
|
|
244
|
+
content
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
let kind = scanner.scan();
|
|
249
|
+
while (kind !== "EOF") {
|
|
250
|
+
const tokenOffset = scanner.getTokenOffset();
|
|
251
|
+
const tokenLength = scanner.getTokenLength();
|
|
252
|
+
if (kind !== "Trivia" && kind !== "LineBreak") {
|
|
253
|
+
if (!firstToken && prevTokenEnd >= 0) {
|
|
254
|
+
const gap = text.substring(prevTokenEnd, tokenOffset);
|
|
255
|
+
let expectedGap;
|
|
256
|
+
if (kind === "CloseBrace" || kind === "CloseBracket") {
|
|
257
|
+
depth--;
|
|
258
|
+
expectedGap = opts.eol + makeIndent(depth);
|
|
259
|
+
} else if (prevToken === "OpenBrace" || prevToken === "OpenBracket") expectedGap = opts.eol + makeIndent(depth);
|
|
260
|
+
else if (prevToken === "Comma") expectedGap = opts.eol + makeIndent(depth);
|
|
261
|
+
else if (prevToken === "Colon") expectedGap = " ";
|
|
262
|
+
else if (kind === "LineComment" || kind === "BlockComment") if (gap.includes("\n")) expectedGap = opts.eol + makeIndent(depth);
|
|
263
|
+
else expectedGap = " ";
|
|
264
|
+
else if (prevToken === "LineComment") expectedGap = opts.eol + makeIndent(depth);
|
|
265
|
+
else if (prevToken === "BlockComment") if (gap.includes("\n")) expectedGap = opts.eol + makeIndent(depth);
|
|
266
|
+
else expectedGap = " ";
|
|
267
|
+
else expectedGap = gap;
|
|
268
|
+
if (opts.keepLines && gap.includes("\n")) expectedGap = gap;
|
|
269
|
+
addEdit(prevTokenEnd, tokenOffset - prevTokenEnd, expectedGap);
|
|
270
|
+
}
|
|
271
|
+
if (kind === "OpenBrace" || kind === "OpenBracket") depth++;
|
|
272
|
+
prevToken = kind;
|
|
273
|
+
prevTokenEnd = tokenOffset + tokenLength;
|
|
274
|
+
firstToken = false;
|
|
275
|
+
}
|
|
276
|
+
kind = scanner.scan();
|
|
277
|
+
}
|
|
278
|
+
if (opts.insertFinalNewline && prevTokenEnd >= 0) {
|
|
279
|
+
const trailing = text.substring(prevTokenEnd);
|
|
280
|
+
if (!trailing.endsWith(opts.eol)) edits.push({
|
|
281
|
+
offset: prevTokenEnd,
|
|
282
|
+
length: trailing.length,
|
|
283
|
+
content: opts.eol
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
return edits;
|
|
287
|
+
}
|
|
288
|
+
function modifyImpl(text, path, value, formattingOptions) {
|
|
289
|
+
const opts = {
|
|
290
|
+
tabSize: formattingOptions?.tabSize ?? 2,
|
|
291
|
+
insertSpaces: formattingOptions?.insertSpaces ?? true,
|
|
292
|
+
eol: formattingOptions?.eol ?? "\n"
|
|
293
|
+
};
|
|
294
|
+
const indentUnit = opts.insertSpaces ? " ".repeat(opts.tabSize) : " ";
|
|
295
|
+
if (path.length === 0) {
|
|
296
|
+
const content = value === void 0 ? "" : JSON.stringify(value, null, opts.tabSize);
|
|
297
|
+
return [{
|
|
298
|
+
offset: 0,
|
|
299
|
+
length: text.length,
|
|
300
|
+
content
|
|
301
|
+
}];
|
|
302
|
+
}
|
|
303
|
+
const scanner = createScanner(text, true);
|
|
304
|
+
let currentToken = scanner.scan();
|
|
305
|
+
function skipValue() {
|
|
306
|
+
switch (currentToken) {
|
|
307
|
+
case "OpenBrace": {
|
|
308
|
+
currentToken = scanner.scan();
|
|
309
|
+
let first = true;
|
|
310
|
+
while (currentToken !== "CloseBrace" && currentToken !== "EOF") {
|
|
311
|
+
if (!first && currentToken === "Comma") currentToken = scanner.scan();
|
|
312
|
+
if (currentToken === "String") {
|
|
313
|
+
currentToken = scanner.scan();
|
|
314
|
+
if (currentToken === "Colon") {
|
|
315
|
+
currentToken = scanner.scan();
|
|
316
|
+
skipValue();
|
|
317
|
+
}
|
|
318
|
+
} else currentToken = scanner.scan();
|
|
319
|
+
first = false;
|
|
320
|
+
}
|
|
321
|
+
if (currentToken === "CloseBrace") currentToken = scanner.scan();
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
case "OpenBracket": {
|
|
325
|
+
currentToken = scanner.scan();
|
|
326
|
+
let first = true;
|
|
327
|
+
while (currentToken !== "CloseBracket" && currentToken !== "EOF") {
|
|
328
|
+
if (!first && currentToken === "Comma") currentToken = scanner.scan();
|
|
329
|
+
skipValue();
|
|
330
|
+
first = false;
|
|
331
|
+
}
|
|
332
|
+
if (currentToken === "CloseBracket") currentToken = scanner.scan();
|
|
333
|
+
break;
|
|
334
|
+
}
|
|
335
|
+
default:
|
|
336
|
+
currentToken = scanner.scan();
|
|
337
|
+
break;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
let depth = 0;
|
|
341
|
+
for (const segment of path) {
|
|
342
|
+
depth++;
|
|
343
|
+
if (typeof segment === "string") {
|
|
344
|
+
if (currentToken !== "OpenBrace") throw new Error(`Expected object at depth ${depth}`);
|
|
345
|
+
currentToken = scanner.scan();
|
|
346
|
+
let found = false;
|
|
347
|
+
let lastValueEnd = scanner.getTokenOffset();
|
|
348
|
+
let isFirst = true;
|
|
349
|
+
while (currentToken !== "CloseBrace" && currentToken !== "EOF") {
|
|
350
|
+
if (!isFirst && currentToken === "Comma") currentToken = scanner.scan();
|
|
351
|
+
if (currentToken === "String") {
|
|
352
|
+
const key = scanner.getTokenValue();
|
|
353
|
+
currentToken = scanner.scan();
|
|
354
|
+
if (currentToken === "Colon") currentToken = scanner.scan();
|
|
355
|
+
if (key === segment) {
|
|
356
|
+
found = true;
|
|
357
|
+
if (depth === path.length) {
|
|
358
|
+
const valueStart = scanner.getTokenOffset();
|
|
359
|
+
const prevEnd = valueStart;
|
|
360
|
+
skipValue();
|
|
361
|
+
const valueEnd = scanner.getTokenOffset();
|
|
362
|
+
if (value === void 0) {
|
|
363
|
+
let removeStart = valueStart;
|
|
364
|
+
let removeEnd = valueEnd;
|
|
365
|
+
const keyStart = text.substring(0, valueStart).lastIndexOf(`"${segment}"`);
|
|
366
|
+
if (keyStart >= 0) removeStart = keyStart;
|
|
367
|
+
const commaPosBefore = text.substring(0, removeStart).trimEnd().lastIndexOf(",");
|
|
368
|
+
if (commaPosBefore >= 0) removeStart = commaPosBefore;
|
|
369
|
+
else {
|
|
370
|
+
const trimmedAfter = text.substring(removeEnd).match(/^(\s*,)/);
|
|
371
|
+
if (trimmedAfter) removeEnd += trimmedAfter[1].length;
|
|
372
|
+
}
|
|
373
|
+
return [{
|
|
374
|
+
offset: removeStart,
|
|
375
|
+
length: removeEnd - removeStart,
|
|
376
|
+
content: ""
|
|
377
|
+
}];
|
|
378
|
+
}
|
|
379
|
+
const serialized = JSON.stringify(value, null, opts.tabSize);
|
|
380
|
+
return [{
|
|
381
|
+
offset: prevEnd,
|
|
382
|
+
length: valueEnd - prevEnd,
|
|
383
|
+
content: serialized
|
|
384
|
+
}];
|
|
385
|
+
}
|
|
386
|
+
break;
|
|
387
|
+
}
|
|
388
|
+
skipValue();
|
|
389
|
+
} else currentToken = scanner.scan();
|
|
390
|
+
lastValueEnd = scanner.getTokenOffset();
|
|
391
|
+
isFirst = false;
|
|
392
|
+
}
|
|
393
|
+
if (!found && depth === path.length && value !== void 0) {
|
|
394
|
+
const indent = indentUnit.repeat(depth);
|
|
395
|
+
const serialized = JSON.stringify(value, null, opts.tabSize);
|
|
396
|
+
const insertText = isFirst ? `${opts.eol}${indent}"${segment}": ${serialized}${opts.eol}${indentUnit.repeat(depth - 1)}` : `,${opts.eol}${indent}"${segment}": ${serialized}`;
|
|
397
|
+
return [{
|
|
398
|
+
offset: lastValueEnd,
|
|
399
|
+
length: 0,
|
|
400
|
+
content: insertText
|
|
401
|
+
}];
|
|
402
|
+
}
|
|
403
|
+
} else {
|
|
404
|
+
if (currentToken !== "OpenBracket") throw new Error(`Expected array at depth ${depth}`);
|
|
405
|
+
currentToken = scanner.scan();
|
|
406
|
+
let idx = 0;
|
|
407
|
+
let lastEnd = scanner.getTokenOffset();
|
|
408
|
+
while (currentToken !== "CloseBracket" && currentToken !== "EOF") {
|
|
409
|
+
if (idx > 0 && currentToken === "Comma") currentToken = scanner.scan();
|
|
410
|
+
if (idx === segment) {
|
|
411
|
+
if (depth === path.length) {
|
|
412
|
+
const valueStart = scanner.getTokenOffset();
|
|
413
|
+
skipValue();
|
|
414
|
+
const valueEnd = scanner.getTokenOffset();
|
|
415
|
+
if (value === void 0) {
|
|
416
|
+
let removeEnd = valueEnd;
|
|
417
|
+
if (text.substring(removeEnd).trimStart().startsWith(",")) removeEnd = text.indexOf(",", removeEnd) + 1;
|
|
418
|
+
return [{
|
|
419
|
+
offset: valueStart,
|
|
420
|
+
length: removeEnd - valueStart,
|
|
421
|
+
content: ""
|
|
422
|
+
}];
|
|
423
|
+
}
|
|
424
|
+
const serialized = JSON.stringify(value, null, opts.tabSize);
|
|
425
|
+
return [{
|
|
426
|
+
offset: valueStart,
|
|
427
|
+
length: valueEnd - valueStart,
|
|
428
|
+
content: serialized
|
|
429
|
+
}];
|
|
430
|
+
}
|
|
431
|
+
break;
|
|
432
|
+
}
|
|
433
|
+
skipValue();
|
|
434
|
+
lastEnd = scanner.getTokenOffset();
|
|
435
|
+
idx++;
|
|
436
|
+
}
|
|
437
|
+
if (idx <= segment && depth === path.length && value !== void 0) {
|
|
438
|
+
const indent = indentUnit.repeat(depth);
|
|
439
|
+
const serialized = JSON.stringify(value, null, opts.tabSize);
|
|
440
|
+
const insertText = idx === 0 ? `${opts.eol}${indent}${serialized}${opts.eol}${indentUnit.repeat(depth - 1)}` : `,${opts.eol}${indent}${serialized}`;
|
|
441
|
+
return [{
|
|
442
|
+
offset: lastEnd,
|
|
443
|
+
length: 0,
|
|
444
|
+
content: insertText
|
|
445
|
+
}];
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return [];
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
//#endregion
|
|
453
|
+
export { applyEdits, format, formatAndApply, modify };
|