jsonc-effect 0.1.0 → 0.2.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/README.md +17 -0
- package/index.d.ts +1375 -143
- package/index.js +410 -380
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -412,396 +412,135 @@ const createScanner = (text, ignoreTrivia = false)=>{
|
|
|
412
412
|
}
|
|
413
413
|
};
|
|
414
414
|
};
|
|
415
|
-
const
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
415
|
+
const parse = (text, options)=>Effect.sync(()=>parseInternal(text, options ?? {}, false)).pipe(Effect.flatMap(({ value, errors })=>{
|
|
416
|
+
if (errors.length > 0) return Effect.fail(new JsoncParseError({
|
|
417
|
+
errors,
|
|
418
|
+
text,
|
|
419
|
+
...void 0 !== options ? {
|
|
420
|
+
options
|
|
421
|
+
} : {}
|
|
422
|
+
}));
|
|
423
|
+
return Effect.succeed(value);
|
|
423
424
|
}));
|
|
424
|
-
const
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
}
|
|
425
|
+
const parseTree = (text, options)=>Effect.sync(()=>parseInternal(text, options ?? {}, true)).pipe(Effect.flatMap(({ root, errors })=>{
|
|
426
|
+
if (errors.length > 0) return Effect.fail(new JsoncParseError({
|
|
427
|
+
errors,
|
|
428
|
+
text,
|
|
429
|
+
...void 0 !== options ? {
|
|
430
|
+
options
|
|
431
|
+
} : {}
|
|
432
|
+
}));
|
|
433
|
+
return Effect.succeed(root ? Option.some(root) : Option.none());
|
|
431
434
|
}));
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
435
|
+
const stripComments = (text, replaceCh)=>Effect.sync(()=>{
|
|
436
|
+
const scanner = createScanner(text);
|
|
437
|
+
const parts = [];
|
|
438
|
+
let lastOffset = 0;
|
|
439
|
+
let kind;
|
|
440
|
+
do {
|
|
441
|
+
kind = scanner.scan();
|
|
442
|
+
const offset = scanner.getTokenOffset();
|
|
443
|
+
const length = scanner.getTokenLength();
|
|
444
|
+
if ("LineComment" === kind || "BlockComment" === kind) {
|
|
445
|
+
if (lastOffset < offset) parts.push(text.substring(lastOffset, offset));
|
|
446
|
+
if (void 0 !== replaceCh) for(let i = 0; i < length; i++){
|
|
447
|
+
const ch = text.charCodeAt(offset + i);
|
|
448
|
+
parts.push(0x0a === ch || 0x0d === ch ? text[offset + i] : replaceCh);
|
|
449
|
+
}
|
|
450
|
+
lastOffset = offset + length;
|
|
451
|
+
}
|
|
452
|
+
}while ("EOF" !== kind);
|
|
453
|
+
if (lastOffset < text.length) parts.push(text.substring(lastOffset));
|
|
454
|
+
return parts.join("");
|
|
455
|
+
});
|
|
456
|
+
function parseInternal(text, options, buildTree) {
|
|
442
457
|
const scanner = createScanner(text, false);
|
|
443
|
-
const
|
|
444
|
-
const
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
let
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
return indentUnit.repeat(d);
|
|
451
|
-
}
|
|
452
|
-
function addEdit(offset, length, content) {
|
|
453
|
-
if (offset >= rangeStart && offset + length <= rangeEnd) {
|
|
454
|
-
if (text.substring(offset, offset + length) !== content) edits.push({
|
|
455
|
-
offset,
|
|
456
|
-
length,
|
|
457
|
-
content
|
|
458
|
-
});
|
|
459
|
-
}
|
|
458
|
+
const errors = [];
|
|
459
|
+
const disallowComments = options.disallowComments ?? false;
|
|
460
|
+
const allowTrailingComma = options.allowTrailingComma ?? true;
|
|
461
|
+
const allowEmptyContent = options.allowEmptyContent ?? false;
|
|
462
|
+
let currentToken = "Unknown";
|
|
463
|
+
function token() {
|
|
464
|
+
return currentToken;
|
|
460
465
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
466
|
+
function scanNext() {
|
|
467
|
+
for(;;){
|
|
468
|
+
currentToken = scanner.scan();
|
|
469
|
+
switch(scanner.getTokenError()){
|
|
470
|
+
case "InvalidUnicode":
|
|
471
|
+
handleError("InvalidUnicode");
|
|
472
|
+
break;
|
|
473
|
+
case "InvalidEscapeCharacter":
|
|
474
|
+
handleError("InvalidEscapeCharacter");
|
|
475
|
+
break;
|
|
476
|
+
case "UnexpectedEndOfNumber":
|
|
477
|
+
handleError("InvalidNumberFormat");
|
|
478
|
+
break;
|
|
479
|
+
case "UnexpectedEndOfComment":
|
|
480
|
+
handleError("UnexpectedEndOfComment");
|
|
481
|
+
break;
|
|
482
|
+
case "UnexpectedEndOfString":
|
|
483
|
+
handleError("UnexpectedEndOfString");
|
|
484
|
+
break;
|
|
485
|
+
case "InvalidCharacter":
|
|
486
|
+
handleError("InvalidCharacter");
|
|
487
|
+
break;
|
|
475
488
|
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
offset: prevTokenEnd,
|
|
487
|
-
length: trailing.length,
|
|
488
|
-
content: opts.eol
|
|
489
|
-
});
|
|
490
|
-
}
|
|
491
|
-
return edits;
|
|
492
|
-
}
|
|
493
|
-
function modifyImpl(text, path, value, formattingOptions) {
|
|
494
|
-
const opts = {
|
|
495
|
-
tabSize: formattingOptions?.tabSize ?? 2,
|
|
496
|
-
insertSpaces: formattingOptions?.insertSpaces ?? true,
|
|
497
|
-
eol: formattingOptions?.eol ?? "\n"
|
|
498
|
-
};
|
|
499
|
-
const indentUnit = opts.insertSpaces ? " ".repeat(opts.tabSize) : "\t";
|
|
500
|
-
if (0 === path.length) {
|
|
501
|
-
const content = void 0 === value ? "" : JSON.stringify(value, null, opts.tabSize);
|
|
502
|
-
return [
|
|
503
|
-
{
|
|
504
|
-
offset: 0,
|
|
505
|
-
length: text.length,
|
|
506
|
-
content
|
|
489
|
+
switch(currentToken){
|
|
490
|
+
case "LineComment":
|
|
491
|
+
case "BlockComment":
|
|
492
|
+
if (disallowComments) handleError("InvalidCommentToken");
|
|
493
|
+
break;
|
|
494
|
+
case "Trivia":
|
|
495
|
+
case "LineBreak":
|
|
496
|
+
break;
|
|
497
|
+
default:
|
|
498
|
+
return currentToken;
|
|
507
499
|
}
|
|
508
|
-
|
|
500
|
+
}
|
|
509
501
|
}
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
skipValue();
|
|
525
|
-
}
|
|
526
|
-
} else currentToken = scanner.scan();
|
|
527
|
-
first = false;
|
|
528
|
-
}
|
|
529
|
-
if ("CloseBrace" === currentToken) currentToken = scanner.scan();
|
|
502
|
+
function handleError(code, skipUntilAfter = [], skipUntil = []) {
|
|
503
|
+
errors.push(new JsoncParseErrorDetail({
|
|
504
|
+
code,
|
|
505
|
+
message: formatError(code, scanner.getTokenOffset()),
|
|
506
|
+
offset: scanner.getTokenOffset(),
|
|
507
|
+
length: scanner.getTokenLength(),
|
|
508
|
+
startLine: scanner.getTokenStartLine(),
|
|
509
|
+
startCharacter: scanner.getTokenStartCharacter()
|
|
510
|
+
}));
|
|
511
|
+
if (skipUntilAfter.length > 0 || skipUntil.length > 0) {
|
|
512
|
+
let t = token();
|
|
513
|
+
while("EOF" !== t){
|
|
514
|
+
if (skipUntilAfter.includes(t)) {
|
|
515
|
+
scanNext();
|
|
530
516
|
break;
|
|
531
517
|
}
|
|
518
|
+
if (skipUntil.includes(t)) break;
|
|
519
|
+
t = scanNext();
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
function parseValue() {
|
|
524
|
+
switch(token()){
|
|
532
525
|
case "OpenBracket":
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
526
|
+
return parseArray();
|
|
527
|
+
case "OpenBrace":
|
|
528
|
+
return parseObject();
|
|
529
|
+
case "String":
|
|
530
|
+
return parseString();
|
|
531
|
+
case "Number":
|
|
532
|
+
return parseNumber();
|
|
533
|
+
case "True":
|
|
534
|
+
scanNext();
|
|
535
|
+
return true;
|
|
536
|
+
case "False":
|
|
537
|
+
scanNext();
|
|
538
|
+
return false;
|
|
539
|
+
case "Null":
|
|
540
|
+
scanNext();
|
|
541
|
+
return null;
|
|
544
542
|
default:
|
|
545
|
-
|
|
546
|
-
break;
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
let depth = 0;
|
|
550
|
-
for (const segment of path){
|
|
551
|
-
depth++;
|
|
552
|
-
if ("string" == typeof segment) {
|
|
553
|
-
if ("OpenBrace" !== currentToken) throw new Error(`Expected object at depth ${depth}`);
|
|
554
|
-
currentToken = scanner.scan();
|
|
555
|
-
let found = false;
|
|
556
|
-
let lastValueEnd = scanner.getTokenOffset();
|
|
557
|
-
let isFirst = true;
|
|
558
|
-
while("CloseBrace" !== currentToken && "EOF" !== currentToken){
|
|
559
|
-
if (!isFirst && "Comma" === currentToken) currentToken = scanner.scan();
|
|
560
|
-
if ("String" === currentToken) {
|
|
561
|
-
const key = scanner.getTokenValue();
|
|
562
|
-
currentToken = scanner.scan();
|
|
563
|
-
if ("Colon" === currentToken) currentToken = scanner.scan();
|
|
564
|
-
if (key === segment) {
|
|
565
|
-
found = true;
|
|
566
|
-
if (depth === path.length) {
|
|
567
|
-
const valueStart = scanner.getTokenOffset();
|
|
568
|
-
const prevEnd = valueStart;
|
|
569
|
-
skipValue();
|
|
570
|
-
const valueEnd = scanner.getTokenOffset();
|
|
571
|
-
if (void 0 === value) {
|
|
572
|
-
let removeStart = valueStart;
|
|
573
|
-
let removeEnd = valueEnd;
|
|
574
|
-
const keySearchArea = text.substring(0, valueStart);
|
|
575
|
-
const keyStart = keySearchArea.lastIndexOf(`"${segment}"`);
|
|
576
|
-
if (keyStart >= 0) removeStart = keyStart;
|
|
577
|
-
const beforeKey = text.substring(0, removeStart).trimEnd();
|
|
578
|
-
const commaPosBefore = beforeKey.lastIndexOf(",");
|
|
579
|
-
if (commaPosBefore >= 0) removeStart = commaPosBefore;
|
|
580
|
-
else {
|
|
581
|
-
const afterValue = text.substring(removeEnd);
|
|
582
|
-
const trimmedAfter = afterValue.match(/^(\s*,)/);
|
|
583
|
-
if (trimmedAfter) removeEnd += trimmedAfter[1].length;
|
|
584
|
-
}
|
|
585
|
-
return [
|
|
586
|
-
{
|
|
587
|
-
offset: removeStart,
|
|
588
|
-
length: removeEnd - removeStart,
|
|
589
|
-
content: ""
|
|
590
|
-
}
|
|
591
|
-
];
|
|
592
|
-
}
|
|
593
|
-
const serialized = JSON.stringify(value, null, opts.tabSize);
|
|
594
|
-
return [
|
|
595
|
-
{
|
|
596
|
-
offset: prevEnd,
|
|
597
|
-
length: valueEnd - prevEnd,
|
|
598
|
-
content: serialized
|
|
599
|
-
}
|
|
600
|
-
];
|
|
601
|
-
}
|
|
602
|
-
break;
|
|
603
|
-
}
|
|
604
|
-
skipValue();
|
|
605
|
-
} else currentToken = scanner.scan();
|
|
606
|
-
lastValueEnd = scanner.getTokenOffset();
|
|
607
|
-
isFirst = false;
|
|
608
|
-
}
|
|
609
|
-
if (!found && depth === path.length && void 0 !== value) {
|
|
610
|
-
const indent = indentUnit.repeat(depth);
|
|
611
|
-
const serialized = JSON.stringify(value, null, opts.tabSize);
|
|
612
|
-
const insertText = isFirst ? `${opts.eol}${indent}"${segment}": ${serialized}${opts.eol}${indentUnit.repeat(depth - 1)}` : `,${opts.eol}${indent}"${segment}": ${serialized}`;
|
|
613
|
-
return [
|
|
614
|
-
{
|
|
615
|
-
offset: lastValueEnd,
|
|
616
|
-
length: 0,
|
|
617
|
-
content: insertText
|
|
618
|
-
}
|
|
619
|
-
];
|
|
620
|
-
}
|
|
621
|
-
} else {
|
|
622
|
-
if ("OpenBracket" !== currentToken) throw new Error(`Expected array at depth ${depth}`);
|
|
623
|
-
currentToken = scanner.scan();
|
|
624
|
-
let idx = 0;
|
|
625
|
-
let lastEnd = scanner.getTokenOffset();
|
|
626
|
-
while("CloseBracket" !== currentToken && "EOF" !== currentToken){
|
|
627
|
-
if (idx > 0 && "Comma" === currentToken) currentToken = scanner.scan();
|
|
628
|
-
if (idx === segment) {
|
|
629
|
-
if (depth === path.length) {
|
|
630
|
-
const valueStart = scanner.getTokenOffset();
|
|
631
|
-
skipValue();
|
|
632
|
-
const valueEnd = scanner.getTokenOffset();
|
|
633
|
-
if (void 0 === value) {
|
|
634
|
-
let removeEnd = valueEnd;
|
|
635
|
-
const after = text.substring(removeEnd).trimStart();
|
|
636
|
-
if (after.startsWith(",")) removeEnd = text.indexOf(",", removeEnd) + 1;
|
|
637
|
-
return [
|
|
638
|
-
{
|
|
639
|
-
offset: valueStart,
|
|
640
|
-
length: removeEnd - valueStart,
|
|
641
|
-
content: ""
|
|
642
|
-
}
|
|
643
|
-
];
|
|
644
|
-
}
|
|
645
|
-
const serialized = JSON.stringify(value, null, opts.tabSize);
|
|
646
|
-
return [
|
|
647
|
-
{
|
|
648
|
-
offset: valueStart,
|
|
649
|
-
length: valueEnd - valueStart,
|
|
650
|
-
content: serialized
|
|
651
|
-
}
|
|
652
|
-
];
|
|
653
|
-
}
|
|
654
|
-
break;
|
|
655
|
-
}
|
|
656
|
-
skipValue();
|
|
657
|
-
lastEnd = scanner.getTokenOffset();
|
|
658
|
-
idx++;
|
|
659
|
-
}
|
|
660
|
-
if (idx <= segment && depth === path.length && void 0 !== value) {
|
|
661
|
-
const indent = indentUnit.repeat(depth);
|
|
662
|
-
const serialized = JSON.stringify(value, null, opts.tabSize);
|
|
663
|
-
const insertText = 0 === idx ? `${opts.eol}${indent}${serialized}${opts.eol}${indentUnit.repeat(depth - 1)}` : `,${opts.eol}${indent}${serialized}`;
|
|
664
|
-
return [
|
|
665
|
-
{
|
|
666
|
-
offset: lastEnd,
|
|
667
|
-
length: 0,
|
|
668
|
-
content: insertText
|
|
669
|
-
}
|
|
670
|
-
];
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
return [];
|
|
675
|
-
}
|
|
676
|
-
const parse = (text, options)=>Effect.sync(()=>parseInternal(text, options ?? {}, false)).pipe(Effect.flatMap(({ value, errors })=>{
|
|
677
|
-
if (errors.length > 0) return Effect.fail(new JsoncParseError({
|
|
678
|
-
errors,
|
|
679
|
-
text,
|
|
680
|
-
...void 0 !== options ? {
|
|
681
|
-
options
|
|
682
|
-
} : {}
|
|
683
|
-
}));
|
|
684
|
-
return Effect.succeed(value);
|
|
685
|
-
}));
|
|
686
|
-
const parseTree = (text, options)=>Effect.sync(()=>parseInternal(text, options ?? {}, true)).pipe(Effect.flatMap(({ root, errors })=>{
|
|
687
|
-
if (errors.length > 0) return Effect.fail(new JsoncParseError({
|
|
688
|
-
errors,
|
|
689
|
-
text,
|
|
690
|
-
...void 0 !== options ? {
|
|
691
|
-
options
|
|
692
|
-
} : {}
|
|
693
|
-
}));
|
|
694
|
-
return Effect.succeed(root ? Option.some(root) : Option.none());
|
|
695
|
-
}));
|
|
696
|
-
const stripComments = (text, replaceCh)=>Effect.sync(()=>{
|
|
697
|
-
const scanner = createScanner(text);
|
|
698
|
-
const parts = [];
|
|
699
|
-
let lastOffset = 0;
|
|
700
|
-
let kind;
|
|
701
|
-
do {
|
|
702
|
-
kind = scanner.scan();
|
|
703
|
-
const offset = scanner.getTokenOffset();
|
|
704
|
-
const length = scanner.getTokenLength();
|
|
705
|
-
if ("LineComment" === kind || "BlockComment" === kind) {
|
|
706
|
-
if (lastOffset < offset) parts.push(text.substring(lastOffset, offset));
|
|
707
|
-
if (void 0 !== replaceCh) for(let i = 0; i < length; i++){
|
|
708
|
-
const ch = text.charCodeAt(offset + i);
|
|
709
|
-
parts.push(0x0a === ch || 0x0d === ch ? text[offset + i] : replaceCh);
|
|
710
|
-
}
|
|
711
|
-
lastOffset = offset + length;
|
|
712
|
-
}
|
|
713
|
-
}while ("EOF" !== kind);
|
|
714
|
-
if (lastOffset < text.length) parts.push(text.substring(lastOffset));
|
|
715
|
-
return parts.join("");
|
|
716
|
-
});
|
|
717
|
-
function parseInternal(text, options, buildTree) {
|
|
718
|
-
const scanner = createScanner(text, false);
|
|
719
|
-
const errors = [];
|
|
720
|
-
const disallowComments = options.disallowComments ?? false;
|
|
721
|
-
const allowTrailingComma = options.allowTrailingComma ?? true;
|
|
722
|
-
const allowEmptyContent = options.allowEmptyContent ?? false;
|
|
723
|
-
let currentToken = "Unknown";
|
|
724
|
-
function token() {
|
|
725
|
-
return currentToken;
|
|
726
|
-
}
|
|
727
|
-
function scanNext() {
|
|
728
|
-
for(;;){
|
|
729
|
-
currentToken = scanner.scan();
|
|
730
|
-
switch(scanner.getTokenError()){
|
|
731
|
-
case "InvalidUnicode":
|
|
732
|
-
handleError("InvalidUnicode");
|
|
733
|
-
break;
|
|
734
|
-
case "InvalidEscapeCharacter":
|
|
735
|
-
handleError("InvalidEscapeCharacter");
|
|
736
|
-
break;
|
|
737
|
-
case "UnexpectedEndOfNumber":
|
|
738
|
-
handleError("InvalidNumberFormat");
|
|
739
|
-
break;
|
|
740
|
-
case "UnexpectedEndOfComment":
|
|
741
|
-
handleError("UnexpectedEndOfComment");
|
|
742
|
-
break;
|
|
743
|
-
case "UnexpectedEndOfString":
|
|
744
|
-
handleError("UnexpectedEndOfString");
|
|
745
|
-
break;
|
|
746
|
-
case "InvalidCharacter":
|
|
747
|
-
handleError("InvalidCharacter");
|
|
748
|
-
break;
|
|
749
|
-
}
|
|
750
|
-
switch(currentToken){
|
|
751
|
-
case "LineComment":
|
|
752
|
-
case "BlockComment":
|
|
753
|
-
if (disallowComments) handleError("InvalidCommentToken");
|
|
754
|
-
break;
|
|
755
|
-
case "Trivia":
|
|
756
|
-
case "LineBreak":
|
|
757
|
-
break;
|
|
758
|
-
default:
|
|
759
|
-
return currentToken;
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
function handleError(code, skipUntilAfter = [], skipUntil = []) {
|
|
764
|
-
errors.push(new JsoncParseErrorDetail({
|
|
765
|
-
code,
|
|
766
|
-
message: formatError(code, scanner.getTokenOffset()),
|
|
767
|
-
offset: scanner.getTokenOffset(),
|
|
768
|
-
length: scanner.getTokenLength(),
|
|
769
|
-
startLine: scanner.getTokenStartLine(),
|
|
770
|
-
startCharacter: scanner.getTokenStartCharacter()
|
|
771
|
-
}));
|
|
772
|
-
if (skipUntilAfter.length > 0 || skipUntil.length > 0) {
|
|
773
|
-
let t = token();
|
|
774
|
-
while("EOF" !== t){
|
|
775
|
-
if (skipUntilAfter.includes(t)) {
|
|
776
|
-
scanNext();
|
|
777
|
-
break;
|
|
778
|
-
}
|
|
779
|
-
if (skipUntil.includes(t)) break;
|
|
780
|
-
t = scanNext();
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
function parseValue() {
|
|
785
|
-
switch(token()){
|
|
786
|
-
case "OpenBracket":
|
|
787
|
-
return parseArray();
|
|
788
|
-
case "OpenBrace":
|
|
789
|
-
return parseObject();
|
|
790
|
-
case "String":
|
|
791
|
-
return parseString();
|
|
792
|
-
case "Number":
|
|
793
|
-
return parseNumber();
|
|
794
|
-
case "True":
|
|
795
|
-
scanNext();
|
|
796
|
-
return true;
|
|
797
|
-
case "False":
|
|
798
|
-
scanNext();
|
|
799
|
-
return false;
|
|
800
|
-
case "Null":
|
|
801
|
-
scanNext();
|
|
802
|
-
return null;
|
|
803
|
-
default:
|
|
804
|
-
return;
|
|
543
|
+
return;
|
|
805
544
|
}
|
|
806
545
|
}
|
|
807
546
|
function parseString() {
|
|
@@ -1094,6 +833,297 @@ function formatError(code, offset) {
|
|
|
1094
833
|
return `Parse error at offset ${offset}`;
|
|
1095
834
|
}
|
|
1096
835
|
}
|
|
836
|
+
function deepEqual(a, b) {
|
|
837
|
+
if (a === b) return true;
|
|
838
|
+
if (null === a || null === b) return false;
|
|
839
|
+
if (typeof a !== typeof b) return false;
|
|
840
|
+
if (Array.isArray(a)) {
|
|
841
|
+
if (!Array.isArray(b)) return false;
|
|
842
|
+
if (a.length !== b.length) return false;
|
|
843
|
+
for(let i = 0; i < a.length; i++)if (!deepEqual(a[i], b[i])) return false;
|
|
844
|
+
return true;
|
|
845
|
+
}
|
|
846
|
+
if (Array.isArray(b)) return false;
|
|
847
|
+
if ("object" == typeof a && "object" == typeof b) {
|
|
848
|
+
const aObj = a;
|
|
849
|
+
const bObj = b;
|
|
850
|
+
const aKeys = Object.keys(aObj);
|
|
851
|
+
const bKeys = Object.keys(bObj);
|
|
852
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
853
|
+
for (const key of aKeys){
|
|
854
|
+
if (!Object.hasOwn(bObj, key)) return false;
|
|
855
|
+
if (!deepEqual(aObj[key], bObj[key])) return false;
|
|
856
|
+
}
|
|
857
|
+
return true;
|
|
858
|
+
}
|
|
859
|
+
return false;
|
|
860
|
+
}
|
|
861
|
+
const equals = Function.dual(2, (self, that)=>Effect.map(Effect.all([
|
|
862
|
+
parse(self),
|
|
863
|
+
parse(that)
|
|
864
|
+
]), ([a, b])=>deepEqual(a, b)));
|
|
865
|
+
const equalsValue = Function.dual(2, (self, value)=>Effect.map(parse(self), (parsed)=>deepEqual(parsed, value)));
|
|
866
|
+
const format = (text, range, options)=>Effect.sync(()=>formatImpl(text, range, options));
|
|
867
|
+
const applyEdits = Function.dual(2, (text, edits)=>Effect.sync(()=>{
|
|
868
|
+
const sorted = [
|
|
869
|
+
...edits
|
|
870
|
+
].sort((a, b)=>b.offset - a.offset);
|
|
871
|
+
let result = text;
|
|
872
|
+
for (const edit of sorted)result = result.substring(0, edit.offset) + edit.content + result.substring(edit.offset + edit.length);
|
|
873
|
+
return result;
|
|
874
|
+
}));
|
|
875
|
+
const formatAndApply = (text, range, options)=>format(text, range, options).pipe(Effect.flatMap((edits)=>applyEdits(text, edits)));
|
|
876
|
+
const modify = Function.dual((args)=>"string" == typeof args[0] && Array.isArray(args[1]), (text, path, value, options)=>Effect["try"]({
|
|
877
|
+
try: ()=>modifyImpl(text, path, value, options?.formattingOptions),
|
|
878
|
+
catch: (e)=>new JsoncModificationError({
|
|
879
|
+
path,
|
|
880
|
+
reason: String(e)
|
|
881
|
+
})
|
|
882
|
+
}));
|
|
883
|
+
function formatImpl(text, range, options) {
|
|
884
|
+
const opts = {
|
|
885
|
+
tabSize: options?.tabSize ?? 2,
|
|
886
|
+
insertSpaces: options?.insertSpaces ?? true,
|
|
887
|
+
eol: options?.eol ?? "\n",
|
|
888
|
+
insertFinalNewline: options?.insertFinalNewline ?? false,
|
|
889
|
+
keepLines: options?.keepLines ?? false
|
|
890
|
+
};
|
|
891
|
+
const indentUnit = opts.insertSpaces ? " ".repeat(opts.tabSize) : "\t";
|
|
892
|
+
const edits = [];
|
|
893
|
+
const scanner = createScanner(text, false);
|
|
894
|
+
const rangeStart = range?.offset ?? 0;
|
|
895
|
+
const rangeEnd = range ? range.offset + range.length : text.length;
|
|
896
|
+
let depth = 0;
|
|
897
|
+
let prevTokenEnd = -1;
|
|
898
|
+
let prevToken = "Unknown";
|
|
899
|
+
let firstToken = true;
|
|
900
|
+
function makeIndent(d) {
|
|
901
|
+
return indentUnit.repeat(d);
|
|
902
|
+
}
|
|
903
|
+
function addEdit(offset, length, content) {
|
|
904
|
+
if (offset >= rangeStart && offset + length <= rangeEnd) {
|
|
905
|
+
if (text.substring(offset, offset + length) !== content) edits.push({
|
|
906
|
+
offset,
|
|
907
|
+
length,
|
|
908
|
+
content
|
|
909
|
+
});
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
let kind = scanner.scan();
|
|
913
|
+
while("EOF" !== kind){
|
|
914
|
+
const tokenOffset = scanner.getTokenOffset();
|
|
915
|
+
const tokenLength = scanner.getTokenLength();
|
|
916
|
+
if ("Trivia" !== kind && "LineBreak" !== kind) {
|
|
917
|
+
if (!firstToken && prevTokenEnd >= 0) {
|
|
918
|
+
const gap = text.substring(prevTokenEnd, tokenOffset);
|
|
919
|
+
let expectedGap;
|
|
920
|
+
if ("CloseBrace" === kind || "CloseBracket" === kind) {
|
|
921
|
+
depth--;
|
|
922
|
+
expectedGap = opts.eol + makeIndent(depth);
|
|
923
|
+
} else expectedGap = "OpenBrace" === prevToken || "OpenBracket" === prevToken ? opts.eol + makeIndent(depth) : "Comma" === prevToken ? opts.eol + makeIndent(depth) : "Colon" === prevToken ? " " : "LineComment" === kind || "BlockComment" === kind ? gap.includes("\n") ? opts.eol + makeIndent(depth) : " " : "LineComment" === prevToken ? opts.eol + makeIndent(depth) : "BlockComment" === prevToken ? gap.includes("\n") ? opts.eol + makeIndent(depth) : " " : gap;
|
|
924
|
+
if (opts.keepLines && gap.includes("\n")) expectedGap = gap;
|
|
925
|
+
addEdit(prevTokenEnd, tokenOffset - prevTokenEnd, expectedGap);
|
|
926
|
+
}
|
|
927
|
+
if ("OpenBrace" === kind || "OpenBracket" === kind) depth++;
|
|
928
|
+
prevToken = kind;
|
|
929
|
+
prevTokenEnd = tokenOffset + tokenLength;
|
|
930
|
+
firstToken = false;
|
|
931
|
+
}
|
|
932
|
+
kind = scanner.scan();
|
|
933
|
+
}
|
|
934
|
+
if (opts.insertFinalNewline && prevTokenEnd >= 0) {
|
|
935
|
+
const trailing = text.substring(prevTokenEnd);
|
|
936
|
+
if (!trailing.endsWith(opts.eol)) edits.push({
|
|
937
|
+
offset: prevTokenEnd,
|
|
938
|
+
length: trailing.length,
|
|
939
|
+
content: opts.eol
|
|
940
|
+
});
|
|
941
|
+
}
|
|
942
|
+
return edits;
|
|
943
|
+
}
|
|
944
|
+
function modifyImpl(text, path, value, formattingOptions) {
|
|
945
|
+
const opts = {
|
|
946
|
+
tabSize: formattingOptions?.tabSize ?? 2,
|
|
947
|
+
insertSpaces: formattingOptions?.insertSpaces ?? true,
|
|
948
|
+
eol: formattingOptions?.eol ?? "\n"
|
|
949
|
+
};
|
|
950
|
+
const indentUnit = opts.insertSpaces ? " ".repeat(opts.tabSize) : "\t";
|
|
951
|
+
if (0 === path.length) {
|
|
952
|
+
const content = void 0 === value ? "" : JSON.stringify(value, null, opts.tabSize);
|
|
953
|
+
return [
|
|
954
|
+
{
|
|
955
|
+
offset: 0,
|
|
956
|
+
length: text.length,
|
|
957
|
+
content
|
|
958
|
+
}
|
|
959
|
+
];
|
|
960
|
+
}
|
|
961
|
+
const scanner = createScanner(text, true);
|
|
962
|
+
let currentToken = scanner.scan();
|
|
963
|
+
function skipValue() {
|
|
964
|
+
switch(currentToken){
|
|
965
|
+
case "OpenBrace":
|
|
966
|
+
{
|
|
967
|
+
currentToken = scanner.scan();
|
|
968
|
+
let first = true;
|
|
969
|
+
while("CloseBrace" !== currentToken && "EOF" !== currentToken){
|
|
970
|
+
if (!first && "Comma" === currentToken) currentToken = scanner.scan();
|
|
971
|
+
if ("String" === currentToken) {
|
|
972
|
+
currentToken = scanner.scan();
|
|
973
|
+
if ("Colon" === currentToken) {
|
|
974
|
+
currentToken = scanner.scan();
|
|
975
|
+
skipValue();
|
|
976
|
+
}
|
|
977
|
+
} else currentToken = scanner.scan();
|
|
978
|
+
first = false;
|
|
979
|
+
}
|
|
980
|
+
if ("CloseBrace" === currentToken) currentToken = scanner.scan();
|
|
981
|
+
break;
|
|
982
|
+
}
|
|
983
|
+
case "OpenBracket":
|
|
984
|
+
{
|
|
985
|
+
currentToken = scanner.scan();
|
|
986
|
+
let first = true;
|
|
987
|
+
while("CloseBracket" !== currentToken && "EOF" !== currentToken){
|
|
988
|
+
if (!first && "Comma" === currentToken) currentToken = scanner.scan();
|
|
989
|
+
skipValue();
|
|
990
|
+
first = false;
|
|
991
|
+
}
|
|
992
|
+
if ("CloseBracket" === currentToken) currentToken = scanner.scan();
|
|
993
|
+
break;
|
|
994
|
+
}
|
|
995
|
+
default:
|
|
996
|
+
currentToken = scanner.scan();
|
|
997
|
+
break;
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
let depth = 0;
|
|
1001
|
+
for (const segment of path){
|
|
1002
|
+
depth++;
|
|
1003
|
+
if ("string" == typeof segment) {
|
|
1004
|
+
if ("OpenBrace" !== currentToken) throw new Error(`Expected object at depth ${depth}`);
|
|
1005
|
+
currentToken = scanner.scan();
|
|
1006
|
+
let found = false;
|
|
1007
|
+
let lastValueEnd = scanner.getTokenOffset();
|
|
1008
|
+
let isFirst = true;
|
|
1009
|
+
while("CloseBrace" !== currentToken && "EOF" !== currentToken){
|
|
1010
|
+
if (!isFirst && "Comma" === currentToken) currentToken = scanner.scan();
|
|
1011
|
+
if ("String" === currentToken) {
|
|
1012
|
+
const key = scanner.getTokenValue();
|
|
1013
|
+
currentToken = scanner.scan();
|
|
1014
|
+
if ("Colon" === currentToken) currentToken = scanner.scan();
|
|
1015
|
+
if (key === segment) {
|
|
1016
|
+
found = true;
|
|
1017
|
+
if (depth === path.length) {
|
|
1018
|
+
const valueStart = scanner.getTokenOffset();
|
|
1019
|
+
const prevEnd = valueStart;
|
|
1020
|
+
skipValue();
|
|
1021
|
+
const valueEnd = scanner.getTokenOffset();
|
|
1022
|
+
if (void 0 === value) {
|
|
1023
|
+
let removeStart = valueStart;
|
|
1024
|
+
let removeEnd = valueEnd;
|
|
1025
|
+
const keySearchArea = text.substring(0, valueStart);
|
|
1026
|
+
const keyStart = keySearchArea.lastIndexOf(`"${segment}"`);
|
|
1027
|
+
if (keyStart >= 0) removeStart = keyStart;
|
|
1028
|
+
const beforeKey = text.substring(0, removeStart).trimEnd();
|
|
1029
|
+
const commaPosBefore = beforeKey.lastIndexOf(",");
|
|
1030
|
+
if (commaPosBefore >= 0) removeStart = commaPosBefore;
|
|
1031
|
+
else {
|
|
1032
|
+
const afterValue = text.substring(removeEnd);
|
|
1033
|
+
const trimmedAfter = afterValue.match(/^(\s*,)/);
|
|
1034
|
+
if (trimmedAfter) removeEnd += trimmedAfter[1].length;
|
|
1035
|
+
}
|
|
1036
|
+
return [
|
|
1037
|
+
{
|
|
1038
|
+
offset: removeStart,
|
|
1039
|
+
length: removeEnd - removeStart,
|
|
1040
|
+
content: ""
|
|
1041
|
+
}
|
|
1042
|
+
];
|
|
1043
|
+
}
|
|
1044
|
+
const serialized = JSON.stringify(value, null, opts.tabSize);
|
|
1045
|
+
return [
|
|
1046
|
+
{
|
|
1047
|
+
offset: prevEnd,
|
|
1048
|
+
length: valueEnd - prevEnd,
|
|
1049
|
+
content: serialized
|
|
1050
|
+
}
|
|
1051
|
+
];
|
|
1052
|
+
}
|
|
1053
|
+
break;
|
|
1054
|
+
}
|
|
1055
|
+
skipValue();
|
|
1056
|
+
} else currentToken = scanner.scan();
|
|
1057
|
+
lastValueEnd = scanner.getTokenOffset();
|
|
1058
|
+
isFirst = false;
|
|
1059
|
+
}
|
|
1060
|
+
if (!found && depth === path.length && void 0 !== value) {
|
|
1061
|
+
const indent = indentUnit.repeat(depth);
|
|
1062
|
+
const serialized = JSON.stringify(value, null, opts.tabSize);
|
|
1063
|
+
const insertText = isFirst ? `${opts.eol}${indent}"${segment}": ${serialized}${opts.eol}${indentUnit.repeat(depth - 1)}` : `,${opts.eol}${indent}"${segment}": ${serialized}`;
|
|
1064
|
+
return [
|
|
1065
|
+
{
|
|
1066
|
+
offset: lastValueEnd,
|
|
1067
|
+
length: 0,
|
|
1068
|
+
content: insertText
|
|
1069
|
+
}
|
|
1070
|
+
];
|
|
1071
|
+
}
|
|
1072
|
+
} else {
|
|
1073
|
+
if ("OpenBracket" !== currentToken) throw new Error(`Expected array at depth ${depth}`);
|
|
1074
|
+
currentToken = scanner.scan();
|
|
1075
|
+
let idx = 0;
|
|
1076
|
+
let lastEnd = scanner.getTokenOffset();
|
|
1077
|
+
while("CloseBracket" !== currentToken && "EOF" !== currentToken){
|
|
1078
|
+
if (idx > 0 && "Comma" === currentToken) currentToken = scanner.scan();
|
|
1079
|
+
if (idx === segment) {
|
|
1080
|
+
if (depth === path.length) {
|
|
1081
|
+
const valueStart = scanner.getTokenOffset();
|
|
1082
|
+
skipValue();
|
|
1083
|
+
const valueEnd = scanner.getTokenOffset();
|
|
1084
|
+
if (void 0 === value) {
|
|
1085
|
+
let removeEnd = valueEnd;
|
|
1086
|
+
const after = text.substring(removeEnd).trimStart();
|
|
1087
|
+
if (after.startsWith(",")) removeEnd = text.indexOf(",", removeEnd) + 1;
|
|
1088
|
+
return [
|
|
1089
|
+
{
|
|
1090
|
+
offset: valueStart,
|
|
1091
|
+
length: removeEnd - valueStart,
|
|
1092
|
+
content: ""
|
|
1093
|
+
}
|
|
1094
|
+
];
|
|
1095
|
+
}
|
|
1096
|
+
const serialized = JSON.stringify(value, null, opts.tabSize);
|
|
1097
|
+
return [
|
|
1098
|
+
{
|
|
1099
|
+
offset: valueStart,
|
|
1100
|
+
length: valueEnd - valueStart,
|
|
1101
|
+
content: serialized
|
|
1102
|
+
}
|
|
1103
|
+
];
|
|
1104
|
+
}
|
|
1105
|
+
break;
|
|
1106
|
+
}
|
|
1107
|
+
skipValue();
|
|
1108
|
+
lastEnd = scanner.getTokenOffset();
|
|
1109
|
+
idx++;
|
|
1110
|
+
}
|
|
1111
|
+
if (idx <= segment && depth === path.length && void 0 !== value) {
|
|
1112
|
+
const indent = indentUnit.repeat(depth);
|
|
1113
|
+
const serialized = JSON.stringify(value, null, opts.tabSize);
|
|
1114
|
+
const insertText = 0 === idx ? `${opts.eol}${indent}${serialized}${opts.eol}${indentUnit.repeat(depth - 1)}` : `,${opts.eol}${indent}${serialized}`;
|
|
1115
|
+
return [
|
|
1116
|
+
{
|
|
1117
|
+
offset: lastEnd,
|
|
1118
|
+
length: 0,
|
|
1119
|
+
content: insertText
|
|
1120
|
+
}
|
|
1121
|
+
];
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
return [];
|
|
1126
|
+
}
|
|
1097
1127
|
const JsoncFromString = makeJsoncFromString();
|
|
1098
1128
|
function makeJsoncFromString(options) {
|
|
1099
1129
|
return Schema.transformOrFail(Schema.String, Schema.Unknown, {
|
|
@@ -1421,4 +1451,4 @@ function* visitGen(text, options) {
|
|
|
1421
1451
|
yield* scanNext();
|
|
1422
1452
|
if ("EOF" !== scanner.getToken()) yield* visitValue();
|
|
1423
1453
|
}
|
|
1424
|
-
export { JsoncEdit, JsoncFormattingOptions, JsoncFromString, JsoncModificationError, JsoncModificationErrorBase, JsoncNode, JsoncNodeNotFoundError, JsoncNodeNotFoundErrorBase, JsoncParseError, JsoncParseErrorBase, JsoncParseErrorDetail, JsoncParseOptions, JsoncRange, JsoncToken, applyEdits, createScanner, findNode, findNodeAtOffset, format, formatAndApply, getNodePath, getNodeValue, makeJsoncFromString, makeJsoncSchema, modify, parse, parseTree, stripComments, visit, visitCollect };
|
|
1454
|
+
export { JsoncEdit, JsoncFormattingOptions, JsoncFromString, JsoncModificationError, JsoncModificationErrorBase, JsoncNode, JsoncNodeNotFoundError, JsoncNodeNotFoundErrorBase, JsoncParseError, JsoncParseErrorBase, JsoncParseErrorDetail, JsoncParseOptions, JsoncRange, JsoncToken, applyEdits, createScanner, equals, equalsValue, findNode, findNodeAtOffset, format, formatAndApply, getNodePath, getNodeValue, makeJsoncFromString, makeJsoncSchema, modify, parse, parseTree, stripComments, visit, visitCollect };
|