@sightmap/sightmap 0.2.1 → 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/dist/index.js CHANGED
@@ -74,6 +74,9 @@ var DUPLICATE_ROUTE = "duplicate-route";
74
74
  var ROUTE_SHADOWING = "route-shadowing";
75
75
  var UNKNOWN_SOURCE = "unknown-source";
76
76
  var SELECTOR_SYNTAX = "selector-syntax";
77
+ var FMT_NOT_CANONICAL = "fmt.not-canonical";
78
+ var FMT_PARSE_ERROR = "fmt.parse-error";
79
+ var FMT_SCHEMA_INVALID = "fmt.schema-invalid";
77
80
 
78
81
  // src/validate.ts
79
82
  import { existsSync, readFileSync } from "fs";
@@ -649,17 +652,58 @@ async function lint(sightmap, opts = {}) {
649
652
 
650
653
  // src/format/index.ts
651
654
  import { stringify } from "yaml";
652
- var CANONICAL_KEY_ORDER = [
655
+
656
+ // src/format/canonical-rules.ts
657
+ var TOP_LEVEL_KEY_ORDER = [
653
658
  "version",
654
659
  "memory",
655
660
  "views",
656
661
  "components",
657
662
  "requests"
658
663
  ];
664
+ var VIEW_KEY_ORDER = [
665
+ "name",
666
+ "route",
667
+ "description",
668
+ "components",
669
+ "memory",
670
+ "requests"
671
+ ];
672
+ var COMPONENT_KEY_ORDER = [
673
+ "name",
674
+ "selector",
675
+ "description",
676
+ "children",
677
+ "memory"
678
+ ];
679
+ var REQUEST_KEY_ORDER = [
680
+ "name",
681
+ "route",
682
+ "method",
683
+ "description",
684
+ "source",
685
+ "request",
686
+ "response",
687
+ "headers",
688
+ "memory"
689
+ ];
690
+ function compareByName(a, b) {
691
+ return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
692
+ }
693
+ function compareRequests(a, b) {
694
+ if (a.route !== b.route) return a.route < b.route ? -1 : 1;
695
+ const am = a.method ?? "";
696
+ const bm = b.method ?? "";
697
+ return am < bm ? -1 : am > bm ? 1 : 0;
698
+ }
699
+
700
+ // src/format/index.ts
659
701
  function format(input) {
660
702
  const ordered = {};
661
- for (const key of CANONICAL_KEY_ORDER) {
662
- if (input[key] !== void 0) ordered[key] = input[key];
703
+ for (const key of TOP_LEVEL_KEY_ORDER) {
704
+ if (input[key] !== void 0) {
705
+ ordered[key] = input[key];
706
+ }
663
707
  }
664
708
  for (const key of Object.keys(input)) {
665
709
  if (key.startsWith("__")) continue;
@@ -672,9 +716,264 @@ function format(input) {
672
716
  });
673
717
  return yaml3.endsWith("\n") ? yaml3 : yaml3 + "\n";
674
718
  }
719
+
720
+ // src/format/canonicalize.ts
721
+ import { parseDocument, isMap, isSeq, isScalar } from "yaml";
722
+ function canonicalize(input, opts) {
723
+ const doc = parseDocument(input, { keepSourceTokens: false });
724
+ const first = doc.errors[0];
725
+ if (first !== void 0) {
726
+ const loc = first.linePos?.[0] ? { line: first.linePos[0].line, column: first.linePos[0].col } : void 0;
727
+ return {
728
+ kind: "parse-error",
729
+ diagnostics: [
730
+ {
731
+ severity: "error",
732
+ code: FMT_PARSE_ERROR,
733
+ message: first.message,
734
+ file: opts.file,
735
+ ...loc !== void 0 ? { loc } : {}
736
+ }
737
+ ]
738
+ };
739
+ }
740
+ const v = validate(input, { sourceFile: opts.file });
741
+ if (!v.ok) {
742
+ const diagnostics = v.diagnostics.map((d) => ({
743
+ ...d,
744
+ severity: "error",
745
+ code: FMT_SCHEMA_INVALID,
746
+ file: opts.file
747
+ }));
748
+ return { kind: "schema-invalid", diagnostics };
749
+ }
750
+ if (isMap(doc.contents)) {
751
+ reorderTopLevelKeys(doc.contents);
752
+ reorderEntriesUnder(doc.contents, "views", VIEW_KEY_ORDER);
753
+ reorderEntriesUnder(doc.contents, "components", COMPONENT_KEY_ORDER);
754
+ reorderEntriesUnder(doc.contents, "requests", REQUEST_KEY_ORDER);
755
+ forEachViewEntry(doc.contents, (view) => {
756
+ reorderEntriesUnder(view, "components", COMPONENT_KEY_ORDER);
757
+ reorderEntriesUnder(view, "requests", REQUEST_KEY_ORDER);
758
+ forEachComponentEntry(view, "components", (cmp) => {
759
+ reorderChildrenRecursive(cmp);
760
+ });
761
+ });
762
+ forEachComponentEntry(doc.contents, "components", (cmp) => {
763
+ reorderChildrenRecursive(cmp);
764
+ });
765
+ sortTopLevelSeq(doc.contents, "views", (a, b) => {
766
+ const an = scalarPropFromMapItem(a, "name");
767
+ const bn = scalarPropFromMapItem(b, "name");
768
+ return compareByName({ name: an ?? "" }, { name: bn ?? "" });
769
+ });
770
+ sortTopLevelSeq(doc.contents, "components", (a, b) => {
771
+ const an = scalarPropFromMapItem(a, "name");
772
+ const bn = scalarPropFromMapItem(b, "name");
773
+ return compareByName({ name: an ?? "" }, { name: bn ?? "" });
774
+ });
775
+ sortTopLevelSeq(doc.contents, "requests", (a, b) => {
776
+ return compareRequests(
777
+ { route: scalarPropFromMapItem(a, "route") ?? "", method: scalarPropFromMapItem(a, "method") },
778
+ { route: scalarPropFromMapItem(b, "route") ?? "", method: scalarPropFromMapItem(b, "method") }
779
+ );
780
+ });
781
+ visitAllScalars(doc, (scalar) => {
782
+ if (typeof scalar.value !== "string") return;
783
+ scalar.type = chooseQuoteType(scalar.value);
784
+ });
785
+ normalizeBlankLines(doc);
786
+ }
787
+ const output = serialize(doc);
788
+ return { kind: "canonical", text: output, changed: output !== input };
789
+ }
790
+ function reorderTopLevelKeys(map) {
791
+ reorderMapKeys(map, TOP_LEVEL_KEY_ORDER);
792
+ }
793
+ function reorderMapKeys(map, keyOrder) {
794
+ const items = map.items;
795
+ const byKey = /* @__PURE__ */ new Map();
796
+ const unknownInOrder = [];
797
+ for (const item of items) {
798
+ const key = scalarKey(item);
799
+ if (key === null) {
800
+ unknownInOrder.push(item);
801
+ continue;
802
+ }
803
+ if (keyOrder.includes(key)) {
804
+ byKey.set(key, item);
805
+ } else {
806
+ unknownInOrder.push(item);
807
+ }
808
+ }
809
+ const reordered = [];
810
+ for (const k of keyOrder) {
811
+ const p = byKey.get(k);
812
+ if (p !== void 0) reordered.push(p);
813
+ }
814
+ reordered.push(...unknownInOrder);
815
+ map.items = reordered;
816
+ }
817
+ function reorderEntriesUnder(map, key, keyOrder) {
818
+ const seq = lookupSeq(map, key);
819
+ if (seq === null) return;
820
+ for (const item of seq.items) {
821
+ if (isMap(item)) reorderMapKeys(item, keyOrder);
822
+ }
823
+ }
824
+ function forEachViewEntry(root, fn) {
825
+ const seq = lookupSeq(root, "views");
826
+ if (seq === null) return;
827
+ for (const item of seq.items) {
828
+ if (isMap(item)) fn(item);
829
+ }
830
+ }
831
+ function forEachComponentEntry(root, key, fn) {
832
+ const seq = lookupSeq(root, key);
833
+ if (seq === null) return;
834
+ for (const item of seq.items) {
835
+ if (isMap(item)) fn(item);
836
+ }
837
+ }
838
+ function reorderChildrenRecursive(cmp) {
839
+ const children = lookupSeq(cmp, "children");
840
+ if (children === null) return;
841
+ for (const child of children.items) {
842
+ if (!isMap(child)) continue;
843
+ reorderMapKeys(child, COMPONENT_KEY_ORDER);
844
+ reorderChildrenRecursive(child);
845
+ }
846
+ }
847
+ function lookupSeq(map, key) {
848
+ for (const item of map.items) {
849
+ if (scalarKey(item) === key && isSeq(item.value)) return item.value;
850
+ }
851
+ return null;
852
+ }
853
+ function scalarKey(pair) {
854
+ const k = pair.key ?? null;
855
+ if (k === null) return null;
856
+ const v = typeof k === "object" && k !== null && "value" in k ? k.value : k;
857
+ return typeof v === "string" ? v : null;
858
+ }
859
+ function sortTopLevelSeq(root, key, cmp) {
860
+ const seq = lookupSeq(root, key);
861
+ if (seq === null) return;
862
+ seq.items = seq.items.slice().sort(cmp);
863
+ }
864
+ function scalarPropFromMapItem(item, key) {
865
+ if (!isMap(item)) return void 0;
866
+ for (const pair of item.items) {
867
+ if (scalarKey(pair) !== key) continue;
868
+ const v = pair.value;
869
+ if (v !== null && typeof v === "object" && "value" in v) {
870
+ const inner = v.value;
871
+ return typeof inner === "string" ? inner : void 0;
872
+ }
873
+ return typeof v === "string" ? v : void 0;
874
+ }
875
+ return void 0;
876
+ }
877
+ function visitAllScalars(doc, fn) {
878
+ walk(doc.contents, fn);
879
+ }
880
+ function walk(node, fn) {
881
+ if (node === null || node === void 0) return;
882
+ if (isScalar(node)) {
883
+ fn(node);
884
+ return;
885
+ }
886
+ if (isMap(node)) {
887
+ for (const pair of node.items) {
888
+ walk(pair.key, fn);
889
+ walk(pair.value, fn);
890
+ }
891
+ return;
892
+ }
893
+ if (isSeq(node)) {
894
+ for (const item of node.items) walk(item, fn);
895
+ }
896
+ }
897
+ function normalizeBlankLines(doc) {
898
+ const root = doc.contents;
899
+ if (!isMap(root)) return;
900
+ const firstPair = root.items[0];
901
+ if (firstPair !== void 0) {
902
+ const keyNode = firstPair.key;
903
+ const docMutable = doc;
904
+ const keyComment = keyNode?.commentBefore;
905
+ const docComment = docMutable.commentBefore;
906
+ if (typeof keyComment === "string" && keyComment.length > 0) {
907
+ docMutable.commentBefore = keyComment;
908
+ keyNode.commentBefore = null;
909
+ }
910
+ const hasHeader = typeof docMutable.commentBefore === "string" && docMutable.commentBefore.length > 0 || typeof docComment === "string" && docComment.length > 0;
911
+ firstPair.spaceBefore = hasHeader;
912
+ }
913
+ for (const key of ["views", "components", "requests"]) {
914
+ const seq = lookupSeq(root, key);
915
+ if (seq === null) continue;
916
+ seq.items.forEach((item, idx) => {
917
+ if (typeof item === "object" && item !== null) {
918
+ item.spaceBefore = idx > 0;
919
+ }
920
+ });
921
+ for (const item of seq.items) {
922
+ if (isMap(item)) clearInnerBlankLines(item);
923
+ }
924
+ }
925
+ }
926
+ function clearInnerBlankLines(map) {
927
+ for (const pair of map.items) {
928
+ pair.spaceBefore = false;
929
+ if (isSeq(pair.value)) {
930
+ for (const item of pair.value.items) {
931
+ if (typeof item === "object" && item !== null) {
932
+ item.spaceBefore = false;
933
+ }
934
+ if (isMap(item)) clearInnerBlankLines(item);
935
+ }
936
+ } else if (isMap(pair.value)) {
937
+ clearInnerBlankLines(pair.value);
938
+ }
939
+ }
940
+ }
941
+ function chooseQuoteType(value) {
942
+ if (requiresDoubleQuotes(value)) return "QUOTE_DOUBLE";
943
+ if (value.includes('"')) return "QUOTE_SINGLE";
944
+ if (isPlainSafe(value)) return "PLAIN";
945
+ return "QUOTE_SINGLE";
946
+ }
947
+ function requiresDoubleQuotes(s) {
948
+ return /[\x00-\x1f]/.test(s);
949
+ }
950
+ function isPlainSafe(s) {
951
+ if (s.length === 0) return false;
952
+ const first = s[0];
953
+ if ("-?:,[]{}#&*!|>'\"%@`".includes(first)) return false;
954
+ if (first === " " || first === " ") return false;
955
+ if (s.includes(": ")) return false;
956
+ if (s.includes(" #")) return false;
957
+ if (/^(true|false|null|~|y|n|yes|no|on|off|True|False|Null|Yes|No|On|Off|TRUE|FALSE|NULL|YES|NO|ON|OFF)$/.test(s)) return false;
958
+ if (/^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/.test(s)) return false;
959
+ if (/^0[xob]/.test(s)) return false;
960
+ return true;
961
+ }
962
+ function serialize(doc) {
963
+ const out = doc.toString({
964
+ indent: 2,
965
+ lineWidth: 0,
966
+ minContentWidth: 0,
967
+ blockQuote: "literal"
968
+ });
969
+ return out.endsWith("\n") ? out : out + "\n";
970
+ }
675
971
  export {
676
972
  DUPLICATE_ROUTE,
677
973
  DUPLICATE_VIEW_NAME,
974
+ FMT_NOT_CANONICAL,
975
+ FMT_PARSE_ERROR,
976
+ FMT_SCHEMA_INVALID,
678
977
  MERGE_COLLISION_COMPONENT,
679
978
  MERGE_COLLISION_VIEW,
680
979
  PARSE_ERROR,
@@ -683,6 +982,7 @@ export {
683
982
  SELECTOR_SYNTAX,
684
983
  UNKNOWN_SOURCE,
685
984
  UNKNOWN_VERSION,
985
+ canonicalize,
686
986
  explain,
687
987
  format,
688
988
  lint,