jsonc-effect 0.1.0 → 0.2.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.
Files changed (4) hide show
  1. package/README.md +17 -0
  2. package/index.d.ts +1385 -153
  3. package/index.js +410 -380
  4. package/package.json +3 -3
package/index.js CHANGED
@@ -412,396 +412,135 @@ const createScanner = (text, ignoreTrivia = false)=>{
412
412
  }
413
413
  };
414
414
  };
415
- const format = (text, range, options)=>Effect.sync(()=>formatImpl(text, range, options));
416
- const applyEdits = Function.dual(2, (text, edits)=>Effect.sync(()=>{
417
- const sorted = [
418
- ...edits
419
- ].sort((a, b)=>b.offset - a.offset);
420
- let result = text;
421
- for (const edit of sorted)result = result.substring(0, edit.offset) + edit.content + result.substring(edit.offset + edit.length);
422
- return result;
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 formatAndApply = (text, range, options)=>format(text, range, options).pipe(Effect.flatMap((edits)=>applyEdits(text, edits)));
425
- const modify = Function.dual((args)=>"string" == typeof args[0] && Array.isArray(args[1]), (text, path, value, options)=>Effect["try"]({
426
- try: ()=>modifyImpl(text, path, value, options?.formattingOptions),
427
- catch: (e)=>new JsoncModificationError({
428
- path,
429
- reason: String(e)
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
- function formatImpl(text, range, options) {
433
- const opts = {
434
- tabSize: options?.tabSize ?? 2,
435
- insertSpaces: options?.insertSpaces ?? true,
436
- eol: options?.eol ?? "\n",
437
- insertFinalNewline: options?.insertFinalNewline ?? false,
438
- keepLines: options?.keepLines ?? false
439
- };
440
- const indentUnit = opts.insertSpaces ? " ".repeat(opts.tabSize) : "\t";
441
- const edits = [];
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 rangeStart = range?.offset ?? 0;
444
- const rangeEnd = range ? range.offset + range.length : text.length;
445
- let depth = 0;
446
- let prevTokenEnd = -1;
447
- let prevToken = "Unknown";
448
- let firstToken = true;
449
- function makeIndent(d) {
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
- let kind = scanner.scan();
462
- while("EOF" !== kind){
463
- const tokenOffset = scanner.getTokenOffset();
464
- const tokenLength = scanner.getTokenLength();
465
- if ("Trivia" !== kind && "LineBreak" !== kind) {
466
- if (!firstToken && prevTokenEnd >= 0) {
467
- const gap = text.substring(prevTokenEnd, tokenOffset);
468
- let expectedGap;
469
- if ("CloseBrace" === kind || "CloseBracket" === kind) {
470
- depth--;
471
- expectedGap = opts.eol + makeIndent(depth);
472
- } 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;
473
- if (opts.keepLines && gap.includes("\n")) expectedGap = gap;
474
- addEdit(prevTokenEnd, tokenOffset - prevTokenEnd, expectedGap);
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
- if ("OpenBrace" === kind || "OpenBracket" === kind) depth++;
477
- prevToken = kind;
478
- prevTokenEnd = tokenOffset + tokenLength;
479
- firstToken = false;
480
- }
481
- kind = scanner.scan();
482
- }
483
- if (opts.insertFinalNewline && prevTokenEnd >= 0) {
484
- const trailing = text.substring(prevTokenEnd);
485
- if (!trailing.endsWith(opts.eol)) edits.push({
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
- const scanner = createScanner(text, true);
511
- let currentToken = scanner.scan();
512
- function skipValue() {
513
- switch(currentToken){
514
- case "OpenBrace":
515
- {
516
- currentToken = scanner.scan();
517
- let first = true;
518
- while("CloseBrace" !== currentToken && "EOF" !== currentToken){
519
- if (!first && "Comma" === currentToken) currentToken = scanner.scan();
520
- if ("String" === currentToken) {
521
- currentToken = scanner.scan();
522
- if ("Colon" === currentToken) {
523
- currentToken = scanner.scan();
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
- currentToken = scanner.scan();
535
- let first = true;
536
- while("CloseBracket" !== currentToken && "EOF" !== currentToken){
537
- if (!first && "Comma" === currentToken) currentToken = scanner.scan();
538
- skipValue();
539
- first = false;
540
- }
541
- if ("CloseBracket" === currentToken) currentToken = scanner.scan();
542
- break;
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
- currentToken = scanner.scan();
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 };