jsonc-effect 0.3.0 → 0.3.1
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/README.md +17 -11
- package/format.js +33 -18
- package/package.json +1 -1
- package/parse.js +40 -15
package/README.md
CHANGED
|
@@ -1,27 +1,31 @@
|
|
|
1
1
|
# jsonc-effect
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/jsonc-effect)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://nodejs.org/)
|
|
6
|
+
[](https://www.typescriptlang.org/)
|
|
6
7
|
|
|
7
|
-
Pure [Effect](https://effect.website) JSONC (JSON with Comments) parser with no external parser dependencies. Scanner, parser, AST
|
|
8
|
+
Pure [Effect](https://effect.website) JSONC (JSON with Comments) parser with no external parser dependencies. Scanner, parser, AST and formatting are all implemented natively.
|
|
8
9
|
|
|
9
10
|
## Features
|
|
10
11
|
|
|
11
|
-
- **Effect-native** -- typed errors, Schema integration
|
|
12
|
+
- **Effect-native** -- typed errors, Schema integration and composable pipelines
|
|
12
13
|
- **Zero parser dependencies** -- `effect` is the sole runtime dependency
|
|
13
14
|
- **Schema integration** -- parse JSONC strings directly into validated types
|
|
14
|
-
- **Full toolchain** -- scanner, parser, AST navigation, visitor stream, formatting
|
|
15
|
-
- **
|
|
15
|
+
- **Full toolchain** -- scanner, parser, AST navigation, visitor stream, formatting and modification
|
|
16
|
+
- **Byte-minimal edits** -- `modify` targets exactly the value's span, preserving surrounding whitespace and comments
|
|
17
|
+
- **Equality comparisons** -- compare JSONC documents semantically, ignoring comments, formatting and key ordering
|
|
16
18
|
- **Safe by default** -- returns `unknown` (not `any`) and `Option` (not `undefined`)
|
|
17
19
|
|
|
18
|
-
##
|
|
20
|
+
## Install
|
|
19
21
|
|
|
20
22
|
```bash
|
|
21
23
|
npm install jsonc-effect effect
|
|
24
|
+
# or
|
|
25
|
+
pnpm add jsonc-effect effect
|
|
22
26
|
```
|
|
23
27
|
|
|
24
|
-
## Quick
|
|
28
|
+
## Quick start
|
|
25
29
|
|
|
26
30
|
```typescript
|
|
27
31
|
import { parse } from "jsonc-effect"
|
|
@@ -59,8 +63,10 @@ This library is for Effect-based programs that need deeper introspection and edi
|
|
|
59
63
|
|
|
60
64
|
## Documentation
|
|
61
65
|
|
|
62
|
-
|
|
66
|
+
- [Getting started](./docs/01-getting-started.md) — installation, first example, core concepts
|
|
67
|
+
- [Examples](./docs/02-examples.md) — real-world usage patterns
|
|
68
|
+
- [API reference](./docs/03-api-reference.md) — all exports with signatures and descriptions
|
|
63
69
|
|
|
64
70
|
## License
|
|
65
71
|
|
|
66
|
-
[MIT](
|
|
72
|
+
[MIT](LICENSE)
|
package/format.js
CHANGED
|
@@ -302,9 +302,13 @@ function modifyImpl(text, path, value, formattingOptions) {
|
|
|
302
302
|
}
|
|
303
303
|
const scanner = createScanner(text, true);
|
|
304
304
|
let currentToken = scanner.scan();
|
|
305
|
+
function tokenEnd() {
|
|
306
|
+
return scanner.getTokenOffset() + scanner.getTokenLength();
|
|
307
|
+
}
|
|
305
308
|
function skipValue() {
|
|
306
309
|
switch (currentToken) {
|
|
307
310
|
case "OpenBrace": {
|
|
311
|
+
let end = tokenEnd();
|
|
308
312
|
currentToken = scanner.scan();
|
|
309
313
|
let first = true;
|
|
310
314
|
while (currentToken !== "CloseBrace" && currentToken !== "EOF") {
|
|
@@ -313,28 +317,40 @@ function modifyImpl(text, path, value, formattingOptions) {
|
|
|
313
317
|
currentToken = scanner.scan();
|
|
314
318
|
if (currentToken === "Colon") {
|
|
315
319
|
currentToken = scanner.scan();
|
|
316
|
-
skipValue();
|
|
320
|
+
end = skipValue();
|
|
317
321
|
}
|
|
318
|
-
} else
|
|
322
|
+
} else {
|
|
323
|
+
end = tokenEnd();
|
|
324
|
+
currentToken = scanner.scan();
|
|
325
|
+
}
|
|
319
326
|
first = false;
|
|
320
327
|
}
|
|
321
|
-
if (currentToken === "CloseBrace")
|
|
322
|
-
|
|
328
|
+
if (currentToken === "CloseBrace") {
|
|
329
|
+
end = tokenEnd();
|
|
330
|
+
currentToken = scanner.scan();
|
|
331
|
+
}
|
|
332
|
+
return end;
|
|
323
333
|
}
|
|
324
334
|
case "OpenBracket": {
|
|
335
|
+
let end = tokenEnd();
|
|
325
336
|
currentToken = scanner.scan();
|
|
326
337
|
let first = true;
|
|
327
338
|
while (currentToken !== "CloseBracket" && currentToken !== "EOF") {
|
|
328
339
|
if (!first && currentToken === "Comma") currentToken = scanner.scan();
|
|
329
|
-
skipValue();
|
|
340
|
+
end = skipValue();
|
|
330
341
|
first = false;
|
|
331
342
|
}
|
|
332
|
-
if (currentToken === "CloseBracket")
|
|
333
|
-
|
|
343
|
+
if (currentToken === "CloseBracket") {
|
|
344
|
+
end = tokenEnd();
|
|
345
|
+
currentToken = scanner.scan();
|
|
346
|
+
}
|
|
347
|
+
return end;
|
|
334
348
|
}
|
|
335
|
-
default:
|
|
349
|
+
default: {
|
|
350
|
+
const end = tokenEnd();
|
|
336
351
|
currentToken = scanner.scan();
|
|
337
|
-
|
|
352
|
+
return end;
|
|
353
|
+
}
|
|
338
354
|
}
|
|
339
355
|
}
|
|
340
356
|
let depth = 0;
|
|
@@ -357,8 +373,7 @@ function modifyImpl(text, path, value, formattingOptions) {
|
|
|
357
373
|
if (depth === path.length) {
|
|
358
374
|
const valueStart = scanner.getTokenOffset();
|
|
359
375
|
const prevEnd = valueStart;
|
|
360
|
-
skipValue();
|
|
361
|
-
const valueEnd = scanner.getTokenOffset();
|
|
376
|
+
const valueEnd = skipValue();
|
|
362
377
|
if (value === void 0) {
|
|
363
378
|
let removeStart = valueStart;
|
|
364
379
|
let removeEnd = valueEnd;
|
|
@@ -385,9 +400,11 @@ function modifyImpl(text, path, value, formattingOptions) {
|
|
|
385
400
|
}
|
|
386
401
|
break;
|
|
387
402
|
}
|
|
388
|
-
skipValue();
|
|
389
|
-
} else
|
|
390
|
-
|
|
403
|
+
lastValueEnd = skipValue();
|
|
404
|
+
} else {
|
|
405
|
+
currentToken = scanner.scan();
|
|
406
|
+
lastValueEnd = scanner.getTokenOffset();
|
|
407
|
+
}
|
|
391
408
|
isFirst = false;
|
|
392
409
|
}
|
|
393
410
|
if (!found && depth === path.length && value !== void 0) {
|
|
@@ -410,8 +427,7 @@ function modifyImpl(text, path, value, formattingOptions) {
|
|
|
410
427
|
if (idx === segment) {
|
|
411
428
|
if (depth === path.length) {
|
|
412
429
|
const valueStart = scanner.getTokenOffset();
|
|
413
|
-
skipValue();
|
|
414
|
-
const valueEnd = scanner.getTokenOffset();
|
|
430
|
+
const valueEnd = skipValue();
|
|
415
431
|
if (value === void 0) {
|
|
416
432
|
let removeEnd = valueEnd;
|
|
417
433
|
if (text.substring(removeEnd).trimStart().startsWith(",")) removeEnd = text.indexOf(",", removeEnd) + 1;
|
|
@@ -430,8 +446,7 @@ function modifyImpl(text, path, value, formattingOptions) {
|
|
|
430
446
|
}
|
|
431
447
|
break;
|
|
432
448
|
}
|
|
433
|
-
skipValue();
|
|
434
|
-
lastEnd = scanner.getTokenOffset();
|
|
449
|
+
lastEnd = skipValue();
|
|
435
450
|
idx++;
|
|
436
451
|
}
|
|
437
452
|
if (idx <= segment && depth === path.length && value !== void 0) {
|
package/package.json
CHANGED
package/parse.js
CHANGED
|
@@ -263,6 +263,9 @@ function parseInternal(text, options, buildTree) {
|
|
|
263
263
|
}
|
|
264
264
|
}
|
|
265
265
|
}
|
|
266
|
+
function tokenEnd() {
|
|
267
|
+
return scanner.getTokenOffset() + scanner.getTokenLength();
|
|
268
|
+
}
|
|
266
269
|
function handleError(code, skipUntilAfter = [], skipUntil = []) {
|
|
267
270
|
errors.push(new JsoncParseErrorDetail({
|
|
268
271
|
code,
|
|
@@ -372,8 +375,9 @@ function parseInternal(text, options, buildTree) {
|
|
|
372
375
|
length: 0,
|
|
373
376
|
value: scanner.getTokenValue()
|
|
374
377
|
};
|
|
378
|
+
const end = tokenEnd();
|
|
375
379
|
scanNext();
|
|
376
|
-
node.length =
|
|
380
|
+
node.length = end - node.offset;
|
|
377
381
|
return node;
|
|
378
382
|
}
|
|
379
383
|
case "Number": {
|
|
@@ -383,8 +387,9 @@ function parseInternal(text, options, buildTree) {
|
|
|
383
387
|
length: 0,
|
|
384
388
|
value: Number.parseFloat(scanner.getTokenValue())
|
|
385
389
|
};
|
|
390
|
+
const end = tokenEnd();
|
|
386
391
|
scanNext();
|
|
387
|
-
node.length =
|
|
392
|
+
node.length = end - node.offset;
|
|
388
393
|
return node;
|
|
389
394
|
}
|
|
390
395
|
case "True": {
|
|
@@ -394,8 +399,9 @@ function parseInternal(text, options, buildTree) {
|
|
|
394
399
|
length: 0,
|
|
395
400
|
value: true
|
|
396
401
|
};
|
|
402
|
+
const end = tokenEnd();
|
|
397
403
|
scanNext();
|
|
398
|
-
node.length =
|
|
404
|
+
node.length = end - node.offset;
|
|
399
405
|
return node;
|
|
400
406
|
}
|
|
401
407
|
case "False": {
|
|
@@ -405,8 +411,9 @@ function parseInternal(text, options, buildTree) {
|
|
|
405
411
|
length: 0,
|
|
406
412
|
value: false
|
|
407
413
|
};
|
|
414
|
+
const end = tokenEnd();
|
|
408
415
|
scanNext();
|
|
409
|
-
node.length =
|
|
416
|
+
node.length = end - node.offset;
|
|
410
417
|
return node;
|
|
411
418
|
}
|
|
412
419
|
case "Null": {
|
|
@@ -416,8 +423,9 @@ function parseInternal(text, options, buildTree) {
|
|
|
416
423
|
length: 0,
|
|
417
424
|
value: null
|
|
418
425
|
};
|
|
426
|
+
const end = tokenEnd();
|
|
419
427
|
scanNext();
|
|
420
|
-
node.length =
|
|
428
|
+
node.length = end - node.offset;
|
|
421
429
|
return node;
|
|
422
430
|
}
|
|
423
431
|
default: return;
|
|
@@ -443,9 +451,15 @@ function parseInternal(text, options, buildTree) {
|
|
|
443
451
|
else handleError("ValueExpected", [], ["CloseBracket", "Comma"]);
|
|
444
452
|
needsComma = true;
|
|
445
453
|
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
454
|
+
let end;
|
|
455
|
+
if (token() !== "CloseBracket") {
|
|
456
|
+
handleError("CloseBracketExpected");
|
|
457
|
+
end = scanner.getTokenOffset();
|
|
458
|
+
} else {
|
|
459
|
+
end = tokenEnd();
|
|
460
|
+
scanNext();
|
|
461
|
+
}
|
|
462
|
+
node.length = end - node.offset;
|
|
449
463
|
return node;
|
|
450
464
|
}
|
|
451
465
|
function parseObjectTree() {
|
|
@@ -479,8 +493,9 @@ function parseInternal(text, options, buildTree) {
|
|
|
479
493
|
length: 0,
|
|
480
494
|
value: scanner.getTokenValue()
|
|
481
495
|
};
|
|
496
|
+
const keyEnd = tokenEnd();
|
|
482
497
|
scanNext();
|
|
483
|
-
keyNode.length =
|
|
498
|
+
keyNode.length = keyEnd - keyNode.offset;
|
|
484
499
|
property.children.push(keyNode);
|
|
485
500
|
if (token() !== "Colon") {
|
|
486
501
|
handleError("ColonExpected", [], ["CloseBrace", "Comma"]);
|
|
@@ -491,15 +506,25 @@ function parseInternal(text, options, buildTree) {
|
|
|
491
506
|
property.colonOffset = scanner.getTokenOffset();
|
|
492
507
|
scanNext();
|
|
493
508
|
const valueNode = parseValueTree();
|
|
494
|
-
if (valueNode)
|
|
495
|
-
|
|
496
|
-
|
|
509
|
+
if (valueNode) {
|
|
510
|
+
property.children.push(valueNode);
|
|
511
|
+
property.length = valueNode.offset + valueNode.length - property.offset;
|
|
512
|
+
} else {
|
|
513
|
+
handleError("ValueExpected", [], ["CloseBrace", "Comma"]);
|
|
514
|
+
property.length = scanner.getTokenOffset() - property.offset;
|
|
515
|
+
}
|
|
497
516
|
node.children.push(property);
|
|
498
517
|
needsComma = true;
|
|
499
518
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
519
|
+
let end;
|
|
520
|
+
if (token() !== "CloseBrace") {
|
|
521
|
+
handleError("CloseBraceExpected");
|
|
522
|
+
end = scanner.getTokenOffset();
|
|
523
|
+
} else {
|
|
524
|
+
end = tokenEnd();
|
|
525
|
+
scanNext();
|
|
526
|
+
}
|
|
527
|
+
node.length = end - node.offset;
|
|
503
528
|
return node;
|
|
504
529
|
}
|
|
505
530
|
scanNext();
|