@formspec/analysis 0.1.0-alpha.20 → 0.1.0-alpha.21

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.cjs CHANGED
@@ -32,83 +32,42 @@ var index_exports = {};
32
32
  __export(index_exports, {
33
33
  FORMSPEC_ANALYSIS_PROTOCOL_VERSION: () => FORMSPEC_ANALYSIS_PROTOCOL_VERSION,
34
34
  FORMSPEC_ANALYSIS_SCHEMA_VERSION: () => FORMSPEC_ANALYSIS_SCHEMA_VERSION,
35
- analyzeConstraintTargets: () => analyzeConstraintTargets,
36
- buildConstraintTargetStates: () => buildConstraintTargetStates,
37
- buildFormSpecAnalysisFileSnapshot: () => buildFormSpecAnalysisFileSnapshot,
38
- buildSyntheticHelperPrelude: () => buildSyntheticHelperPrelude,
39
- checkSyntheticTagApplication: () => checkSyntheticTagApplication,
40
- collectCompatiblePathTargets: () => collectCompatiblePathTargets,
41
- collectReferencedTypeAnnotations: () => collectReferencedTypeAnnotations,
42
- collectReferencedTypeConstraints: () => collectReferencedTypeConstraints,
43
35
  computeFormSpecTextHash: () => computeFormSpecTextHash,
44
- dereferenceAnalysisType: () => dereferenceAnalysisType,
45
- extractPathTarget: () => extractPathTarget,
46
- findCommentTagAtOffset: () => findCommentTagAtOffset,
47
- findDeclarationForCommentOffset: () => findDeclarationForCommentOffset,
48
- findEnclosingDocComment: () => findEnclosingDocComment,
49
- formatConstraintTargetName: () => formatConstraintTargetName,
50
- formatPathTarget: () => formatPathTarget,
51
- getAllTagDefinitions: () => getAllTagDefinitions,
52
- getCommentCompletionContextAtOffset: () => getCommentCompletionContextAtOffset,
53
- getCommentCursorTargetAtOffset: () => getCommentCursorTargetAtOffset,
54
- getCommentHoverInfoAtOffset: () => getCommentHoverInfoAtOffset,
55
- getCommentTagSemanticContext: () => getCommentTagSemanticContext,
56
- getConstraintTagDefinitions: () => getConstraintTagDefinitions,
57
36
  getFormSpecManifestPath: () => getFormSpecManifestPath,
58
37
  getFormSpecWorkspaceId: () => getFormSpecWorkspaceId,
59
38
  getFormSpecWorkspaceRuntimeDirectory: () => getFormSpecWorkspaceRuntimeDirectory,
60
- getHostType: () => getHostType,
61
- getLastLeadingDocCommentRange: () => getLastLeadingDocCommentRange,
62
- getMatchingTagSignatures: () => getMatchingTagSignatures,
63
- getSemanticCommentCompletionContextAtOffset: () => getSemanticCommentCompletionContextAtOffset,
64
- getSubjectType: () => getSubjectType,
65
- getTagCompletionPrefixAtOffset: () => getTagCompletionPrefixAtOffset,
66
- getTagDefinition: () => getTagDefinition,
67
- getTagHoverMarkdown: () => getTagHoverMarkdown,
68
- getTypeSemanticCapabilities: () => getTypeSemanticCapabilities,
69
- hasTypeSemanticCapability: () => hasTypeSemanticCapability,
70
39
  isFormSpecAnalysisManifest: () => isFormSpecAnalysisManifest,
71
40
  isFormSpecSemanticQuery: () => isFormSpecSemanticQuery,
72
41
  isFormSpecSemanticResponse: () => isFormSpecSemanticResponse,
73
- lowerTagApplicationToSyntheticCall: () => lowerTagApplicationToSyntheticCall,
74
- normalizeFormSpecTagName: () => normalizeFormSpecTagName,
75
- parseCommentBlock: () => parseCommentBlock,
76
- parseConstraintTagValue: () => parseConstraintTagValue,
77
- parseDefaultValueTagValue: () => parseDefaultValueTagValue,
78
- parseTagSyntax: () => parseTagSyntax,
79
- resolveConstraintTargetState: () => resolveConstraintTargetState,
80
- resolveDeclarationPlacement: () => resolveDeclarationPlacement,
81
- resolvePathTargetType: () => resolvePathTargetType,
82
42
  serializeCommentTagSemanticContext: () => serializeCommentTagSemanticContext,
83
43
  serializeCommentTargetSpecifier: () => serializeCommentTargetSpecifier,
84
44
  serializeCompletionContext: () => serializeCompletionContext,
85
45
  serializeHoverInfo: () => serializeHoverInfo,
86
- serializeParsedCommentTag: () => serializeParsedCommentTag,
87
- sliceCommentSpan: () => sliceCommentSpan
46
+ serializeParsedCommentTag: () => serializeParsedCommentTag
88
47
  });
89
48
  module.exports = __toCommonJS(index_exports);
90
49
 
91
- // src/path-target.ts
92
- function extractPathTarget(text) {
93
- const trimmed = text.trimStart();
94
- const match = /^:([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)(?:\s+([\s\S]*))?$/u.exec(trimmed);
95
- if (!match?.[1]) {
96
- return null;
97
- }
98
- return {
99
- path: { segments: match[1].split(".") },
100
- remainingText: match[2] ?? ""
101
- };
102
- }
103
- function formatPathTarget(path2) {
104
- if ("segments" in path2) {
105
- return path2.segments.join(".");
106
- }
107
- return path2.join(".");
108
- }
109
-
110
50
  // src/tag-registry.ts
111
51
  var import_core = require("@formspec/core");
52
+ var FORM_SPEC_PLACEMENTS = [
53
+ "class",
54
+ "class-field",
55
+ "class-method",
56
+ "interface",
57
+ "interface-field",
58
+ "type-alias",
59
+ "type-alias-field",
60
+ "variable",
61
+ "function",
62
+ "function-parameter",
63
+ "method-parameter"
64
+ ];
65
+ var FORM_SPEC_TARGET_KINDS = [
66
+ "none",
67
+ "path",
68
+ "member",
69
+ "variant"
70
+ ];
112
71
  var FIELD_PLACEMENTS = [
113
72
  "class-field",
114
73
  "interface-field",
@@ -688,2525 +647,203 @@ var EXTRA_TAG_DEFINITIONS = Object.fromEntries(
688
647
  buildExtraTagDefinition(canonicalName, spec)
689
648
  ])
690
649
  );
691
- function normalizeFormSpecTagName(rawName) {
692
- return (0, import_core.normalizeConstraintTagName)(rawName);
693
- }
694
- function getTagDefinition(rawName, extensions) {
695
- const normalized = normalizeFormSpecTagName(rawName);
696
- const builtin = BUILTIN_TAG_DEFINITIONS[normalized];
697
- if (builtin !== void 0) {
698
- return builtin;
699
- }
700
- const extra = EXTRA_TAG_DEFINITIONS[normalized];
701
- if (extra !== void 0) {
702
- return extra;
703
- }
704
- const extensionRegistration = getExtensionConstraintTags(extensions).find(
705
- (tag) => tag.tagName === normalized
706
- );
707
- if (extensionRegistration === void 0) {
708
- return null;
709
- }
710
- return {
711
- canonicalName: extensionRegistration.tagName,
712
- valueKind: null,
713
- requiresArgument: true,
714
- supportedTargets: ["none"],
715
- allowDuplicates: true,
716
- category: "constraint",
717
- placements: FIELD_PLACEMENTS,
718
- capabilities: [],
719
- completionDetail: `Extension constraint tag from ${extensionRegistration.extensionId}`,
720
- hoverMarkdown: [
721
- `**@${extensionRegistration.tagName}** \`<value>\``,
722
- "",
723
- `Extension-defined constraint tag from \`${extensionRegistration.extensionId}\`.`,
724
- "",
725
- `**Signature:** \`@${extensionRegistration.tagName} <value>\``
726
- ].join("\n"),
727
- signatures: [
728
- {
729
- label: `@${extensionRegistration.tagName} <value>`,
730
- placements: FIELD_PLACEMENTS,
731
- parameters: [{ kind: "value", label: "<value>" }]
732
- }
733
- ]
734
- };
735
- }
736
- function getConstraintTagDefinitions(extensions) {
737
- const builtins = Object.values(BUILTIN_TAG_DEFINITIONS);
738
- const custom = getExtensionConstraintTags(extensions).map((tag) => getTagDefinition(tag.tagName, extensions)).filter((tag) => tag !== null);
739
- return [...builtins, ...custom];
740
- }
741
- function getAllTagDefinitions(extensions) {
742
- const builtins = Object.values(BUILTIN_TAG_DEFINITIONS);
743
- const extras = Object.values(EXTRA_TAG_DEFINITIONS);
744
- const custom = getExtensionConstraintTags(extensions).map((tag) => getTagDefinition(tag.tagName, extensions)).filter((tag) => tag !== null);
745
- return [...builtins, ...extras, ...custom];
746
- }
747
- function getTagHoverMarkdown(rawName, extensions) {
748
- return getTagDefinition(rawName, extensions)?.hoverMarkdown ?? null;
749
- }
750
- function getExtensionConstraintTags(extensions) {
751
- return extensions?.flatMap((extension) => {
752
- const tagRecords = extension.constraintTags ?? [];
753
- return tagRecords.map((tag) => ({
754
- extensionId: extension.extensionId,
755
- tagName: tag.tagName
756
- }));
757
- }) ?? [];
758
- }
759
650
 
760
- // src/comment-syntax.ts
761
- function isWhitespace(char) {
762
- return char === " " || char === " " || char === "\n" || char === "\r";
651
+ // src/semantic-protocol.ts
652
+ var FORMSPEC_ANALYSIS_PROTOCOL_VERSION = 1;
653
+ var FORMSPEC_ANALYSIS_SCHEMA_VERSION = 1;
654
+ function isObjectRecord(value) {
655
+ return typeof value === "object" && value !== null && !Array.isArray(value);
763
656
  }
764
- function isTagStart(lineText, index) {
765
- if (lineText[index] !== "@") {
766
- return false;
767
- }
768
- const nextChar = lineText[index + 1];
769
- if (nextChar === void 0 || !/[A-Za-z]/u.test(nextChar)) {
657
+ function isCommentSpan(value) {
658
+ if (!isObjectRecord(value)) {
770
659
  return false;
771
660
  }
772
- const previousChar = lineText[index - 1];
773
- return previousChar === void 0 || isWhitespace(previousChar);
774
- }
775
- function findTagEnd(lineText, index) {
776
- let cursor = index + 1;
777
- while (cursor < lineText.length && /[A-Za-z0-9]/u.test(lineText[cursor] ?? "")) {
778
- cursor += 1;
779
- }
780
- return cursor;
661
+ const candidate = value;
662
+ return typeof candidate.start === "number" && typeof candidate.end === "number";
781
663
  }
782
- function trimTrailingWhitespace(lineText, end) {
783
- let cursor = end;
784
- while (cursor > 0 && isWhitespace(lineText[cursor - 1])) {
785
- cursor -= 1;
786
- }
787
- return cursor;
664
+ function isStringArray(value) {
665
+ return Array.isArray(value) && value.every((entry) => typeof entry === "string");
788
666
  }
789
- function spanFromLine(line, start, end, baseOffset) {
790
- const rawStart = line.rawOffsets[start];
791
- if (rawStart === void 0) {
792
- throw new Error(`Invalid projected span start: ${String(start)}`);
793
- }
794
- const rawEnd = end >= line.text.length ? line.rawContentEnd : (line.rawOffsets[end - 1] ?? line.rawContentEnd - 1) + 1;
795
- return {
796
- start: baseOffset + rawStart,
797
- end: baseOffset + rawEnd
798
- };
667
+ var FORM_SPEC_PLACEMENT_VALUES = new Set(FORM_SPEC_PLACEMENTS);
668
+ var FORM_SPEC_TARGET_KIND_VALUES = new Set(FORM_SPEC_TARGET_KINDS);
669
+ function isPlacementValue(value) {
670
+ return typeof value === "string" && FORM_SPEC_PLACEMENT_VALUES.has(value);
799
671
  }
800
- function classifyTargetKind(canonicalName, targetText, extensions) {
801
- if (targetText === "singular" || targetText === "plural") {
802
- return "variant";
803
- }
804
- if (targetText.includes(".")) {
805
- return "path";
806
- }
807
- const definition = getTagDefinition(canonicalName, extensions);
808
- const supportedTargets = definition?.supportedTargets.filter((target) => target !== "none") ?? [];
809
- if (supportedTargets.includes("path")) {
810
- return "path";
811
- }
812
- if (supportedTargets.includes("member") && supportedTargets.includes("variant")) {
813
- return "ambiguous";
814
- }
815
- if (supportedTargets.includes("member")) {
816
- return "member";
817
- }
818
- if (supportedTargets.includes("variant")) {
819
- return "variant";
820
- }
821
- return "path";
672
+ function isTargetKindValue(value) {
673
+ return typeof value === "string" && FORM_SPEC_TARGET_KIND_VALUES.has(value);
822
674
  }
823
- function parseTargetSpecifier(line, payloadStart, payloadEnd, canonicalName, baseOffset, extensions) {
824
- if (payloadStart >= payloadEnd || line.text[payloadStart] !== ":") {
825
- return null;
826
- }
827
- let targetEnd = payloadStart + 1;
828
- while (targetEnd < payloadEnd && !isWhitespace(line.text[targetEnd])) {
829
- targetEnd += 1;
830
- }
831
- const fullText = line.text.slice(payloadStart, targetEnd);
832
- const targetText = fullText.slice(1);
833
- const parsedPath = extractPathTarget(fullText);
834
- const specifierSpan = spanFromLine(line, payloadStart + 1, targetEnd, baseOffset);
835
- return {
836
- rawText: targetText,
837
- valid: parsedPath !== null && parsedPath.remainingText === "",
838
- kind: classifyTargetKind(canonicalName, targetText, extensions),
839
- fullSpan: spanFromLine(line, payloadStart, targetEnd, baseOffset),
840
- colonSpan: spanFromLine(line, payloadStart, payloadStart + 1, baseOffset),
841
- span: specifierSpan,
842
- path: parsedPath?.path ?? null,
843
- localEnd: targetEnd
844
- };
675
+ function isPlacementArray(value) {
676
+ return Array.isArray(value) && value.every(isPlacementValue);
845
677
  }
846
- function projectCommentLines(commentText) {
847
- const projections = [];
848
- const commentBodyStart = commentText.startsWith("/**") ? 3 : commentText.startsWith("/*") ? 2 : 0;
849
- const commentBodyEnd = commentText.endsWith("*/") ? commentText.length - 2 : commentText.length;
850
- let cursor = commentBodyStart;
851
- while (cursor <= commentBodyEnd) {
852
- const lineStart = cursor;
853
- let lineEnd = cursor;
854
- while (lineEnd < commentBodyEnd && commentText[lineEnd] !== "\n") {
855
- lineEnd += 1;
856
- }
857
- let contentEnd = lineEnd;
858
- if (contentEnd > lineStart && commentText[contentEnd - 1] === "\r") {
859
- contentEnd -= 1;
860
- }
861
- let contentStart = lineStart;
862
- while (contentStart < contentEnd && (commentText[contentStart] === " " || commentText[contentStart] === " ")) {
863
- contentStart += 1;
864
- }
865
- if (contentStart < contentEnd && commentText[contentStart] === "*") {
866
- contentStart += 1;
867
- while (contentStart < contentEnd && (commentText[contentStart] === " " || commentText[contentStart] === " ")) {
868
- contentStart += 1;
869
- }
870
- }
871
- const rawOffsets = [];
872
- let text = "";
873
- for (let index = contentStart; index < contentEnd; index += 1) {
874
- rawOffsets.push(index);
875
- text += commentText[index] ?? "";
876
- }
877
- projections.push({
878
- text,
879
- rawOffsets,
880
- rawContentEnd: contentEnd
881
- });
882
- if (lineEnd >= commentBodyEnd) {
883
- break;
884
- }
885
- cursor = lineEnd + 1;
886
- }
887
- return projections;
678
+ function isTargetKindArray(value) {
679
+ return Array.isArray(value) && value.every(isTargetKindValue);
888
680
  }
889
- function parseCommentBlock(commentText, options) {
890
- const tags = [];
891
- const baseOffset = options?.offset ?? 0;
892
- for (const line of projectCommentLines(commentText)) {
893
- const tagStarts = [];
894
- for (let index = 0; index < line.text.length; index += 1) {
895
- if (isTagStart(line.text, index)) {
896
- tagStarts.push(index);
897
- }
898
- }
899
- for (let tagIndex = 0; tagIndex < tagStarts.length; tagIndex += 1) {
900
- const tagStart = tagStarts[tagIndex];
901
- if (tagStart === void 0) {
902
- continue;
903
- }
904
- const tagEnd = findTagEnd(line.text, tagStart);
905
- const nextTagStart = tagStarts[tagIndex + 1] ?? line.text.length;
906
- const trimmedTagSegmentEnd = trimTrailingWhitespace(line.text, nextTagStart);
907
- const rawName = line.text.slice(tagStart + 1, tagEnd);
908
- const canonicalName = normalizeFormSpecTagName(rawName);
909
- let payloadStart = tagEnd;
910
- while (payloadStart < trimmedTagSegmentEnd && isWhitespace(line.text[payloadStart])) {
911
- payloadStart += 1;
912
- }
913
- const target = parseTargetSpecifier(
914
- line,
915
- payloadStart,
916
- trimmedTagSegmentEnd,
917
- canonicalName,
918
- baseOffset,
919
- options?.extensions
920
- );
921
- let valueStart = payloadStart;
922
- if (target !== null) {
923
- valueStart = target.localEnd;
924
- while (valueStart < trimmedTagSegmentEnd && isWhitespace(line.text[valueStart])) {
925
- valueStart += 1;
926
- }
927
- }
928
- const payloadSpan = payloadStart < trimmedTagSegmentEnd ? spanFromLine(line, payloadStart, trimmedTagSegmentEnd, baseOffset) : null;
929
- const valueSpan = valueStart < trimmedTagSegmentEnd ? spanFromLine(line, valueStart, trimmedTagSegmentEnd, baseOffset) : null;
930
- const parsedTarget = target === null ? null : {
931
- rawText: target.rawText,
932
- valid: target.valid,
933
- kind: target.kind,
934
- fullSpan: target.fullSpan,
935
- colonSpan: target.colonSpan,
936
- span: target.span,
937
- path: target.path
938
- };
939
- tags.push({
940
- rawTagName: rawName,
941
- normalizedTagName: canonicalName,
942
- recognized: getTagDefinition(canonicalName, options?.extensions) !== null,
943
- fullSpan: spanFromLine(line, tagStart, trimmedTagSegmentEnd, baseOffset),
944
- tagNameSpan: spanFromLine(line, tagStart, tagEnd, baseOffset),
945
- payloadSpan,
946
- colonSpan: parsedTarget?.colonSpan ?? null,
947
- target: parsedTarget,
948
- argumentSpan: valueSpan,
949
- argumentText: valueSpan === null ? "" : commentText.slice(valueSpan.start - baseOffset, valueSpan.end - baseOffset)
950
- });
951
- }
681
+ function isIpcEndpoint(value) {
682
+ if (!isObjectRecord(value)) {
683
+ return false;
952
684
  }
953
- return {
954
- commentText,
955
- offset: baseOffset,
956
- tags
957
- };
685
+ const candidate = value;
686
+ return (candidate.kind === "unix-socket" || candidate.kind === "windows-pipe") && typeof candidate.address === "string";
958
687
  }
959
- function parseTagSyntax(rawTagName, payloadText, options) {
960
- const separator = payloadText === "" || isWhitespace(payloadText[0]) ? "" : " ";
961
- const parsed = parseCommentBlock(`/** @${rawTagName}${separator}${payloadText} */`, options);
962
- const [tag] = parsed.tags;
963
- if (tag === void 0) {
964
- throw new Error(`Unable to parse synthetic tag syntax for @${rawTagName}`);
688
+ function isSerializedTagDefinition(value) {
689
+ if (!isObjectRecord(value)) {
690
+ return false;
965
691
  }
966
- return tag;
967
- }
968
- function sliceCommentSpan(commentText, span, options) {
969
- const baseOffset = options?.offset ?? 0;
970
- return commentText.slice(span.start - baseOffset, span.end - baseOffset);
692
+ const candidate = value;
693
+ return typeof candidate.canonicalName === "string" && typeof candidate.completionDetail === "string" && typeof candidate.hoverMarkdown === "string";
971
694
  }
972
-
973
- // src/ts-binding.ts
974
- var ts = __toESM(require("typescript"), 1);
975
- function stripNullishUnion(type) {
976
- if (!type.isUnion()) {
977
- return type;
978
- }
979
- const nonNullish = type.types.filter(
980
- (member) => (member.flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined)) === 0
981
- );
982
- if (nonNullish.length === 1 && nonNullish[0] !== void 0) {
983
- return nonNullish[0];
695
+ function isSerializedTagSignature(value) {
696
+ if (!isObjectRecord(value)) {
697
+ return false;
984
698
  }
985
- return type;
986
- }
987
- function isIntersectionWithBase(type, predicate) {
988
- return type.isIntersection() && type.types.some((member) => predicate(member));
699
+ const candidate = value;
700
+ return typeof candidate.label === "string" && isPlacementArray(candidate.placements);
989
701
  }
990
- function isNumberLike(type) {
991
- const stripped = stripNullishUnion(type);
992
- if (stripped.flags & (ts.TypeFlags.Number | ts.TypeFlags.NumberLiteral | ts.TypeFlags.BigInt | ts.TypeFlags.BigIntLiteral)) {
993
- return true;
994
- }
995
- if (stripped.isUnion()) {
996
- return stripped.types.every((member) => isNumberLike(member));
702
+ function isSerializedCommentTargetSpecifier(value) {
703
+ if (!isObjectRecord(value)) {
704
+ return false;
997
705
  }
998
- return isIntersectionWithBase(stripped, isNumberLike);
706
+ const candidate = value;
707
+ return typeof candidate.rawText === "string" && typeof candidate.valid === "boolean" && typeof candidate.kind === "string" && isCommentSpan(candidate.fullSpan) && isCommentSpan(candidate.colonSpan) && isCommentSpan(candidate.span);
999
708
  }
1000
- function isStringLike(type) {
1001
- const stripped = stripNullishUnion(type);
1002
- if (stripped.flags & (ts.TypeFlags.String | ts.TypeFlags.StringLiteral)) {
1003
- return true;
1004
- }
1005
- if (stripped.isUnion()) {
1006
- return stripped.types.every((member) => isStringLike(member));
709
+ function isSerializedTagSemanticContext(value) {
710
+ if (!isObjectRecord(value)) {
711
+ return false;
1007
712
  }
1008
- return isIntersectionWithBase(stripped, isStringLike);
713
+ const candidate = value;
714
+ return typeof candidate.tagName === "string" && (candidate.tagDefinition === null || isSerializedTagDefinition(candidate.tagDefinition)) && (candidate.placement === null || isPlacementValue(candidate.placement)) && isTargetKindArray(candidate.supportedTargets) && isStringArray(candidate.targetCompletions) && isStringArray(candidate.compatiblePathTargets) && isStringArray(candidate.valueLabels) && Array.isArray(candidate.signatures) && candidate.signatures.every(isSerializedTagSignature) && (candidate.tagHoverMarkdown === null || typeof candidate.tagHoverMarkdown === "string") && (candidate.targetHoverMarkdown === null || typeof candidate.targetHoverMarkdown === "string") && (candidate.argumentHoverMarkdown === null || typeof candidate.argumentHoverMarkdown === "string");
1009
715
  }
1010
- function isBooleanLike(type) {
1011
- const stripped = stripNullishUnion(type);
1012
- if (stripped.flags & (ts.TypeFlags.Boolean | ts.TypeFlags.BooleanLiteral)) {
1013
- return true;
1014
- }
1015
- if (stripped.isUnion()) {
1016
- return stripped.types.every((member) => isBooleanLike(member));
716
+ function isSerializedCompletionContext(value) {
717
+ if (!isObjectRecord(value)) {
718
+ return false;
1017
719
  }
1018
- return isIntersectionWithBase(stripped, isBooleanLike);
1019
- }
1020
- function isNullLike(type) {
1021
- const stripped = stripNullishUnion(type);
1022
- if (stripped.flags & ts.TypeFlags.Null) {
1023
- return true;
720
+ const candidate = value;
721
+ if (typeof candidate.kind !== "string") {
722
+ return false;
1024
723
  }
1025
- if (stripped.isUnion()) {
1026
- return stripped.types.every((member) => isNullLike(member));
724
+ switch (candidate.kind) {
725
+ case "tag-name":
726
+ return typeof candidate.prefix === "string" && Array.isArray(candidate.availableTags) ? candidate.availableTags.every(isSerializedTagDefinition) : false;
727
+ case "target":
728
+ return isSerializedTagSemanticContext(candidate.semantic);
729
+ case "argument":
730
+ return isSerializedTagSemanticContext(candidate.semantic) && isStringArray(candidate.valueLabels);
731
+ case "none":
732
+ return true;
733
+ default:
734
+ return false;
1027
735
  }
1028
- return isIntersectionWithBase(stripped, isNullLike);
1029
736
  }
1030
- function isArrayLike(type, checker) {
1031
- const stripped = stripNullishUnion(type);
1032
- if (checker.isArrayType(stripped)) {
1033
- return true;
737
+ function isSerializedHoverInfo(value) {
738
+ if (!isObjectRecord(value)) {
739
+ return false;
1034
740
  }
1035
- const symbol = stripped.getSymbol();
1036
- return symbol?.name === "Array" || symbol?.name === "ReadonlyArray";
1037
- }
1038
- function isStringLiteralUnion(type) {
1039
- const stripped = stripNullishUnion(type);
1040
- return stripped.isUnion() && stripped.types.length > 0 && stripped.types.every(isStringLike);
741
+ const candidate = value;
742
+ return (candidate.kind === "tag-name" || candidate.kind === "target" || candidate.kind === "argument") && typeof candidate.markdown === "string";
1041
743
  }
1042
- function isObjectLike(type, checker) {
1043
- const stripped = stripNullishUnion(type);
1044
- return !isArrayLike(stripped, checker) && (stripped.flags & ts.TypeFlags.Object) !== 0;
744
+ function hasCurrentProtocolVersion(value) {
745
+ return isObjectRecord(value) && value["protocolVersion"] === FORMSPEC_ANALYSIS_PROTOCOL_VERSION;
1045
746
  }
1046
- function isJsonLike(type, checker) {
1047
- const stripped = stripNullishUnion(type);
1048
- if (isNullLike(stripped) || isNumberLike(stripped) || isStringLike(stripped) || isBooleanLike(stripped)) {
1049
- return true;
1050
- }
1051
- if (stripped.isUnion()) {
1052
- return stripped.types.every((member) => isJsonLike(member, checker));
1053
- }
1054
- if (isArrayLike(stripped, checker) || isObjectLike(stripped, checker)) {
1055
- return true;
747
+ function isAnalysisDiagnostic(value) {
748
+ if (!isObjectRecord(value)) {
749
+ return false;
1056
750
  }
1057
- return isIntersectionWithBase(stripped, (member) => isJsonLike(member, checker));
751
+ const candidate = value;
752
+ return typeof candidate.code === "string" && typeof candidate.message === "string" && isCommentSpan(candidate.range) && (candidate.severity === "error" || candidate.severity === "warning" || candidate.severity === "info");
1058
753
  }
1059
- function getArrayElementType(type, checker) {
1060
- const stripped = stripNullishUnion(type);
1061
- if (!checker.isArrayType(stripped)) {
1062
- return null;
754
+ function isAnalysisTagSnapshot(value) {
755
+ if (!isObjectRecord(value)) {
756
+ return false;
1063
757
  }
1064
- return checker.getTypeArguments(stripped)[0] ?? null;
758
+ const candidate = value;
759
+ return typeof candidate.rawTagName === "string" && typeof candidate.normalizedTagName === "string" && typeof candidate.recognized === "boolean" && isCommentSpan(candidate.fullSpan) && isCommentSpan(candidate.tagNameSpan) && (candidate.payloadSpan === null || isCommentSpan(candidate.payloadSpan)) && (candidate.target === null || isSerializedCommentTargetSpecifier(candidate.target)) && (candidate.argumentSpan === null || isCommentSpan(candidate.argumentSpan)) && typeof candidate.argumentText === "string" && isSerializedTagSemanticContext(candidate.semantic);
1065
760
  }
1066
- function resolveDeclarationPlacement(node) {
1067
- if (ts.isClassDeclaration(node)) return "class";
1068
- if (ts.isPropertyDeclaration(node)) return "class-field";
1069
- if (ts.isMethodDeclaration(node)) return "class-method";
1070
- if (ts.isInterfaceDeclaration(node)) return "interface";
1071
- if (ts.isPropertySignature(node)) return "interface-field";
1072
- if (ts.isTypeAliasDeclaration(node)) return "type-alias";
1073
- if (ts.isVariableDeclaration(node)) return "variable";
1074
- if (ts.isFunctionDeclaration(node)) return "function";
1075
- if (ts.isParameter(node)) {
1076
- if (ts.isMethodDeclaration(node.parent) || ts.isConstructorDeclaration(node.parent)) {
1077
- return "method-parameter";
1078
- }
1079
- return "function-parameter";
761
+ function isAnalysisCommentSnapshot(value) {
762
+ if (!isObjectRecord(value)) {
763
+ return false;
1080
764
  }
1081
- return null;
765
+ const candidate = value;
766
+ return isCommentSpan(candidate.commentSpan) && isCommentSpan(candidate.declarationSpan) && (candidate.placement === null || isPlacementValue(candidate.placement)) && (candidate.subjectType === null || typeof candidate.subjectType === "string") && (candidate.hostType === null || typeof candidate.hostType === "string") && Array.isArray(candidate.tags) && candidate.tags.every(isAnalysisTagSnapshot);
1082
767
  }
1083
- function getTypeSemanticCapabilities(type, checker) {
1084
- const capabilities = /* @__PURE__ */ new Set();
1085
- if (isNumberLike(type)) {
1086
- capabilities.add("numeric-comparable");
1087
- }
1088
- if (isStringLike(type)) {
1089
- capabilities.add("string-like");
1090
- }
1091
- if (isJsonLike(type, checker)) {
1092
- capabilities.add("json-like");
1093
- }
1094
- if (isArrayLike(type, checker)) {
1095
- capabilities.add("array-like");
1096
- }
1097
- if (isStringLiteralUnion(type)) {
1098
- capabilities.add("enum-member-addressable");
1099
- }
1100
- if (isObjectLike(type, checker)) {
1101
- capabilities.add("object-like");
768
+ function isAnalysisFileSnapshot(value) {
769
+ if (!isObjectRecord(value)) {
770
+ return false;
1102
771
  }
1103
- return [...capabilities];
1104
- }
1105
- function hasTypeSemanticCapability(type, checker, capability) {
1106
- return getTypeSemanticCapabilities(type, checker).includes(capability);
772
+ const candidate = value;
773
+ return typeof candidate.filePath === "string" && typeof candidate.sourceHash === "string" && typeof candidate.generatedAt === "string" && Array.isArray(candidate.comments) && candidate.comments.every(isAnalysisCommentSnapshot) && Array.isArray(candidate.diagnostics) && candidate.diagnostics.every(isAnalysisDiagnostic);
1107
774
  }
1108
- function resolvePathTargetType(type, checker, segments) {
1109
- const stripped = stripNullishUnion(type);
1110
- if (segments.length === 0) {
1111
- return {
1112
- kind: "resolved",
1113
- type: stripped
1114
- };
1115
- }
1116
- const arrayElementType = getArrayElementType(stripped, checker);
1117
- if (arrayElementType !== null) {
1118
- return resolvePathTargetType(arrayElementType, checker, segments);
1119
- }
1120
- if ((stripped.flags & ts.TypeFlags.Object) === 0) {
1121
- return {
1122
- kind: "unresolvable",
1123
- type: stripped
1124
- };
1125
- }
1126
- const [segment, ...rest] = segments;
1127
- if (segment === void 0) {
1128
- throw new Error("Invariant violation: path traversal requires a segment");
1129
- }
1130
- const property = stripped.getProperty(segment);
1131
- if (property === void 0) {
1132
- return {
1133
- kind: "missing-property",
1134
- segment
1135
- };
1136
- }
1137
- const declaration = property.valueDeclaration ?? property.declarations?.[0];
1138
- if (declaration === void 0) {
1139
- return {
1140
- kind: "unresolvable",
1141
- type: stripped
1142
- };
775
+ function isFormSpecAnalysisManifest(value) {
776
+ if (!isObjectRecord(value)) {
777
+ return false;
1143
778
  }
1144
- return resolvePathTargetType(
1145
- checker.getTypeOfSymbolAtLocation(property, declaration),
1146
- checker,
1147
- rest
1148
- );
779
+ const candidate = value;
780
+ return candidate.protocolVersion === FORMSPEC_ANALYSIS_PROTOCOL_VERSION && candidate.analysisSchemaVersion === FORMSPEC_ANALYSIS_SCHEMA_VERSION && typeof candidate.workspaceRoot === "string" && typeof candidate.workspaceId === "string" && isIpcEndpoint(candidate.endpoint) && typeof candidate.typescriptVersion === "string" && typeof candidate.extensionFingerprint === "string" && typeof candidate.generation === "number" && typeof candidate.updatedAt === "string";
1149
781
  }
1150
- function collectPropertyPaths(type, checker, capability, prefix, visited) {
1151
- const stripped = stripNullishUnion(type);
1152
- if (visited.has(stripped)) {
1153
- return [];
782
+ function isFormSpecSemanticQuery(value) {
783
+ if (!hasCurrentProtocolVersion(value)) {
784
+ return false;
1154
785
  }
1155
- visited.add(stripped);
1156
- const suggestions = [];
1157
- if (hasTypeSemanticCapability(stripped, checker, capability) && prefix.length > 0) {
1158
- suggestions.push(prefix.join("."));
786
+ const candidate = value;
787
+ if (typeof candidate.kind !== "string") {
788
+ return false;
1159
789
  }
1160
- for (const property of stripped.getProperties()) {
1161
- const declaration = property.valueDeclaration ?? property.declarations?.[0];
1162
- if (declaration === void 0) {
1163
- continue;
1164
- }
1165
- const propertyType = checker.getTypeOfSymbolAtLocation(property, declaration);
1166
- const nextPrefix = [...prefix, property.name];
1167
- suggestions.push(
1168
- ...collectPropertyPaths(propertyType, checker, capability, nextPrefix, visited)
1169
- );
790
+ switch (candidate.kind) {
791
+ case "health":
792
+ return true;
793
+ case "completion":
794
+ case "hover":
795
+ return typeof candidate.filePath === "string" && typeof candidate.offset === "number";
796
+ case "diagnostics":
797
+ case "file-snapshot":
798
+ return typeof candidate.filePath === "string";
799
+ default:
800
+ return false;
1170
801
  }
1171
- return suggestions;
1172
- }
1173
- function collectCompatiblePathTargets(type, checker, capability) {
1174
- return collectPropertyPaths(type, checker, capability, [], /* @__PURE__ */ new Set());
1175
- }
1176
-
1177
- // src/cursor-context.ts
1178
- function isWordChar(char) {
1179
- return char !== void 0 && /[A-Za-z0-9]/u.test(char);
1180
- }
1181
- function isWhitespaceLike(char) {
1182
- return char === void 0 || /\s/u.test(char) || char === "*";
1183
802
  }
1184
- function containsOffset(tag, offset) {
1185
- return offset >= tag.tagNameSpan.start && offset <= tag.tagNameSpan.end;
1186
- }
1187
- function filterSignaturesByPlacement(signatures, placement) {
1188
- if (placement === void 0 || placement === null) {
1189
- return signatures;
803
+ function isFormSpecSemanticResponse(value) {
804
+ if (!hasCurrentProtocolVersion(value)) {
805
+ return false;
1190
806
  }
1191
- const filtered = signatures.filter((signature) => signature.placements.includes(placement));
1192
- return filtered.length > 0 ? filtered : signatures;
1193
- }
1194
- function getCompatiblePathTargetsForSignatures(signatures, checker, subjectType) {
1195
- if (checker === void 0 || subjectType === void 0) {
1196
- return [];
807
+ const candidate = value;
808
+ if (typeof candidate.kind !== "string") {
809
+ return false;
1197
810
  }
1198
- const suggestions = /* @__PURE__ */ new Set();
1199
- for (const signature of signatures) {
1200
- for (const parameter of signature.parameters) {
1201
- if (parameter.kind !== "target-path" || parameter.capability === void 0) {
1202
- continue;
1203
- }
1204
- for (const target of collectCompatiblePathTargets(
1205
- subjectType,
1206
- checker,
1207
- parameter.capability
1208
- )) {
1209
- suggestions.add(target);
1210
- }
1211
- }
811
+ switch (candidate.kind) {
812
+ case "health":
813
+ return isFormSpecAnalysisManifest(candidate.manifest);
814
+ case "completion":
815
+ return typeof candidate.sourceHash === "string" && isSerializedCompletionContext(candidate.context);
816
+ case "hover":
817
+ return typeof candidate.sourceHash === "string" && (candidate.hover === null || isSerializedHoverInfo(candidate.hover));
818
+ case "diagnostics":
819
+ return typeof candidate.sourceHash === "string" && Array.isArray(candidate.diagnostics) && candidate.diagnostics.every(isAnalysisDiagnostic);
820
+ case "file-snapshot":
821
+ return candidate.snapshot === null || isAnalysisFileSnapshot(candidate.snapshot);
822
+ case "error":
823
+ return typeof candidate.error === "string";
824
+ default:
825
+ return false;
1212
826
  }
1213
- return [...suggestions].sort();
1214
827
  }
1215
- function getSupportedTargets(signatures) {
1216
- const supportedTargets = /* @__PURE__ */ new Set(["none"]);
1217
- for (const signature of signatures) {
1218
- for (const parameter of signature.parameters) {
1219
- switch (parameter.kind) {
1220
- case "target-path":
1221
- supportedTargets.add("path");
1222
- break;
1223
- case "target-member":
1224
- supportedTargets.add("member");
1225
- break;
1226
- case "target-variant":
1227
- supportedTargets.add("variant");
1228
- break;
1229
- default:
1230
- break;
1231
- }
1232
- }
828
+ function computeFormSpecTextHash(text) {
829
+ let hash = 2166136261;
830
+ for (let index = 0; index < text.length; index += 1) {
831
+ hash ^= text.charCodeAt(index);
832
+ hash = Math.imul(hash, 16777619);
1233
833
  }
1234
- return [...supportedTargets];
834
+ return (hash >>> 0).toString(16).padStart(8, "0");
1235
835
  }
1236
- function getTargetCompletions(signatures, compatiblePathTargets) {
1237
- const completions = /* @__PURE__ */ new Set();
1238
- for (const signature of signatures) {
1239
- for (const parameter of signature.parameters) {
1240
- switch (parameter.kind) {
1241
- case "target-path":
1242
- for (const target of compatiblePathTargets) {
1243
- completions.add(target);
1244
- }
1245
- break;
1246
- case "target-variant":
1247
- completions.add("singular");
1248
- completions.add("plural");
1249
- break;
1250
- default:
1251
- break;
1252
- }
1253
- }
836
+ function serializeCommentTargetSpecifier(target) {
837
+ if (target === null) {
838
+ return null;
1254
839
  }
1255
- return [...completions];
1256
- }
1257
- function getCommentTagSemanticContext(tag, options) {
1258
- const tagDefinition = getTagDefinition(tag.normalizedTagName, options?.extensions);
1259
- const signatures = filterSignaturesByPlacement(
1260
- tagDefinition?.signatures ?? [],
1261
- options?.placement
1262
- );
1263
- const compatiblePathTargets = getCompatiblePathTargetsForSignatures(
1264
- signatures,
1265
- options?.checker,
1266
- options?.subjectType
1267
- );
1268
- const semantic = {
1269
- tag,
1270
- tagDefinition,
1271
- placement: options?.placement ?? null,
1272
- signatures,
1273
- supportedTargets: getSupportedTargets(signatures),
1274
- targetCompletions: getTargetCompletions(signatures, compatiblePathTargets),
1275
- compatiblePathTargets,
1276
- valueLabels: getValueLabels(signatures),
1277
- tagHoverMarkdown: tagDefinition?.hoverMarkdown ?? null,
1278
- targetHoverMarkdown: null,
1279
- argumentHoverMarkdown: null
1280
- };
1281
840
  return {
1282
- ...semantic,
1283
- targetHoverMarkdown: buildTargetHoverMarkdown(semantic),
1284
- argumentHoverMarkdown: buildArgumentHoverMarkdown(semantic)
1285
- };
1286
- }
1287
- function getValueLabels(signatures) {
1288
- const labels = /* @__PURE__ */ new Set();
1289
- for (const signature of signatures) {
1290
- for (const parameter of signature.parameters) {
1291
- if (parameter.kind === "value") {
1292
- labels.add(parameter.label);
1293
- }
1294
- }
1295
- }
1296
- return [...labels];
1297
- }
1298
- function getTargetKindLabels(supportedTargets) {
1299
- const labels = supportedTargets.filter((kind) => kind !== "none").map((kind) => `\`${kind}\``);
1300
- return labels.length === 0 ? "none" : labels.join(", ");
1301
- }
1302
- function buildTargetHoverMarkdown(semantic) {
1303
- if (semantic.tagDefinition === null) {
1304
- return null;
1305
- }
1306
- const currentTarget = semantic.tag.target?.rawText ?? "";
1307
- const lines = [
1308
- `**Target for @${semantic.tagDefinition.canonicalName}**`,
1309
- "",
1310
- `Supported target forms: ${getTargetKindLabels(semantic.supportedTargets)}`
1311
- ];
1312
- if (currentTarget !== "") {
1313
- lines.push("", `Current target: \`:${currentTarget}\``);
1314
- }
1315
- const MAX_HOVER_PATH_TARGETS = 8;
1316
- if (semantic.compatiblePathTargets.length > 0) {
1317
- lines.push("", "**Compatible path targets:**");
1318
- for (const target of semantic.compatiblePathTargets.slice(0, MAX_HOVER_PATH_TARGETS)) {
1319
- lines.push(`- \`:${target}\``);
1320
- }
1321
- } else if (semantic.supportedTargets.includes("variant")) {
1322
- lines.push("", "Use `:singular` or `:plural` for variant-specific names.");
1323
- } else if (semantic.supportedTargets.includes("path")) {
1324
- lines.push(
1325
- "",
1326
- "Type-aware path completions become available when TypeScript binding is provided."
1327
- );
1328
- }
1329
- return lines.join("\n");
1330
- }
1331
- function buildArgumentHoverMarkdown(semantic) {
1332
- if (semantic.tagDefinition === null) {
1333
- return null;
1334
- }
1335
- const valueLabels = getValueLabels(semantic.signatures);
1336
- const formattedValueLabels = valueLabels.map((label) => `\`${label}\``);
1337
- const soleSignature = semantic.signatures.length === 1 ? semantic.signatures[0] : void 0;
1338
- const signatureLines = semantic.signatures.length === 0 ? [] : soleSignature !== void 0 ? [`**Signature:** \`${soleSignature.label}\``] : [
1339
- "**Signatures:**",
1340
- ...semantic.signatures.map((signature) => `- \`${signature.label}\``)
1341
- ];
1342
- return [
1343
- `**Argument for @${semantic.tagDefinition.canonicalName}**`,
1344
- "",
1345
- `Expected value: ${formattedValueLabels.join(" or ") || "`<value>`"}`,
1346
- "",
1347
- ...signatureLines
1348
- ].join("\n");
1349
- }
1350
- function findEnclosingDocComment(documentText, offset, options) {
1351
- const commentPattern = /\/\*\*[\s\S]*?\*\//gu;
1352
- for (const match of documentText.matchAll(commentPattern)) {
1353
- const fullMatch = match[0];
1354
- const index = match.index;
1355
- const start = index;
1356
- const end = start + fullMatch.length;
1357
- if (offset >= start && offset <= end) {
1358
- return {
1359
- text: fullMatch,
1360
- start,
1361
- end,
1362
- parsed: parseCommentBlock(fullMatch, {
1363
- offset: start,
1364
- ...options?.extensions !== void 0 ? { extensions: options.extensions } : {}
1365
- })
1366
- };
1367
- }
1368
- }
1369
- return null;
1370
- }
1371
- function findCommentTagAtOffset(documentText, offset, options) {
1372
- const comment = findEnclosingDocComment(documentText, offset, options);
1373
- if (comment === null) {
1374
- return null;
1375
- }
1376
- return comment.parsed.tags.find((tag) => containsOffset(tag, offset)) ?? null;
1377
- }
1378
- function getCommentCursorTargetAtOffset(documentText, offset, options) {
1379
- const comment = findEnclosingDocComment(documentText, offset, options);
1380
- if (comment === null) {
1381
- return null;
1382
- }
1383
- for (const tag of comment.parsed.tags) {
1384
- if (containsOffset(tag, offset)) {
1385
- return {
1386
- kind: "tag-name",
1387
- tag
1388
- };
1389
- }
1390
- if (tag.colonSpan !== null && offset >= tag.colonSpan.start && offset <= tag.colonSpan.end) {
1391
- return {
1392
- kind: "colon",
1393
- tag
1394
- };
1395
- }
1396
- if (tag.target !== null && offset >= tag.target.span.start && offset <= tag.target.span.end) {
1397
- return {
1398
- kind: "target",
1399
- tag
1400
- };
1401
- }
1402
- if (tag.argumentSpan !== null && offset >= tag.argumentSpan.start && offset <= tag.argumentSpan.end) {
1403
- return {
1404
- kind: "argument",
1405
- tag
1406
- };
1407
- }
1408
- }
1409
- return null;
1410
- }
1411
- function getTagCompletionPrefixAtOffset(documentText, offset) {
1412
- const comment = findEnclosingDocComment(documentText, offset);
1413
- if (comment === null) {
1414
- return null;
1415
- }
1416
- const relativeOffset = offset - comment.start;
1417
- if (relativeOffset < 0 || relativeOffset > comment.text.length) {
1418
- return null;
1419
- }
1420
- let cursor = relativeOffset;
1421
- while (cursor > 0 && isWordChar(comment.text[cursor - 1])) {
1422
- cursor -= 1;
1423
- }
1424
- const atIndex = cursor - 1;
1425
- if (atIndex < 0 || comment.text[atIndex] !== "@") {
1426
- return null;
1427
- }
1428
- const previousChar = atIndex > 0 ? comment.text[atIndex - 1] : void 0;
1429
- if (!isWhitespaceLike(previousChar)) {
1430
- return null;
1431
- }
1432
- return comment.text.slice(cursor, relativeOffset);
1433
- }
1434
- function getCommentCompletionContextAtOffset(documentText, offset, options) {
1435
- const prefix = getTagCompletionPrefixAtOffset(documentText, offset);
1436
- if (prefix !== null) {
1437
- return {
1438
- kind: "tag-name",
1439
- prefix
1440
- };
1441
- }
1442
- const target = getCommentCursorTargetAtOffset(documentText, offset, options);
1443
- if (target?.kind === "target" || target?.kind === "colon") {
1444
- return {
1445
- kind: "target",
1446
- tag: target.tag
1447
- };
1448
- }
1449
- if (target?.kind === "argument") {
1450
- return {
1451
- kind: "argument",
1452
- tag: target.tag
1453
- };
1454
- }
1455
- return {
1456
- kind: "none"
1457
- };
1458
- }
1459
- function getSemanticCommentCompletionContextAtOffset(documentText, offset, options) {
1460
- const prefix = getTagCompletionPrefixAtOffset(documentText, offset);
1461
- if (prefix !== null) {
1462
- return {
1463
- kind: "tag-name",
1464
- prefix,
1465
- availableTags: getAllTagDefinitions(options?.extensions)
1466
- };
1467
- }
1468
- const target = getCommentCursorTargetAtOffset(
1469
- documentText,
1470
- offset,
1471
- options?.extensions ? { extensions: options.extensions } : void 0
1472
- );
1473
- if (target?.kind === "target" || target?.kind === "colon") {
1474
- return {
1475
- kind: "target",
1476
- semantic: getCommentTagSemanticContext(target.tag, options)
1477
- };
1478
- }
1479
- if (target?.kind === "argument") {
1480
- const semantic = getCommentTagSemanticContext(target.tag, options);
1481
- return {
1482
- kind: "argument",
1483
- semantic,
1484
- valueLabels: semantic.valueLabels
1485
- };
1486
- }
1487
- return { kind: "none" };
1488
- }
1489
- function getCommentHoverInfoAtOffset(documentText, offset, options) {
1490
- const target = getCommentCursorTargetAtOffset(
1491
- documentText,
1492
- offset,
1493
- options?.extensions ? { extensions: options.extensions } : void 0
1494
- );
1495
- if (target === null) {
1496
- return null;
1497
- }
1498
- const semantic = getCommentTagSemanticContext(target.tag, options);
1499
- let markdown = null;
1500
- switch (target.kind) {
1501
- case "tag-name":
1502
- markdown = semantic.tagHoverMarkdown;
1503
- break;
1504
- case "colon":
1505
- case "target":
1506
- markdown = semantic.targetHoverMarkdown;
1507
- break;
1508
- case "argument":
1509
- markdown = semantic.argumentHoverMarkdown;
1510
- break;
1511
- default: {
1512
- const exhaustive = target.kind;
1513
- void exhaustive;
1514
- break;
1515
- }
1516
- }
1517
- return markdown === null ? null : {
1518
- kind: target.kind === "colon" ? "target" : target.kind,
1519
- markdown
1520
- };
1521
- }
1522
-
1523
- // src/tag-value-parser.ts
1524
- var import_core2 = require("@formspec/core");
1525
- var NUMERIC_CONSTRAINT_MAP = {
1526
- minimum: "minimum",
1527
- maximum: "maximum",
1528
- exclusiveMinimum: "exclusiveMinimum",
1529
- exclusiveMaximum: "exclusiveMaximum",
1530
- multipleOf: "multipleOf"
1531
- };
1532
- var LENGTH_CONSTRAINT_MAP = {
1533
- minLength: "minLength",
1534
- maxLength: "maxLength",
1535
- minItems: "minItems",
1536
- maxItems: "maxItems"
1537
- };
1538
- function tryParseJson(text) {
1539
- try {
1540
- return JSON.parse(text);
1541
- } catch {
1542
- return null;
1543
- }
1544
- }
1545
- function syntaxOptions(registry) {
1546
- return registry?.extensions !== void 0 ? { extensions: registry.extensions } : void 0;
1547
- }
1548
- function parseConstraintTagValue(tagName, text, provenance, options) {
1549
- const customConstraint = parseExtensionConstraintTagValue(tagName, text, provenance, options);
1550
- if (customConstraint !== null) {
1551
- return customConstraint;
1552
- }
1553
- if (!(0, import_core2.isBuiltinConstraintName)(tagName)) {
1554
- return null;
1555
- }
1556
- const parsedTag = parseTagSyntax(tagName, text, syntaxOptions(options?.registry));
1557
- if (parsedTag.target !== null && !parsedTag.target.valid) {
1558
- return null;
1559
- }
1560
- const effectiveText = parsedTag.argumentText;
1561
- const path2 = parsedTag.target?.path ?? void 0;
1562
- const expectedType = import_core2.BUILTIN_CONSTRAINT_DEFINITIONS[tagName];
1563
- if (expectedType !== "boolean" && effectiveText.trim() === "") {
1564
- return null;
1565
- }
1566
- if (expectedType === "number") {
1567
- const value = Number(effectiveText);
1568
- if (Number.isNaN(value)) {
1569
- return null;
1570
- }
1571
- const numericKind = NUMERIC_CONSTRAINT_MAP[tagName];
1572
- if (numericKind !== void 0) {
1573
- return {
1574
- kind: "constraint",
1575
- constraintKind: numericKind,
1576
- value,
1577
- ...path2 !== void 0 && { path: path2 },
1578
- provenance
1579
- };
1580
- }
1581
- const lengthKind = LENGTH_CONSTRAINT_MAP[tagName];
1582
- if (lengthKind !== void 0) {
1583
- return {
1584
- kind: "constraint",
1585
- constraintKind: lengthKind,
1586
- value,
1587
- ...path2 !== void 0 && { path: path2 },
1588
- provenance
1589
- };
1590
- }
1591
- return null;
1592
- }
1593
- if (expectedType === "boolean") {
1594
- const trimmed = effectiveText.trim();
1595
- if (trimmed !== "" && trimmed !== "true") {
1596
- return null;
1597
- }
1598
- if (tagName === "uniqueItems") {
1599
- return {
1600
- kind: "constraint",
1601
- constraintKind: "uniqueItems",
1602
- value: true,
1603
- ...path2 !== void 0 && { path: path2 },
1604
- provenance
1605
- };
1606
- }
1607
- return null;
1608
- }
1609
- if (expectedType === "json") {
1610
- if (tagName === "const") {
1611
- const trimmedText = effectiveText.trim();
1612
- if (trimmedText === "") {
1613
- return null;
1614
- }
1615
- try {
1616
- const parsed2 = JSON.parse(trimmedText);
1617
- return {
1618
- kind: "constraint",
1619
- constraintKind: "const",
1620
- value: parsed2,
1621
- ...path2 !== void 0 && { path: path2 },
1622
- provenance
1623
- };
1624
- } catch {
1625
- return {
1626
- kind: "constraint",
1627
- constraintKind: "const",
1628
- value: trimmedText,
1629
- ...path2 !== void 0 && { path: path2 },
1630
- provenance
1631
- };
1632
- }
1633
- }
1634
- const parsed = tryParseJson(effectiveText);
1635
- if (!Array.isArray(parsed)) {
1636
- return null;
1637
- }
1638
- const members = [];
1639
- for (const item of parsed) {
1640
- if (typeof item === "string" || typeof item === "number") {
1641
- members.push(item);
1642
- continue;
1643
- }
1644
- if (typeof item === "object" && item !== null && "id" in item) {
1645
- const id = item["id"];
1646
- if (typeof id === "string" || typeof id === "number") {
1647
- members.push(id);
1648
- }
1649
- }
1650
- }
1651
- return {
1652
- kind: "constraint",
1653
- constraintKind: "allowedMembers",
1654
- members,
1655
- ...path2 !== void 0 && { path: path2 },
1656
- provenance
1657
- };
1658
- }
1659
- return {
1660
- kind: "constraint",
1661
- constraintKind: "pattern",
1662
- pattern: effectiveText,
1663
- ...path2 !== void 0 && { path: path2 },
1664
- provenance
1665
- };
1666
- }
1667
- function parseDefaultValueTagValue(text, provenance) {
1668
- const trimmed = text.trim();
1669
- let value;
1670
- if (trimmed === "null") {
1671
- value = null;
1672
- } else if (trimmed === "true") {
1673
- value = true;
1674
- } else if (trimmed === "false") {
1675
- value = false;
1676
- } else {
1677
- const parsed = tryParseJson(trimmed);
1678
- value = parsed !== null ? parsed : trimmed;
1679
- }
1680
- return {
1681
- kind: "annotation",
1682
- annotationKind: "defaultValue",
1683
- value,
1684
- provenance
1685
- };
1686
- }
1687
- function parseExtensionConstraintTagValue(tagName, text, provenance, options) {
1688
- const parsedTag = parseTagSyntax(tagName, text, syntaxOptions(options?.registry));
1689
- if (parsedTag.target !== null && !parsedTag.target.valid) {
1690
- return null;
1691
- }
1692
- const effectiveText = parsedTag.argumentText;
1693
- const path2 = parsedTag.target?.path ?? void 0;
1694
- const registry = options?.registry;
1695
- if (registry === void 0) {
1696
- return null;
1697
- }
1698
- if (effectiveText.trim() === "") {
1699
- return null;
1700
- }
1701
- const directTag = registry.findConstraintTag(tagName);
1702
- if (directTag !== void 0) {
1703
- return makeCustomConstraintNode(
1704
- directTag.extensionId,
1705
- directTag.registration.constraintName,
1706
- directTag.registration.parseValue(effectiveText),
1707
- provenance,
1708
- path2,
1709
- registry
1710
- );
1711
- }
1712
- if (!(0, import_core2.isBuiltinConstraintName)(tagName)) {
1713
- return null;
1714
- }
1715
- const broadenedTypeId = getBroadenedCustomTypeId(options?.fieldType);
1716
- if (broadenedTypeId === void 0) {
1717
- return null;
1718
- }
1719
- const broadened = registry.findBuiltinConstraintBroadening(broadenedTypeId, tagName);
1720
- if (broadened === void 0) {
1721
- return null;
1722
- }
1723
- return makeCustomConstraintNode(
1724
- broadened.extensionId,
1725
- broadened.registration.constraintName,
1726
- broadened.registration.parseValue(effectiveText),
1727
- provenance,
1728
- path2,
1729
- registry
1730
- );
1731
- }
1732
- function getBroadenedCustomTypeId(fieldType) {
1733
- if (fieldType?.kind === "custom") {
1734
- return fieldType.typeId;
1735
- }
1736
- if (fieldType?.kind !== "union") {
1737
- return void 0;
1738
- }
1739
- const customMembers = fieldType.members.filter(
1740
- (member) => member.kind === "custom"
1741
- );
1742
- if (customMembers.length !== 1) {
1743
- return void 0;
1744
- }
1745
- const nonCustomMembers = fieldType.members.filter((member) => member.kind !== "custom");
1746
- const allOtherMembersAreNull = nonCustomMembers.every(
1747
- (member) => member.kind === "primitive" && member.primitiveKind === "null"
1748
- );
1749
- const customMember = customMembers[0];
1750
- return allOtherMembersAreNull && customMember !== void 0 ? customMember.typeId : void 0;
1751
- }
1752
- function makeCustomConstraintNode(extensionId, constraintName, payload, provenance, path2, registry) {
1753
- const constraintId = `${extensionId}/${constraintName}`;
1754
- const registration = registry.findConstraint(constraintId);
1755
- if (registration === void 0) {
1756
- throw new Error(
1757
- `Custom TSDoc tag resolved to unregistered constraint "${constraintId}". Register the constraint before using its tag.`
1758
- );
1759
- }
1760
- return {
1761
- kind: "constraint",
1762
- constraintKind: "custom",
1763
- constraintId,
1764
- payload,
1765
- compositionRule: registration.compositionRule,
1766
- ...path2 !== void 0 && { path: path2 },
1767
- provenance
1768
- };
1769
- }
1770
-
1771
- // src/semantic-targets.ts
1772
- var import_core3 = require("@formspec/core");
1773
- function pathKey(path2) {
1774
- return path2?.segments.join(".") ?? "";
1775
- }
1776
- function formatConstraintTargetName(fieldName, path2) {
1777
- if (path2 === null || path2.segments.length === 0) {
1778
- return fieldName;
1779
- }
1780
- return `${fieldName}.${path2.segments.join(".")}`;
1781
- }
1782
- function dereferenceAnalysisType(type, typeRegistry) {
1783
- let current = type;
1784
- const seen = /* @__PURE__ */ new Set();
1785
- while (current.kind === "reference") {
1786
- if (seen.has(current.name)) {
1787
- return current;
1788
- }
1789
- seen.add(current.name);
1790
- const definition = typeRegistry[current.name];
1791
- if (definition === void 0) {
1792
- return current;
1793
- }
1794
- current = definition.type;
1795
- }
1796
- return current;
1797
- }
1798
- function collectReferencedTypeConstraints(type, typeRegistry) {
1799
- const collected = [];
1800
- let current = type;
1801
- const seen = /* @__PURE__ */ new Set();
1802
- while (current.kind === "reference") {
1803
- if (seen.has(current.name)) {
1804
- break;
1805
- }
1806
- seen.add(current.name);
1807
- const definition = typeRegistry[current.name];
1808
- if (definition === void 0) {
1809
- break;
1810
- }
1811
- if (definition.constraints !== void 0) {
1812
- collected.push(...definition.constraints);
1813
- }
1814
- current = definition.type;
1815
- }
1816
- return collected;
1817
- }
1818
- function collectReferencedTypeAnnotations(type, typeRegistry) {
1819
- const collected = [];
1820
- let current = type;
1821
- const seen = /* @__PURE__ */ new Set();
1822
- while (current.kind === "reference") {
1823
- if (seen.has(current.name)) {
1824
- break;
1825
- }
1826
- seen.add(current.name);
1827
- const definition = typeRegistry[current.name];
1828
- if (definition === void 0) {
1829
- break;
1830
- }
1831
- if (definition.annotations !== void 0) {
1832
- collected.push(...definition.annotations);
1833
- }
1834
- current = definition.type;
1835
- }
1836
- return collected;
1837
- }
1838
- function resolveProperty(type, typeRegistry, segments) {
1839
- const effectiveType = dereferenceAnalysisType(type, typeRegistry);
1840
- if (segments.length === 0) {
1841
- return { kind: "resolved", property: null, rawType: type, type: effectiveType };
1842
- }
1843
- if (effectiveType.kind === "array") {
1844
- return resolveProperty(effectiveType.items, typeRegistry, segments);
1845
- }
1846
- if (effectiveType.kind !== "object") {
1847
- return { kind: "unresolvable", type: effectiveType };
1848
- }
1849
- const [segment, ...rest] = segments;
1850
- if (segment === void 0) {
1851
- throw new Error("Invariant violation: object traversal requires a segment");
1852
- }
1853
- const property = effectiveType.properties.find((candidate) => candidate.name === segment);
1854
- if (property === void 0) {
1855
- return { kind: "missing-property", segment };
1856
- }
1857
- if (rest.length === 0) {
1858
- return {
1859
- kind: "resolved",
1860
- property,
1861
- rawType: property.type,
1862
- type: dereferenceAnalysisType(property.type, typeRegistry)
1863
- };
1864
- }
1865
- return resolveProperty(property.type, typeRegistry, rest);
1866
- }
1867
- function resolveConstraintTargetState(fieldName, fieldType, path2, localConstraints, typeRegistry) {
1868
- if (path2 === null) {
1869
- const inheritedConstraints2 = collectReferencedTypeConstraints(fieldType, typeRegistry);
1870
- const inheritedAnnotations2 = collectReferencedTypeAnnotations(fieldType, typeRegistry);
1871
- const type = dereferenceAnalysisType(fieldType, typeRegistry);
1872
- return {
1873
- kind: "resolved",
1874
- fieldName,
1875
- path: path2,
1876
- targetName: fieldName,
1877
- type,
1878
- inheritedConstraints: inheritedConstraints2,
1879
- inheritedAnnotations: inheritedAnnotations2,
1880
- localConstraints,
1881
- effectiveConstraints: [...inheritedConstraints2, ...localConstraints]
1882
- };
1883
- }
1884
- const resolution = resolveProperty(fieldType, typeRegistry, path2.segments);
1885
- const targetName = formatConstraintTargetName(fieldName, path2);
1886
- if (resolution.kind === "missing-property") {
1887
- return {
1888
- kind: "missing-property",
1889
- fieldName,
1890
- path: path2,
1891
- targetName,
1892
- segment: resolution.segment,
1893
- localConstraints
1894
- };
1895
- }
1896
- if (resolution.kind === "unresolvable") {
1897
- return {
1898
- kind: "unresolvable",
1899
- fieldName,
1900
- path: path2,
1901
- targetName,
1902
- type: resolution.type,
1903
- localConstraints
1904
- };
1905
- }
1906
- const propertyConstraints = resolution.property?.constraints ?? [];
1907
- const propertyAnnotations = resolution.property?.annotations ?? [];
1908
- const referencedConstraints = collectReferencedTypeConstraints(resolution.rawType, typeRegistry);
1909
- const referencedAnnotations = collectReferencedTypeAnnotations(resolution.rawType, typeRegistry);
1910
- const inheritedConstraints = [...propertyConstraints, ...referencedConstraints];
1911
- const inheritedAnnotations = [...propertyAnnotations, ...referencedAnnotations];
1912
- return {
1913
- kind: "resolved",
1914
- fieldName,
1915
- path: path2,
1916
- targetName,
1917
- type: resolution.type,
1918
- inheritedConstraints,
1919
- inheritedAnnotations,
1920
- localConstraints,
1921
- effectiveConstraints: [...inheritedConstraints, ...localConstraints]
1922
- };
1923
- }
1924
- function cloneTargetPath(path2) {
1925
- if (path2 === void 0) {
1926
- return null;
1927
- }
1928
- return { segments: [...path2.segments] };
1929
- }
1930
- function buildConstraintTargetStates(fieldName, fieldType, constraints, typeRegistry) {
1931
- const grouped = /* @__PURE__ */ new Map([
1932
- ["", { path: null, constraints: [] }]
1933
- ]);
1934
- for (const constraint of constraints) {
1935
- const path2 = cloneTargetPath(constraint.path);
1936
- const key = pathKey(path2);
1937
- let bucket = grouped.get(key);
1938
- if (bucket === void 0) {
1939
- bucket = { path: path2, constraints: [] };
1940
- grouped.set(key, bucket);
1941
- }
1942
- bucket.constraints.push(constraint);
1943
- }
1944
- return [...grouped.values()].map(
1945
- (group) => resolveConstraintTargetState(fieldName, fieldType, group.path, group.constraints, typeRegistry)
1946
- );
1947
- }
1948
- function addContradiction(diagnostics, message, primary, related) {
1949
- diagnostics.push({
1950
- code: "CONTRADICTING_CONSTRAINTS",
1951
- message,
1952
- severity: "error",
1953
- primaryLocation: primary,
1954
- relatedLocations: [related]
1955
- });
1956
- }
1957
- function addTypeMismatch(diagnostics, message, primary) {
1958
- diagnostics.push({
1959
- code: "TYPE_MISMATCH",
1960
- message,
1961
- severity: "error",
1962
- primaryLocation: primary,
1963
- relatedLocations: []
1964
- });
1965
- }
1966
- function addUnknownExtension(diagnostics, message, primary) {
1967
- diagnostics.push({
1968
- code: "UNKNOWN_EXTENSION",
1969
- message,
1970
- severity: "warning",
1971
- primaryLocation: primary,
1972
- relatedLocations: []
1973
- });
1974
- }
1975
- function addUnknownPathTarget(diagnostics, message, primary) {
1976
- diagnostics.push({
1977
- code: "UNKNOWN_PATH_TARGET",
1978
- message,
1979
- severity: "error",
1980
- primaryLocation: primary,
1981
- relatedLocations: []
1982
- });
1983
- }
1984
- function addConstraintBroadening(diagnostics, message, primary, related) {
1985
- diagnostics.push({
1986
- code: "CONSTRAINT_BROADENING",
1987
- message,
1988
- severity: "error",
1989
- primaryLocation: primary,
1990
- relatedLocations: [related]
1991
- });
1992
- }
1993
- function getExtensionIdFromConstraintId(constraintId) {
1994
- const separator = constraintId.lastIndexOf("/");
1995
- if (separator <= 0) {
1996
- return null;
1997
- }
1998
- return constraintId.slice(0, separator);
1999
- }
2000
- function typeLabel(type) {
2001
- switch (type.kind) {
2002
- case "primitive":
2003
- return type.primitiveKind;
2004
- case "enum":
2005
- return "enum";
2006
- case "array":
2007
- return "array";
2008
- case "object":
2009
- return "object";
2010
- case "record":
2011
- return "record";
2012
- case "union":
2013
- return "union";
2014
- case "reference":
2015
- return `reference(${type.name})`;
2016
- case "dynamic":
2017
- return `dynamic(${type.dynamicKind})`;
2018
- case "custom":
2019
- return `custom(${type.typeId})`;
2020
- default: {
2021
- const exhaustive = type;
2022
- return String(exhaustive);
2023
- }
2024
- }
2025
- }
2026
- function isJsonObject(value) {
2027
- return typeof value === "object" && value !== null && !Array.isArray(value);
2028
- }
2029
- function isJsonArray(value) {
2030
- return Array.isArray(value);
2031
- }
2032
- function jsonValueEquals(left, right) {
2033
- if (left === right) {
2034
- return true;
2035
- }
2036
- if (isJsonArray(left) || isJsonArray(right)) {
2037
- if (!isJsonArray(left) || !isJsonArray(right) || left.length !== right.length) {
2038
- return false;
2039
- }
2040
- for (const [index, item] of left.entries()) {
2041
- const rightItem = right[index];
2042
- if (rightItem === void 0 || !jsonValueEquals(item, rightItem)) {
2043
- return false;
2044
- }
2045
- }
2046
- return true;
2047
- }
2048
- if (isJsonObject(left) || isJsonObject(right)) {
2049
- if (!isJsonObject(left) || !isJsonObject(right)) {
2050
- return false;
2051
- }
2052
- const leftKeys = Object.keys(left).sort();
2053
- const rightKeys = Object.keys(right).sort();
2054
- if (leftKeys.length !== rightKeys.length) {
2055
- return false;
2056
- }
2057
- return leftKeys.every((key, index) => {
2058
- const rightKey = rightKeys[index];
2059
- if (rightKey !== key) {
2060
- return false;
2061
- }
2062
- const leftValue = left[key];
2063
- const rightValue = right[rightKey];
2064
- return leftValue !== void 0 && rightValue !== void 0 && jsonValueEquals(leftValue, rightValue);
2065
- });
2066
- }
2067
- return false;
2068
- }
2069
- function findNumeric(constraints, constraintKind) {
2070
- return constraints.find(
2071
- (constraint) => constraint.constraintKind === constraintKind
2072
- );
2073
- }
2074
- function findLength(constraints, constraintKind) {
2075
- return constraints.find(
2076
- (constraint) => constraint.constraintKind === constraintKind
2077
- );
2078
- }
2079
- function findAllowedMembers(constraints) {
2080
- return constraints.filter(
2081
- (constraint) => constraint.constraintKind === "allowedMembers"
2082
- );
2083
- }
2084
- function findConstConstraints(constraints) {
2085
- return constraints.filter(
2086
- (constraint) => constraint.constraintKind === "const"
2087
- );
2088
- }
2089
- function isOrderedBoundConstraint(constraint) {
2090
- return constraint.constraintKind === "minimum" || constraint.constraintKind === "exclusiveMinimum" || constraint.constraintKind === "minLength" || constraint.constraintKind === "minItems" || constraint.constraintKind === "maximum" || constraint.constraintKind === "exclusiveMaximum" || constraint.constraintKind === "maxLength" || constraint.constraintKind === "maxItems";
2091
- }
2092
- function constraintPathKey(constraint) {
2093
- return constraint.path?.segments.join(".") ?? "";
2094
- }
2095
- function orderedBoundFamily(kind) {
2096
- switch (kind) {
2097
- case "minimum":
2098
- case "exclusiveMinimum":
2099
- return "numeric-lower";
2100
- case "maximum":
2101
- case "exclusiveMaximum":
2102
- return "numeric-upper";
2103
- case "minLength":
2104
- return "minLength";
2105
- case "minItems":
2106
- return "minItems";
2107
- case "maxLength":
2108
- return "maxLength";
2109
- case "maxItems":
2110
- return "maxItems";
2111
- default: {
2112
- const exhaustive = kind;
2113
- return exhaustive;
2114
- }
2115
- }
2116
- }
2117
- function isNumericLowerKind(kind) {
2118
- return kind === "minimum" || kind === "exclusiveMinimum";
2119
- }
2120
- function isNumericUpperKind(kind) {
2121
- return kind === "maximum" || kind === "exclusiveMaximum";
2122
- }
2123
- function describeConstraintTag(constraint) {
2124
- return `@${constraint.constraintKind}`;
2125
- }
2126
- function compareConstraintStrength(current, previous) {
2127
- const family = orderedBoundFamily(current.constraintKind);
2128
- if (family === "numeric-lower") {
2129
- if (!isNumericLowerKind(current.constraintKind) || !isNumericLowerKind(previous.constraintKind)) {
2130
- throw new Error("numeric-lower family received non-numeric lower-bound constraint");
2131
- }
2132
- if (current.value !== previous.value) {
2133
- return current.value > previous.value ? 1 : -1;
2134
- }
2135
- if (current.constraintKind === "exclusiveMinimum" && previous.constraintKind === "minimum") {
2136
- return 1;
2137
- }
2138
- if (current.constraintKind === "minimum" && previous.constraintKind === "exclusiveMinimum") {
2139
- return -1;
2140
- }
2141
- return 0;
2142
- }
2143
- if (family === "numeric-upper") {
2144
- if (!isNumericUpperKind(current.constraintKind) || !isNumericUpperKind(previous.constraintKind)) {
2145
- throw new Error("numeric-upper family received non-numeric upper-bound constraint");
2146
- }
2147
- if (current.value !== previous.value) {
2148
- return current.value < previous.value ? 1 : -1;
2149
- }
2150
- if (current.constraintKind === "exclusiveMaximum" && previous.constraintKind === "maximum") {
2151
- return 1;
2152
- }
2153
- if (current.constraintKind === "maximum" && previous.constraintKind === "exclusiveMaximum") {
2154
- return -1;
2155
- }
2156
- return 0;
2157
- }
2158
- switch (family) {
2159
- case "minLength":
2160
- case "minItems":
2161
- if (current.value === previous.value) {
2162
- return 0;
2163
- }
2164
- return current.value > previous.value ? 1 : -1;
2165
- case "maxLength":
2166
- case "maxItems":
2167
- if (current.value === previous.value) {
2168
- return 0;
2169
- }
2170
- return current.value < previous.value ? 1 : -1;
2171
- default: {
2172
- const exhaustive = family;
2173
- return exhaustive;
2174
- }
2175
- }
2176
- }
2177
- function compareSemanticInclusivity(currentInclusive, previousInclusive) {
2178
- if (currentInclusive === previousInclusive) {
2179
- return 0;
2180
- }
2181
- return currentInclusive ? -1 : 1;
2182
- }
2183
- function compareCustomConstraintStrength(current, previous) {
2184
- const order = current.comparePayloads(current.constraint.payload, previous.constraint.payload);
2185
- const equalPayloadTiebreaker = order === 0 ? compareSemanticInclusivity(current.role.inclusive, previous.role.inclusive) : order;
2186
- switch (current.role.bound) {
2187
- case "lower":
2188
- return equalPayloadTiebreaker;
2189
- case "upper":
2190
- return equalPayloadTiebreaker === 0 ? 0 : -equalPayloadTiebreaker;
2191
- case "exact":
2192
- return order === 0 ? 0 : Number.NaN;
2193
- default: {
2194
- const exhaustive = current.role.bound;
2195
- return exhaustive;
2196
- }
2197
- }
2198
- }
2199
- function customConstraintsContradict(lower, upper) {
2200
- const order = lower.comparePayloads(lower.constraint.payload, upper.constraint.payload);
2201
- if (order > 0) {
2202
- return true;
2203
- }
2204
- if (order < 0) {
2205
- return false;
2206
- }
2207
- return !lower.role.inclusive || !upper.role.inclusive;
2208
- }
2209
- function describeCustomConstraintTag(constraint) {
2210
- return constraint.provenance.tagName ?? constraint.constraintId;
2211
- }
2212
- function isNullType(type) {
2213
- return type.kind === "primitive" && type.primitiveKind === "null";
2214
- }
2215
- function collectCustomConstraintCandidateTypes(type, typeRegistry) {
2216
- const effectiveType = dereferenceAnalysisType(type, typeRegistry);
2217
- const candidates = [effectiveType];
2218
- if (effectiveType.kind === "array") {
2219
- candidates.push(...collectCustomConstraintCandidateTypes(effectiveType.items, typeRegistry));
2220
- }
2221
- if (effectiveType.kind === "union") {
2222
- const memberTypes = effectiveType.members.map(
2223
- (member) => dereferenceAnalysisType(member, typeRegistry)
2224
- );
2225
- const nonNullMembers = memberTypes.filter((member) => !isNullType(member));
2226
- if (nonNullMembers.length === 1 && nonNullMembers.length < memberTypes.length) {
2227
- const [nullableMember] = nonNullMembers;
2228
- if (nullableMember !== void 0) {
2229
- candidates.push(...collectCustomConstraintCandidateTypes(nullableMember, typeRegistry));
2230
- }
2231
- }
2232
- }
2233
- return candidates;
2234
- }
2235
- function checkNumericContradictions(diagnostics, fieldName, constraints) {
2236
- const min = findNumeric(constraints, "minimum");
2237
- const max = findNumeric(constraints, "maximum");
2238
- const exMin = findNumeric(constraints, "exclusiveMinimum");
2239
- const exMax = findNumeric(constraints, "exclusiveMaximum");
2240
- if (min !== void 0 && max !== void 0 && min.value > max.value) {
2241
- addContradiction(
2242
- diagnostics,
2243
- `Field "${fieldName}": minimum (${String(min.value)}) is greater than maximum (${String(max.value)})`,
2244
- min.provenance,
2245
- max.provenance
2246
- );
2247
- }
2248
- if (exMin !== void 0 && max !== void 0 && exMin.value >= max.value) {
2249
- addContradiction(
2250
- diagnostics,
2251
- `Field "${fieldName}": exclusiveMinimum (${String(exMin.value)}) is greater than or equal to maximum (${String(max.value)})`,
2252
- exMin.provenance,
2253
- max.provenance
2254
- );
2255
- }
2256
- if (min !== void 0 && exMax !== void 0 && min.value >= exMax.value) {
2257
- addContradiction(
2258
- diagnostics,
2259
- `Field "${fieldName}": minimum (${String(min.value)}) is greater than or equal to exclusiveMaximum (${String(exMax.value)})`,
2260
- min.provenance,
2261
- exMax.provenance
2262
- );
2263
- }
2264
- if (exMin !== void 0 && exMax !== void 0 && exMin.value >= exMax.value) {
2265
- addContradiction(
2266
- diagnostics,
2267
- `Field "${fieldName}": exclusiveMinimum (${String(exMin.value)}) is greater than or equal to exclusiveMaximum (${String(exMax.value)})`,
2268
- exMin.provenance,
2269
- exMax.provenance
2270
- );
2271
- }
2272
- }
2273
- function checkLengthContradictions(diagnostics, fieldName, constraints) {
2274
- const minLen = findLength(constraints, "minLength");
2275
- const maxLen = findLength(constraints, "maxLength");
2276
- if (minLen !== void 0 && maxLen !== void 0 && minLen.value > maxLen.value) {
2277
- addContradiction(
2278
- diagnostics,
2279
- `Field "${fieldName}": minLength (${String(minLen.value)}) is greater than maxLength (${String(maxLen.value)})`,
2280
- minLen.provenance,
2281
- maxLen.provenance
2282
- );
2283
- }
2284
- const minItems = findLength(constraints, "minItems");
2285
- const maxItems = findLength(constraints, "maxItems");
2286
- if (minItems !== void 0 && maxItems !== void 0 && minItems.value > maxItems.value) {
2287
- addContradiction(
2288
- diagnostics,
2289
- `Field "${fieldName}": minItems (${String(minItems.value)}) is greater than maxItems (${String(maxItems.value)})`,
2290
- minItems.provenance,
2291
- maxItems.provenance
2292
- );
2293
- }
2294
- }
2295
- function checkAllowedMembersContradiction(diagnostics, fieldName, constraints) {
2296
- const members = findAllowedMembers(constraints);
2297
- if (members.length < 2) {
2298
- return;
2299
- }
2300
- const firstSet = new Set(members[0]?.members ?? []);
2301
- for (let index = 1; index < members.length; index += 1) {
2302
- const current = members[index];
2303
- if (current === void 0) {
2304
- continue;
2305
- }
2306
- for (const member of firstSet) {
2307
- if (!current.members.includes(member)) {
2308
- firstSet.delete(member);
2309
- }
2310
- }
2311
- }
2312
- if (firstSet.size === 0) {
2313
- const first = members[0];
2314
- const second = members[1];
2315
- if (first !== void 0 && second !== void 0) {
2316
- addContradiction(
2317
- diagnostics,
2318
- `Field "${fieldName}": allowedMembers constraints have an empty intersection (no valid values remain)`,
2319
- first.provenance,
2320
- second.provenance
2321
- );
2322
- }
2323
- }
2324
- }
2325
- function checkConstContradictions(diagnostics, fieldName, constraints) {
2326
- const constConstraints = findConstConstraints(constraints);
2327
- if (constConstraints.length < 2) {
2328
- return;
2329
- }
2330
- const first = constConstraints[0];
2331
- if (first === void 0) {
2332
- return;
2333
- }
2334
- for (let index = 1; index < constConstraints.length; index += 1) {
2335
- const current = constConstraints[index];
2336
- if (current === void 0 || jsonValueEquals(first.value, current.value)) {
2337
- continue;
2338
- }
2339
- addContradiction(
2340
- diagnostics,
2341
- `Field "${fieldName}": conflicting @const constraints require both ${JSON.stringify(first.value)} and ${JSON.stringify(current.value)}`,
2342
- first.provenance,
2343
- current.provenance
2344
- );
2345
- }
2346
- }
2347
- function checkConstraintBroadening(diagnostics, fieldName, constraints) {
2348
- const strongestByKey = /* @__PURE__ */ new Map();
2349
- for (const constraint of constraints) {
2350
- if (!isOrderedBoundConstraint(constraint)) {
2351
- continue;
2352
- }
2353
- const key = `${orderedBoundFamily(constraint.constraintKind)}:${constraintPathKey(constraint)}`;
2354
- const previous = strongestByKey.get(key);
2355
- if (previous === void 0) {
2356
- strongestByKey.set(key, constraint);
2357
- continue;
2358
- }
2359
- const strength = compareConstraintStrength(constraint, previous);
2360
- if (strength < 0) {
2361
- addConstraintBroadening(
2362
- diagnostics,
2363
- `Field "${fieldName}": ${describeConstraintTag(constraint)} (${String(constraint.value)}) is broader than earlier ${describeConstraintTag(previous)} (${String(previous.value)}). Constraints can only narrow.`,
2364
- constraint.provenance,
2365
- previous.provenance
2366
- );
2367
- continue;
2368
- }
2369
- if (strength > 0) {
2370
- strongestByKey.set(key, constraint);
2371
- }
2372
- }
2373
- }
2374
- function checkCustomConstraintSemantics(diagnostics, fieldName, constraints, extensionRegistry) {
2375
- if (extensionRegistry === void 0) {
2376
- return;
2377
- }
2378
- const strongestByKey = /* @__PURE__ */ new Map();
2379
- const lowerByFamily = /* @__PURE__ */ new Map();
2380
- const upperByFamily = /* @__PURE__ */ new Map();
2381
- for (const constraint of constraints) {
2382
- if (constraint.constraintKind !== "custom") {
2383
- continue;
2384
- }
2385
- const registration = extensionRegistry.findConstraint(constraint.constraintId);
2386
- if (registration?.comparePayloads === void 0 || registration.semanticRole === void 0) {
2387
- continue;
2388
- }
2389
- const entry = {
2390
- constraint,
2391
- comparePayloads: registration.comparePayloads,
2392
- role: registration.semanticRole
2393
- };
2394
- const familyKey = `${registration.semanticRole.family}:${constraintPathKey(constraint)}`;
2395
- const boundKey = `${familyKey}:${registration.semanticRole.bound}`;
2396
- const previous = strongestByKey.get(boundKey);
2397
- if (previous !== void 0) {
2398
- const strength = compareCustomConstraintStrength(entry, previous);
2399
- if (Number.isNaN(strength)) {
2400
- addContradiction(
2401
- diagnostics,
2402
- `Field "${fieldName}": ${describeCustomConstraintTag(constraint)} conflicts with ${describeCustomConstraintTag(previous.constraint)}`,
2403
- constraint.provenance,
2404
- previous.constraint.provenance
2405
- );
2406
- continue;
2407
- }
2408
- if (strength < 0) {
2409
- addConstraintBroadening(
2410
- diagnostics,
2411
- `Field "${fieldName}": ${describeCustomConstraintTag(constraint)} is broader than earlier ${describeCustomConstraintTag(previous.constraint)}. Constraints can only narrow.`,
2412
- constraint.provenance,
2413
- previous.constraint.provenance
2414
- );
2415
- continue;
2416
- }
2417
- if (strength > 0) {
2418
- strongestByKey.set(boundKey, entry);
2419
- }
2420
- } else {
2421
- strongestByKey.set(boundKey, entry);
2422
- }
2423
- if (registration.semanticRole.bound === "lower") {
2424
- lowerByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
2425
- } else if (registration.semanticRole.bound === "upper") {
2426
- upperByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
2427
- }
2428
- }
2429
- for (const [familyKey, lower] of lowerByFamily) {
2430
- const upper = upperByFamily.get(familyKey);
2431
- if (upper === void 0 || !customConstraintsContradict(lower, upper)) {
2432
- continue;
2433
- }
2434
- addContradiction(
2435
- diagnostics,
2436
- `Field "${fieldName}": ${describeCustomConstraintTag(lower.constraint)} contradicts ${describeCustomConstraintTag(upper.constraint)}`,
2437
- lower.constraint.provenance,
2438
- upper.constraint.provenance
2439
- );
2440
- }
2441
- }
2442
- function checkCustomConstraint(diagnostics, fieldName, type, constraint, typeRegistry, extensionRegistry) {
2443
- if (extensionRegistry === void 0) {
2444
- return;
2445
- }
2446
- const registration = extensionRegistry.findConstraint(constraint.constraintId);
2447
- if (registration === void 0) {
2448
- addUnknownExtension(
2449
- diagnostics,
2450
- `Field "${fieldName}": custom constraint "${constraint.constraintId}" is not registered in the extension registry`,
2451
- constraint.provenance
2452
- );
2453
- return;
2454
- }
2455
- const candidateTypes = collectCustomConstraintCandidateTypes(type, typeRegistry);
2456
- const normalizedTagName = constraint.provenance.tagName === void 0 ? void 0 : (0, import_core3.normalizeConstraintTagName)(constraint.provenance.tagName.replace(/^@/, ""));
2457
- if (normalizedTagName !== void 0) {
2458
- const tagRegistration = extensionRegistry.findConstraintTag(normalizedTagName);
2459
- const extensionId = getExtensionIdFromConstraintId(constraint.constraintId);
2460
- if (extensionId !== null && tagRegistration?.extensionId === extensionId && tagRegistration.registration.constraintName === registration.constraintName && !candidateTypes.some(
2461
- (candidateType) => tagRegistration.registration.isApplicableToType?.(candidateType) !== false
2462
- )) {
2463
- addTypeMismatch(
2464
- diagnostics,
2465
- `Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
2466
- constraint.provenance
2467
- );
2468
- return;
2469
- }
2470
- }
2471
- if (registration.applicableTypes === null) {
2472
- if (!candidateTypes.some(
2473
- (candidateType) => registration.isApplicableToType?.(candidateType) !== false
2474
- )) {
2475
- addTypeMismatch(
2476
- diagnostics,
2477
- `Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
2478
- constraint.provenance
2479
- );
2480
- }
2481
- return;
2482
- }
2483
- const applicableTypes = registration.applicableTypes;
2484
- const matchesApplicableType = candidateTypes.some(
2485
- (candidateType) => applicableTypes.includes(candidateType.kind) && registration.isApplicableToType?.(candidateType) !== false
2486
- );
2487
- if (!matchesApplicableType) {
2488
- addTypeMismatch(
2489
- diagnostics,
2490
- `Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
2491
- constraint.provenance
2492
- );
2493
- }
2494
- }
2495
- function checkConstraintOnType(diagnostics, fieldName, type, constraint, typeRegistry, extensionRegistry) {
2496
- const effectiveType = dereferenceAnalysisType(type, typeRegistry);
2497
- const isNumber = effectiveType.kind === "primitive" && ["number", "integer", "bigint"].includes(effectiveType.primitiveKind);
2498
- const isString = effectiveType.kind === "primitive" && effectiveType.primitiveKind === "string";
2499
- const isArray = effectiveType.kind === "array";
2500
- const isEnum = effectiveType.kind === "enum";
2501
- const arrayItemType = effectiveType.kind === "array" ? dereferenceAnalysisType(effectiveType.items, typeRegistry) : void 0;
2502
- const isStringArray2 = arrayItemType?.kind === "primitive" && arrayItemType.primitiveKind === "string";
2503
- const label = typeLabel(effectiveType);
2504
- switch (constraint.constraintKind) {
2505
- case "minimum":
2506
- case "maximum":
2507
- case "exclusiveMinimum":
2508
- case "exclusiveMaximum":
2509
- case "multipleOf":
2510
- if (!isNumber) {
2511
- addTypeMismatch(
2512
- diagnostics,
2513
- `Field "${fieldName}": constraint "${constraint.constraintKind}" is only valid on number fields, but field type is "${label}"`,
2514
- constraint.provenance
2515
- );
2516
- }
2517
- break;
2518
- case "minLength":
2519
- case "maxLength":
2520
- case "pattern":
2521
- if (!isString && !isStringArray2) {
2522
- addTypeMismatch(
2523
- diagnostics,
2524
- `Field "${fieldName}": constraint "${constraint.constraintKind}" is only valid on string fields or string array items, but field type is "${label}"`,
2525
- constraint.provenance
2526
- );
2527
- }
2528
- break;
2529
- case "minItems":
2530
- case "maxItems":
2531
- case "uniqueItems":
2532
- if (!isArray) {
2533
- addTypeMismatch(
2534
- diagnostics,
2535
- `Field "${fieldName}": constraint "${constraint.constraintKind}" is only valid on array fields, but field type is "${label}"`,
2536
- constraint.provenance
2537
- );
2538
- }
2539
- break;
2540
- case "allowedMembers":
2541
- if (!isEnum) {
2542
- addTypeMismatch(
2543
- diagnostics,
2544
- `Field "${fieldName}": constraint "allowedMembers" is only valid on enum fields, but field type is "${label}"`,
2545
- constraint.provenance
2546
- );
2547
- }
2548
- break;
2549
- case "const": {
2550
- const isPrimitiveConstType = effectiveType.kind === "primitive" && ["string", "number", "integer", "bigint", "boolean", "null"].includes(
2551
- effectiveType.primitiveKind
2552
- ) || effectiveType.kind === "enum";
2553
- if (!isPrimitiveConstType) {
2554
- addTypeMismatch(
2555
- diagnostics,
2556
- `Field "${fieldName}": constraint "const" is only valid on primitive or enum fields, but field type is "${label}"`,
2557
- constraint.provenance
2558
- );
2559
- break;
2560
- }
2561
- if (effectiveType.kind === "primitive") {
2562
- const valueType = constraint.value === null ? "null" : Array.isArray(constraint.value) ? "array" : typeof constraint.value;
2563
- const expectedValueType = effectiveType.primitiveKind === "integer" || effectiveType.primitiveKind === "bigint" ? "number" : effectiveType.primitiveKind;
2564
- if (valueType !== expectedValueType) {
2565
- addTypeMismatch(
2566
- diagnostics,
2567
- `Field "${fieldName}": @const value type "${valueType}" is incompatible with field type "${effectiveType.primitiveKind}"`,
2568
- constraint.provenance
2569
- );
2570
- }
2571
- break;
2572
- }
2573
- const memberValues = effectiveType.members.map((member) => member.value);
2574
- if (!memberValues.some((member) => jsonValueEquals(member, constraint.value))) {
2575
- addTypeMismatch(
2576
- diagnostics,
2577
- `Field "${fieldName}": @const value ${JSON.stringify(constraint.value)} is not one of the enum members`,
2578
- constraint.provenance
2579
- );
2580
- }
2581
- break;
2582
- }
2583
- case "custom":
2584
- checkCustomConstraint(
2585
- diagnostics,
2586
- fieldName,
2587
- effectiveType,
2588
- constraint,
2589
- typeRegistry,
2590
- extensionRegistry
2591
- );
2592
- break;
2593
- default: {
2594
- const exhaustive = constraint;
2595
- throw new Error(`Unhandled constraint: ${JSON.stringify(exhaustive)}`);
2596
- }
2597
- }
2598
- }
2599
- function analyzeResolvedTargetState(diagnostics, state, typeRegistry, extensionRegistry) {
2600
- checkNumericContradictions(diagnostics, state.targetName, state.effectiveConstraints);
2601
- checkLengthContradictions(diagnostics, state.targetName, state.effectiveConstraints);
2602
- checkAllowedMembersContradiction(diagnostics, state.targetName, state.effectiveConstraints);
2603
- checkConstContradictions(diagnostics, state.targetName, state.effectiveConstraints);
2604
- checkConstraintBroadening(diagnostics, state.targetName, state.effectiveConstraints);
2605
- checkCustomConstraintSemantics(
2606
- diagnostics,
2607
- state.targetName,
2608
- state.effectiveConstraints,
2609
- extensionRegistry
2610
- );
2611
- for (const constraint of state.effectiveConstraints) {
2612
- checkConstraintOnType(
2613
- diagnostics,
2614
- state.targetName,
2615
- state.type,
2616
- constraint,
2617
- typeRegistry,
2618
- extensionRegistry
2619
- );
2620
- }
2621
- }
2622
- function analyzeConstraintTargets(fieldName, fieldType, constraints, typeRegistry, options) {
2623
- const diagnostics = [];
2624
- const targetStates = buildConstraintTargetStates(fieldName, fieldType, constraints, typeRegistry);
2625
- for (const targetState of targetStates) {
2626
- switch (targetState.kind) {
2627
- case "resolved":
2628
- analyzeResolvedTargetState(
2629
- diagnostics,
2630
- targetState,
2631
- typeRegistry,
2632
- options?.extensionRegistry
2633
- );
2634
- break;
2635
- case "missing-property":
2636
- for (const constraint of targetState.localConstraints) {
2637
- addUnknownPathTarget(
2638
- diagnostics,
2639
- `Field "${targetState.targetName}": path-targeted constraint "${constraint.constraintKind}" references unknown path segment "${targetState.segment}"`,
2640
- constraint.provenance
2641
- );
2642
- }
2643
- break;
2644
- case "unresolvable":
2645
- for (const constraint of targetState.localConstraints) {
2646
- addTypeMismatch(
2647
- diagnostics,
2648
- `Field "${targetState.targetName}": path-targeted constraint "${constraint.constraintKind}" is invalid because type "${typeLabel(targetState.type)}" cannot be traversed`,
2649
- constraint.provenance
2650
- );
2651
- }
2652
- break;
2653
- default: {
2654
- const exhaustive = targetState;
2655
- throw new Error(`Unhandled target state: ${String(exhaustive)}`);
2656
- }
2657
- }
2658
- }
2659
- return {
2660
- diagnostics,
2661
- targetStates
2662
- };
2663
- }
2664
-
2665
- // src/file-snapshots.ts
2666
- var ts4 = __toESM(require("typescript"), 1);
2667
-
2668
- // src/compiler-signatures.ts
2669
- var ts2 = __toESM(require("typescript"), 1);
2670
- var PRELUDE_LINES = [
2671
- "type FormSpecPlacement =",
2672
- ' | "class"',
2673
- ' | "class-field"',
2674
- ' | "class-method"',
2675
- ' | "interface"',
2676
- ' | "interface-field"',
2677
- ' | "type-alias"',
2678
- ' | "type-alias-field"',
2679
- ' | "variable"',
2680
- ' | "function"',
2681
- ' | "function-parameter"',
2682
- ' | "method-parameter";',
2683
- "",
2684
- "type FormSpecCapability =",
2685
- ' | "numeric-comparable"',
2686
- ' | "string-like"',
2687
- ' | "array-like"',
2688
- ' | "enum-member-addressable"',
2689
- ' | "json-like"',
2690
- ' | "condition-like"',
2691
- ' | "object-like";',
2692
- "",
2693
- "interface TagContext<P extends FormSpecPlacement, Host, Subject> {",
2694
- " readonly placement: P;",
2695
- " readonly hostType: Host;",
2696
- " readonly subjectType: Subject;",
2697
- "}",
2698
- "",
2699
- "type NonNullish<T> = Exclude<T, null | undefined>;",
2700
- "",
2701
- "type ProvidesCapability<T, Capability extends FormSpecCapability> =",
2702
- ' Capability extends "numeric-comparable"',
2703
- " ? NonNullish<T> extends number | bigint",
2704
- " ? true",
2705
- " : false",
2706
- ' : Capability extends "string-like"',
2707
- " ? NonNullish<T> extends string",
2708
- " ? true",
2709
- " : false",
2710
- ' : Capability extends "array-like"',
2711
- " ? NonNullish<T> extends readonly unknown[]",
2712
- " ? true",
2713
- " : false",
2714
- ' : Capability extends "enum-member-addressable"',
2715
- " ? NonNullish<T> extends string",
2716
- " ? true",
2717
- " : false",
2718
- ' : Capability extends "json-like"',
2719
- " ? true",
2720
- ' : Capability extends "condition-like"',
2721
- " ? true",
2722
- ' : Capability extends "object-like"',
2723
- " ? NonNullish<T> extends readonly unknown[]",
2724
- " ? false",
2725
- " : NonNullish<T> extends object",
2726
- " ? true",
2727
- " : false",
2728
- " : false;",
2729
- "",
2730
- "type NestedPathOfCapability<Subject, Capability extends FormSpecCapability> =",
2731
- " NonNullish<Subject> extends readonly (infer Item)[]",
2732
- " ? NestedPathOfCapability<Item, Capability>",
2733
- " : NonNullish<Subject> extends object",
2734
- " ? {",
2735
- " [Key in Extract<keyof NonNullish<Subject>, string>]:",
2736
- " | (ProvidesCapability<NonNullish<Subject>[Key], Capability> extends true ? Key : never)",
2737
- " | (NestedPathOfCapability<NonNullish<Subject>[Key], Capability> extends never",
2738
- " ? never",
2739
- " : `${Key}.${NestedPathOfCapability<NonNullish<Subject>[Key], Capability>}`);",
2740
- " }[Extract<keyof NonNullish<Subject>, string>]",
2741
- " : never;",
2742
- "",
2743
- "type PathOfCapability<Subject, Capability extends FormSpecCapability> =",
2744
- " NestedPathOfCapability<Subject, Capability>;",
2745
- "",
2746
- "type MemberTarget<Subject> = Extract<keyof NonNullish<Subject>, string>;",
2747
- "",
2748
- 'type VariantTarget<Subject> = "singular" | "plural";',
2749
- "",
2750
- "type FormSpecCondition = unknown;",
2751
- "type JsonValue = unknown;",
2752
- "",
2753
- "declare function __ctx<P extends FormSpecPlacement, Host, Subject>(): TagContext<P, Host, Subject>;",
2754
- "declare function __path<Subject, Capability extends FormSpecCapability>(",
2755
- " path: PathOfCapability<Subject, Capability>",
2756
- "): PathOfCapability<Subject, Capability>;",
2757
- "declare function __member<Subject>(member: MemberTarget<Subject>): MemberTarget<Subject>;",
2758
- "declare function __variant<Subject>(variant: VariantTarget<Subject>): VariantTarget<Subject>;"
2759
- ];
2760
- function placementUnion(placements) {
2761
- return placements.map((placement) => JSON.stringify(placement)).join(" | ");
2762
- }
2763
- function renderValueType(valueKind) {
2764
- switch (valueKind) {
2765
- case "number":
2766
- case "integer":
2767
- case "signedInteger":
2768
- return "number";
2769
- case "string":
2770
- return "string";
2771
- case "json":
2772
- return "JsonValue";
2773
- case "boolean":
2774
- return "boolean";
2775
- case "condition":
2776
- return "FormSpecCondition";
2777
- case void 0:
2778
- return "unknown";
2779
- default: {
2780
- const exhaustive = valueKind;
2781
- return exhaustive;
2782
- }
2783
- }
2784
- }
2785
- function renderTargetParameterType(parameter) {
2786
- switch (parameter.kind) {
2787
- case "target-path":
2788
- return parameter.capability === void 0 ? "PathOfCapability<Subject, FormSpecCapability>" : `PathOfCapability<Subject, ${JSON.stringify(parameter.capability)}>`;
2789
- case "target-member":
2790
- return "MemberTarget<Subject>";
2791
- case "target-variant":
2792
- return "VariantTarget<Subject>";
2793
- case "value":
2794
- return renderValueType(parameter.valueKind);
2795
- default: {
2796
- const exhaustive = parameter.kind;
2797
- return exhaustive;
2798
- }
2799
- }
2800
- }
2801
- function renderSignature(tagName, signature) {
2802
- const parameters = signature.parameters.map((parameter, index) => {
2803
- const name = parameter.kind === "value" ? "value" : `target${String(index)}`;
2804
- return `${name}: ${renderTargetParameterType(parameter)}`;
2805
- });
2806
- return [
2807
- ` function ${getSyntheticTagHelperName(tagName)}<Host, Subject>(`,
2808
- ` ctx: TagContext<${placementUnion(signature.placements)}, Host, Subject>${parameters.length > 0 ? "," : ""}`,
2809
- ...parameters.map(
2810
- (parameter, index) => ` ${parameter}${index === parameters.length - 1 ? "" : ","}`
2811
- ),
2812
- " ): void;"
2813
- ].join("\n");
2814
- }
2815
- function getSyntheticTagHelperName(tagName) {
2816
- return `tag_${tagName}`;
2817
- }
2818
- function targetKindForParameter(parameter) {
2819
- switch (parameter.kind) {
2820
- case "target-path":
2821
- return "path";
2822
- case "target-member":
2823
- return "member";
2824
- case "target-variant":
2825
- return "variant";
2826
- case "value":
2827
- return null;
2828
- default: {
2829
- const exhaustive = parameter.kind;
2830
- return exhaustive;
2831
- }
2832
- }
2833
- }
2834
- function getSignatureTargetKind(signature) {
2835
- for (const parameter of signature.parameters) {
2836
- const targetKind = targetKindForParameter(parameter);
2837
- if (targetKind !== null) {
2838
- return targetKind;
2839
- }
2840
- }
2841
- return null;
2842
- }
2843
- function getTargetParameter(signature) {
2844
- return signature.parameters.find(
2845
- (parameter) => parameter.kind !== "value"
2846
- ) ?? null;
2847
- }
2848
- function getPathTargetCapability(signature) {
2849
- const parameter = getTargetParameter(signature);
2850
- if (parameter?.kind !== "target-path") {
2851
- throw new Error(`Invariant violation: expected a path-target synthetic signature`);
2852
- }
2853
- if (parameter.capability === void 0) {
2854
- throw new Error(
2855
- `Invariant violation: path-target synthetic signatures must declare a capability`
2856
- );
2857
- }
2858
- return JSON.stringify(parameter.capability);
2859
- }
2860
- function renderTargetArgument(target, signature, subjectType) {
2861
- switch (target.kind) {
2862
- case "path":
2863
- return `__path<${subjectType}, ${getPathTargetCapability(signature)}>(${JSON.stringify(
2864
- target.text
2865
- )})`;
2866
- case "member":
2867
- return `__member<${subjectType}>(${JSON.stringify(target.text)})`;
2868
- case "variant":
2869
- return `__variant<${subjectType}>(${JSON.stringify(target.text)})`;
2870
- }
2871
- }
2872
- function getMatchingTagSignatures(definition, placement, targetKind) {
2873
- return definition.signatures.filter(
2874
- (signature) => signature.placements.includes(placement) && getSignatureTargetKind(signature) === targetKind
2875
- );
2876
- }
2877
- function buildSyntheticHelperPrelude(extensions) {
2878
- const lines = [...PRELUDE_LINES, "", "declare namespace __formspec {"];
2879
- for (const definition of getAllTagDefinitions(extensions)) {
2880
- for (const signature of definition.signatures) {
2881
- lines.push(renderSignature(definition.canonicalName, signature));
2882
- }
2883
- }
2884
- lines.push("}");
2885
- return lines.join("\n");
2886
- }
2887
- function lowerTagApplicationToSyntheticCall(options) {
2888
- const definition = getTagDefinition(options.tagName, options.extensions);
2889
- if (definition === null) {
2890
- throw new Error(`Unknown FormSpec tag: ${options.tagName}`);
2891
- }
2892
- const targetKind = options.target?.kind ?? null;
2893
- const matchingSignatures = getMatchingTagSignatures(definition, options.placement, targetKind);
2894
- if (matchingSignatures.length === 0) {
2895
- throw new Error(
2896
- `No synthetic signature for @${definition.canonicalName} on placement "${options.placement}"` + (targetKind === null ? "" : ` with target kind "${targetKind}"`)
2897
- );
2898
- }
2899
- const args = [
2900
- `__ctx<${JSON.stringify(options.placement)}, ${options.hostType}, ${options.subjectType}>()`
2901
- ];
2902
- const signature = matchingSignatures[0];
2903
- if (signature === void 0) {
2904
- throw new Error(
2905
- `Invariant violation: missing synthetic signature for @${definition.canonicalName}`
2906
- );
2907
- }
2908
- if (options.target !== void 0 && options.target !== null) {
2909
- args.push(renderTargetArgument(options.target, signature, options.subjectType));
2910
- }
2911
- if (options.argumentExpression !== void 0 && options.argumentExpression !== null) {
2912
- args.push(options.argumentExpression);
2913
- }
2914
- return {
2915
- definition,
2916
- matchingSignatures,
2917
- callExpression: `__formspec.${getSyntheticTagHelperName(definition.canonicalName)}(${args.join(", ")});`
2918
- };
2919
- }
2920
- function createSyntheticCompilerHost(fileName, sourceText, compilerOptions) {
2921
- const host = ts2.createCompilerHost(compilerOptions, true);
2922
- const originalGetSourceFile = host.getSourceFile.bind(host);
2923
- host.getSourceFile = (requestedFileName, languageVersion, onError, shouldCreateNewSourceFile) => {
2924
- if (requestedFileName === fileName) {
2925
- return ts2.createSourceFile(requestedFileName, sourceText, languageVersion, true);
2926
- }
2927
- return originalGetSourceFile(
2928
- requestedFileName,
2929
- languageVersion,
2930
- onError,
2931
- shouldCreateNewSourceFile
2932
- );
2933
- };
2934
- host.readFile = (requestedFileName) => {
2935
- if (requestedFileName === fileName) {
2936
- return sourceText;
2937
- }
2938
- return ts2.sys.readFile(requestedFileName);
2939
- };
2940
- host.fileExists = (requestedFileName) => requestedFileName === fileName || ts2.sys.fileExists(requestedFileName);
2941
- host.writeFile = () => void 0;
2942
- return host;
2943
- }
2944
- function flattenDiagnosticMessage(message) {
2945
- return ts2.flattenDiagnosticMessageText(message, "\n");
2946
- }
2947
- var syntheticCheckCache = /* @__PURE__ */ new Map();
2948
- function checkSyntheticTagApplication(options) {
2949
- const lowered = lowerTagApplicationToSyntheticCall(options);
2950
- const sourceText = [
2951
- buildSyntheticHelperPrelude(options.extensions),
2952
- "",
2953
- ...options.supportingDeclarations ?? [],
2954
- "",
2955
- lowered.callExpression
2956
- ].join("\n");
2957
- const cached = syntheticCheckCache.get(sourceText);
2958
- if (cached !== void 0) {
2959
- return cached;
2960
- }
2961
- const fileName = "/virtual/formspec-synthetic.ts";
2962
- const compilerOptions = {
2963
- strict: true,
2964
- noEmit: true,
2965
- target: ts2.ScriptTarget.ES2022,
2966
- module: ts2.ModuleKind.ESNext,
2967
- lib: ["lib.es2022.d.ts"]
2968
- };
2969
- const host = createSyntheticCompilerHost(fileName, sourceText, compilerOptions);
2970
- const program = ts2.createProgram([fileName], compilerOptions, host);
2971
- const diagnostics = ts2.getPreEmitDiagnostics(program).filter((diagnostic) => diagnostic.file === void 0 || diagnostic.file.fileName === fileName).map((diagnostic) => ({
2972
- code: diagnostic.code,
2973
- message: flattenDiagnosticMessage(diagnostic.messageText)
2974
- }));
2975
- const result = {
2976
- sourceText,
2977
- diagnostics
2978
- };
2979
- syntheticCheckCache.set(sourceText, result);
2980
- return result;
2981
- }
2982
-
2983
- // src/source-bindings.ts
2984
- var ts3 = __toESM(require("typescript"), 1);
2985
- function getLastLeadingDocCommentRange(node, sourceFile) {
2986
- const ranges = ts3.getLeadingCommentRanges(sourceFile.text, node.getFullStart()) ?? [];
2987
- const docRanges = ranges.filter(
2988
- (range) => sourceFile.text.slice(range.pos, range.end).startsWith("/**")
2989
- );
2990
- return docRanges.length === 0 ? null : docRanges[docRanges.length - 1] ?? null;
2991
- }
2992
- function getSubjectType(node, checker) {
2993
- if (ts3.isClassDeclaration(node) || ts3.isInterfaceDeclaration(node) || ts3.isTypeAliasDeclaration(node) || ts3.isPropertyDeclaration(node) || ts3.isPropertySignature(node) || ts3.isMethodDeclaration(node) || ts3.isFunctionDeclaration(node) || ts3.isVariableDeclaration(node) || ts3.isParameter(node)) {
2994
- return checker.getTypeAtLocation(node);
2995
- }
2996
- return void 0;
2997
- }
2998
- function getHostType(node, checker) {
2999
- const parent = node.parent;
3000
- if (ts3.isClassDeclaration(parent) || ts3.isInterfaceDeclaration(parent) || ts3.isTypeLiteralNode(parent) || ts3.isTypeAliasDeclaration(parent)) {
3001
- return checker.getTypeAtLocation(parent);
3002
- }
3003
- return getSubjectType(node, checker);
3004
- }
3005
- function findDeclarationForCommentOffset(sourceFile, offset) {
3006
- let bestMatch = null;
3007
- const visit = (node) => {
3008
- if (resolveDeclarationPlacement(node) !== null) {
3009
- const range = getLastLeadingDocCommentRange(node, sourceFile);
3010
- if (range !== null && offset >= range.pos && offset <= range.end) {
3011
- if (bestMatch === null || node.getWidth(sourceFile) < bestMatch.getWidth(sourceFile)) {
3012
- bestMatch = node;
3013
- }
3014
- }
3015
- }
3016
- ts3.forEachChild(node, visit);
3017
- };
3018
- visit(sourceFile);
3019
- return bestMatch;
3020
- }
3021
-
3022
- // src/semantic-protocol.ts
3023
- var FORMSPEC_ANALYSIS_PROTOCOL_VERSION = 1;
3024
- var FORMSPEC_ANALYSIS_SCHEMA_VERSION = 1;
3025
- function isObjectRecord(value) {
3026
- return typeof value === "object" && value !== null && !Array.isArray(value);
3027
- }
3028
- function isCommentSpan(value) {
3029
- if (!isObjectRecord(value)) {
3030
- return false;
3031
- }
3032
- const candidate = value;
3033
- return typeof candidate.start === "number" && typeof candidate.end === "number";
3034
- }
3035
- function isStringArray(value) {
3036
- return Array.isArray(value) && value.every((entry) => typeof entry === "string");
3037
- }
3038
- function isPlacementArray(value) {
3039
- return isStringArray(value);
3040
- }
3041
- function isTargetKindArray(value) {
3042
- return isStringArray(value);
3043
- }
3044
- function isIpcEndpoint(value) {
3045
- if (!isObjectRecord(value)) {
3046
- return false;
3047
- }
3048
- const candidate = value;
3049
- return (candidate.kind === "unix-socket" || candidate.kind === "windows-pipe") && typeof candidate.address === "string";
3050
- }
3051
- function isSerializedTagDefinition(value) {
3052
- if (!isObjectRecord(value)) {
3053
- return false;
3054
- }
3055
- const candidate = value;
3056
- return typeof candidate.canonicalName === "string" && typeof candidate.completionDetail === "string" && typeof candidate.hoverMarkdown === "string";
3057
- }
3058
- function isSerializedTagSignature(value) {
3059
- if (!isObjectRecord(value)) {
3060
- return false;
3061
- }
3062
- const candidate = value;
3063
- return typeof candidate.label === "string" && isPlacementArray(candidate.placements);
3064
- }
3065
- function isSerializedCommentTargetSpecifier(value) {
3066
- if (!isObjectRecord(value)) {
3067
- return false;
3068
- }
3069
- const candidate = value;
3070
- return typeof candidate.rawText === "string" && typeof candidate.valid === "boolean" && typeof candidate.kind === "string" && isCommentSpan(candidate.fullSpan) && isCommentSpan(candidate.colonSpan) && isCommentSpan(candidate.span);
3071
- }
3072
- function isSerializedTagSemanticContext(value) {
3073
- if (!isObjectRecord(value)) {
3074
- return false;
3075
- }
3076
- const candidate = value;
3077
- return typeof candidate.tagName === "string" && (candidate.tagDefinition === null || isSerializedTagDefinition(candidate.tagDefinition)) && (candidate.placement === null || typeof candidate.placement === "string") && isTargetKindArray(candidate.supportedTargets) && isStringArray(candidate.targetCompletions) && isStringArray(candidate.compatiblePathTargets) && isStringArray(candidate.valueLabels) && Array.isArray(candidate.signatures) && candidate.signatures.every(isSerializedTagSignature) && (candidate.tagHoverMarkdown === null || typeof candidate.tagHoverMarkdown === "string") && (candidate.targetHoverMarkdown === null || typeof candidate.targetHoverMarkdown === "string") && (candidate.argumentHoverMarkdown === null || typeof candidate.argumentHoverMarkdown === "string");
3078
- }
3079
- function isSerializedCompletionContext(value) {
3080
- if (!isObjectRecord(value)) {
3081
- return false;
3082
- }
3083
- const candidate = value;
3084
- if (typeof candidate.kind !== "string") {
3085
- return false;
3086
- }
3087
- switch (candidate.kind) {
3088
- case "tag-name":
3089
- return typeof candidate.prefix === "string" && Array.isArray(candidate.availableTags) ? candidate.availableTags.every(isSerializedTagDefinition) : false;
3090
- case "target":
3091
- return isSerializedTagSemanticContext(candidate.semantic);
3092
- case "argument":
3093
- return isSerializedTagSemanticContext(candidate.semantic) && isStringArray(candidate.valueLabels);
3094
- case "none":
3095
- return true;
3096
- default:
3097
- return false;
3098
- }
3099
- }
3100
- function isSerializedHoverInfo(value) {
3101
- if (!isObjectRecord(value)) {
3102
- return false;
3103
- }
3104
- const candidate = value;
3105
- return (candidate.kind === "tag-name" || candidate.kind === "target" || candidate.kind === "argument") && typeof candidate.markdown === "string";
3106
- }
3107
- function hasCurrentProtocolVersion(value) {
3108
- return isObjectRecord(value) && value["protocolVersion"] === FORMSPEC_ANALYSIS_PROTOCOL_VERSION;
3109
- }
3110
- function isAnalysisDiagnostic(value) {
3111
- if (!isObjectRecord(value)) {
3112
- return false;
3113
- }
3114
- const candidate = value;
3115
- return typeof candidate.code === "string" && typeof candidate.message === "string" && isCommentSpan(candidate.range) && (candidate.severity === "error" || candidate.severity === "warning" || candidate.severity === "info");
3116
- }
3117
- function isAnalysisTagSnapshot(value) {
3118
- if (!isObjectRecord(value)) {
3119
- return false;
3120
- }
3121
- const candidate = value;
3122
- return typeof candidate.rawTagName === "string" && typeof candidate.normalizedTagName === "string" && typeof candidate.recognized === "boolean" && isCommentSpan(candidate.fullSpan) && isCommentSpan(candidate.tagNameSpan) && (candidate.payloadSpan === null || isCommentSpan(candidate.payloadSpan)) && (candidate.target === null || isSerializedCommentTargetSpecifier(candidate.target)) && (candidate.argumentSpan === null || isCommentSpan(candidate.argumentSpan)) && typeof candidate.argumentText === "string" && isSerializedTagSemanticContext(candidate.semantic);
3123
- }
3124
- function isAnalysisCommentSnapshot(value) {
3125
- if (!isObjectRecord(value)) {
3126
- return false;
3127
- }
3128
- const candidate = value;
3129
- return isCommentSpan(candidate.commentSpan) && isCommentSpan(candidate.declarationSpan) && (candidate.placement === null || typeof candidate.placement === "string") && (candidate.subjectType === null || typeof candidate.subjectType === "string") && (candidate.hostType === null || typeof candidate.hostType === "string") && Array.isArray(candidate.tags) && candidate.tags.every(isAnalysisTagSnapshot);
3130
- }
3131
- function isAnalysisFileSnapshot(value) {
3132
- if (!isObjectRecord(value)) {
3133
- return false;
3134
- }
3135
- const candidate = value;
3136
- return typeof candidate.filePath === "string" && typeof candidate.sourceHash === "string" && typeof candidate.generatedAt === "string" && Array.isArray(candidate.comments) && candidate.comments.every(isAnalysisCommentSnapshot) && Array.isArray(candidate.diagnostics) && candidate.diagnostics.every(isAnalysisDiagnostic);
3137
- }
3138
- function isFormSpecAnalysisManifest(value) {
3139
- if (!isObjectRecord(value)) {
3140
- return false;
3141
- }
3142
- const candidate = value;
3143
- return candidate.protocolVersion === FORMSPEC_ANALYSIS_PROTOCOL_VERSION && candidate.analysisSchemaVersion === FORMSPEC_ANALYSIS_SCHEMA_VERSION && typeof candidate.workspaceRoot === "string" && typeof candidate.workspaceId === "string" && isIpcEndpoint(candidate.endpoint) && typeof candidate.typescriptVersion === "string" && typeof candidate.extensionFingerprint === "string" && typeof candidate.generation === "number" && typeof candidate.updatedAt === "string";
3144
- }
3145
- function isFormSpecSemanticQuery(value) {
3146
- if (!hasCurrentProtocolVersion(value)) {
3147
- return false;
3148
- }
3149
- const candidate = value;
3150
- if (typeof candidate.kind !== "string") {
3151
- return false;
3152
- }
3153
- switch (candidate.kind) {
3154
- case "health":
3155
- return true;
3156
- case "completion":
3157
- case "hover":
3158
- return typeof candidate.filePath === "string" && typeof candidate.offset === "number";
3159
- case "diagnostics":
3160
- case "file-snapshot":
3161
- return typeof candidate.filePath === "string";
3162
- default:
3163
- return false;
3164
- }
3165
- }
3166
- function isFormSpecSemanticResponse(value) {
3167
- if (!hasCurrentProtocolVersion(value)) {
3168
- return false;
3169
- }
3170
- const candidate = value;
3171
- if (typeof candidate.kind !== "string") {
3172
- return false;
3173
- }
3174
- switch (candidate.kind) {
3175
- case "health":
3176
- return isFormSpecAnalysisManifest(candidate.manifest);
3177
- case "completion":
3178
- return typeof candidate.sourceHash === "string" && isSerializedCompletionContext(candidate.context);
3179
- case "hover":
3180
- return typeof candidate.sourceHash === "string" && (candidate.hover === null || isSerializedHoverInfo(candidate.hover));
3181
- case "diagnostics":
3182
- return typeof candidate.sourceHash === "string" && Array.isArray(candidate.diagnostics) && candidate.diagnostics.every(isAnalysisDiagnostic);
3183
- case "file-snapshot":
3184
- return candidate.snapshot === null || isAnalysisFileSnapshot(candidate.snapshot);
3185
- case "error":
3186
- return typeof candidate.error === "string";
3187
- default:
3188
- return false;
3189
- }
3190
- }
3191
- function computeFormSpecTextHash(text) {
3192
- let hash = 2166136261;
3193
- for (let index = 0; index < text.length; index += 1) {
3194
- hash ^= text.charCodeAt(index);
3195
- hash = Math.imul(hash, 16777619);
3196
- }
3197
- return (hash >>> 0).toString(16).padStart(8, "0");
3198
- }
3199
- function serializeCommentTargetSpecifier(target) {
3200
- if (target === null) {
3201
- return null;
3202
- }
3203
- return {
3204
- rawText: target.rawText,
3205
- valid: target.valid,
3206
- kind: target.kind,
3207
- fullSpan: target.fullSpan,
3208
- colonSpan: target.colonSpan,
3209
- span: target.span
841
+ rawText: target.rawText,
842
+ valid: target.valid,
843
+ kind: target.kind,
844
+ fullSpan: target.fullSpan,
845
+ colonSpan: target.colonSpan,
846
+ span: target.span
3210
847
  };
3211
848
  }
3212
849
  function serializeCommentTagSemanticContext(semantic) {
@@ -3283,233 +920,6 @@ function serializeParsedCommentTag(tag, semantic) {
3283
920
  };
3284
921
  }
3285
922
 
3286
- // src/file-snapshots.ts
3287
- function spanFromPos(start, end) {
3288
- return { start, end };
3289
- }
3290
- function typeToString(type, checker) {
3291
- if (type === void 0) {
3292
- return null;
3293
- }
3294
- return checker.typeToString(type, void 0, ts4.TypeFormatFlags.NoTruncation);
3295
- }
3296
- function supportingDeclarationsForType(type) {
3297
- if (type === void 0) {
3298
- return [];
3299
- }
3300
- const symbol = type.aliasSymbol ?? type.getSymbol();
3301
- const declarations = symbol?.declarations ?? [];
3302
- return declarations.map(
3303
- (declaration) => declaration.getSourceFile().text.slice(declaration.getFullStart(), declaration.getEnd())
3304
- ).filter((declarationText) => declarationText.trim().length > 0);
3305
- }
3306
- function getSyntheticTargetForTag(tag) {
3307
- if (tag.target === null) {
3308
- return null;
3309
- }
3310
- switch (tag.target.kind) {
3311
- case "path":
3312
- case "member":
3313
- case "variant":
3314
- return {
3315
- kind: tag.target.kind,
3316
- text: tag.target.rawText
3317
- };
3318
- case "ambiguous":
3319
- return {
3320
- kind: "path",
3321
- text: tag.target.rawText
3322
- };
3323
- default: {
3324
- const exhaustive = tag.target.kind;
3325
- return exhaustive;
3326
- }
3327
- }
3328
- }
3329
- function getArgumentExpression(argumentText, valueLabels, capabilityTargets) {
3330
- const trimmed = argumentText.trim();
3331
- if (trimmed === "") {
3332
- return null;
3333
- }
3334
- if (valueLabels.some((label) => label.includes("number") || label.includes("integer"))) {
3335
- return trimmed;
3336
- }
3337
- if (valueLabels.some((label) => label.includes("boolean"))) {
3338
- return trimmed === "true" || trimmed === "false" ? trimmed : null;
3339
- }
3340
- if (valueLabels.some((label) => label.includes("json"))) {
3341
- try {
3342
- return JSON.stringify(JSON.parse(trimmed));
3343
- } catch {
3344
- return null;
3345
- }
3346
- }
3347
- if (valueLabels.some((label) => label.includes("condition"))) {
3348
- return "undefined as unknown as FormSpecCondition";
3349
- }
3350
- if (capabilityTargets.length > 0 || valueLabels.some((label) => label.includes("string"))) {
3351
- return JSON.stringify(trimmed);
3352
- }
3353
- return JSON.stringify(trimmed);
3354
- }
3355
- function diagnosticSeverity(code) {
3356
- switch (code) {
3357
- case "INVALID_TAG_ARGUMENT":
3358
- case "INVALID_TAG_PLACEMENT":
3359
- case "TYPE_MISMATCH":
3360
- case "UNKNOWN_PATH_TARGET":
3361
- return "error";
3362
- default:
3363
- return "warning";
3364
- }
3365
- }
3366
- function buildTagDiagnostics(sourceFile, checker, placement, hostType, subjectType, commentTags, semanticOptions) {
3367
- if (placement === null || subjectType === void 0) {
3368
- return [];
3369
- }
3370
- const diagnostics = [];
3371
- const hostTypeText = typeToString(hostType, checker) ?? "unknown";
3372
- const subjectTypeText = typeToString(subjectType, checker) ?? "unknown";
3373
- const supportingDeclarations = [
3374
- ...supportingDeclarationsForType(hostType),
3375
- ...supportingDeclarationsForType(subjectType)
3376
- ];
3377
- for (const tag of commentTags) {
3378
- const semantic = getCommentTagSemanticContext(tag, semanticOptions);
3379
- if (semantic.tagDefinition === null) {
3380
- continue;
3381
- }
3382
- const target = getSyntheticTargetForTag(tag);
3383
- const argumentExpression = getArgumentExpression(
3384
- tag.argumentText,
3385
- semantic.valueLabels,
3386
- semantic.compatiblePathTargets
3387
- );
3388
- try {
3389
- const result = checkSyntheticTagApplication({
3390
- tagName: tag.normalizedTagName,
3391
- placement,
3392
- hostType: hostTypeText,
3393
- subjectType: subjectTypeText,
3394
- supportingDeclarations,
3395
- ...target === null ? {} : { target },
3396
- ...argumentExpression === null ? {} : { argumentExpression },
3397
- ...semanticOptions.extensions === void 0 ? {} : { extensions: semanticOptions.extensions }
3398
- });
3399
- for (const diagnostic of result.diagnostics) {
3400
- const code = target !== null && diagnostic.message.includes("not assignable") ? target.kind === "path" ? "UNKNOWN_PATH_TARGET" : "TYPE_MISMATCH" : diagnostic.message.includes("Expected") ? "INVALID_TAG_ARGUMENT" : diagnostic.message.includes("No overload") ? "INVALID_TAG_PLACEMENT" : "TYPE_MISMATCH";
3401
- diagnostics.push({
3402
- code,
3403
- message: diagnostic.message,
3404
- range: tag.fullSpan,
3405
- severity: diagnosticSeverity(code)
3406
- });
3407
- }
3408
- } catch (error) {
3409
- diagnostics.push({
3410
- code: "INVALID_TAG_PLACEMENT",
3411
- message: error instanceof Error ? error.message : String(error),
3412
- range: tag.fullSpan,
3413
- severity: "error"
3414
- });
3415
- }
3416
- }
3417
- return diagnostics;
3418
- }
3419
- function buildCommentSnapshot(node, sourceFile, checker, extensions) {
3420
- const docComment = getLastLeadingDocCommentRange(node, sourceFile);
3421
- if (docComment === null) {
3422
- return null;
3423
- }
3424
- const commentText = sourceFile.text.slice(docComment.pos, docComment.end);
3425
- const parsed = parseCommentBlock(commentText, {
3426
- offset: docComment.pos,
3427
- ...extensions === void 0 ? {} : { extensions }
3428
- });
3429
- if (parsed.tags.length === 0) {
3430
- return null;
3431
- }
3432
- const placement = resolveDeclarationPlacement(node);
3433
- const subjectType = getSubjectType(node, checker);
3434
- const hostType = getHostType(node, checker);
3435
- const semanticOptions = {
3436
- checker,
3437
- ...subjectType === void 0 ? {} : { subjectType },
3438
- ...placement === null ? {} : { placement },
3439
- ...extensions === void 0 ? {} : { extensions }
3440
- };
3441
- const tags = parsed.tags.map(
3442
- (tag) => serializeParsedCommentTag(tag, getCommentTagSemanticContext(tag, semanticOptions))
3443
- );
3444
- return {
3445
- commentSpan: spanFromPos(docComment.pos, docComment.end),
3446
- declarationSpan: spanFromPos(node.getStart(sourceFile), node.getEnd()),
3447
- placement,
3448
- subjectType: typeToString(subjectType, checker),
3449
- hostType: typeToString(hostType, checker),
3450
- tags
3451
- };
3452
- }
3453
- function buildFormSpecAnalysisFileSnapshot(sourceFile, options) {
3454
- const comments = [];
3455
- const diagnostics = [];
3456
- const visit = (node) => {
3457
- const placement = resolveDeclarationPlacement(node);
3458
- if (placement !== null) {
3459
- const snapshot = buildCommentSnapshot(node, sourceFile, options.checker, options.extensions);
3460
- if (snapshot !== null) {
3461
- comments.push(snapshot);
3462
- const subjectType = getSubjectType(node, options.checker);
3463
- const hostType = getHostType(node, options.checker);
3464
- diagnostics.push(
3465
- ...buildTagDiagnostics(
3466
- sourceFile,
3467
- options.checker,
3468
- placement,
3469
- hostType,
3470
- subjectType,
3471
- snapshot.tags.map((tag) => ({
3472
- rawTagName: tag.rawTagName,
3473
- normalizedTagName: tag.normalizedTagName,
3474
- recognized: tag.recognized,
3475
- fullSpan: tag.fullSpan,
3476
- tagNameSpan: tag.tagNameSpan,
3477
- payloadSpan: tag.payloadSpan,
3478
- colonSpan: tag.target?.colonSpan ?? null,
3479
- target: tag.target === null ? null : {
3480
- rawText: tag.target.rawText,
3481
- valid: tag.target.valid,
3482
- kind: tag.target.kind,
3483
- fullSpan: tag.target.fullSpan,
3484
- colonSpan: tag.target.colonSpan,
3485
- span: tag.target.span,
3486
- path: null
3487
- },
3488
- argumentSpan: tag.argumentSpan,
3489
- argumentText: tag.argumentText
3490
- })),
3491
- {
3492
- checker: options.checker,
3493
- ...subjectType === void 0 ? {} : { subjectType },
3494
- placement,
3495
- ...options.extensions === void 0 ? {} : { extensions: options.extensions }
3496
- }
3497
- )
3498
- );
3499
- }
3500
- }
3501
- ts4.forEachChild(node, visit);
3502
- };
3503
- visit(sourceFile);
3504
- return {
3505
- filePath: sourceFile.fileName,
3506
- sourceHash: computeFormSpecTextHash(sourceFile.text),
3507
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
3508
- comments,
3509
- diagnostics
3510
- };
3511
- }
3512
-
3513
923
  // src/workspace-runtime.ts
3514
924
  var import_node_path = __toESM(require("path"), 1);
3515
925
  function getFormSpecWorkspaceId(workspaceRoot) {
@@ -3525,58 +935,17 @@ function getFormSpecManifestPath(workspaceRoot) {
3525
935
  0 && (module.exports = {
3526
936
  FORMSPEC_ANALYSIS_PROTOCOL_VERSION,
3527
937
  FORMSPEC_ANALYSIS_SCHEMA_VERSION,
3528
- analyzeConstraintTargets,
3529
- buildConstraintTargetStates,
3530
- buildFormSpecAnalysisFileSnapshot,
3531
- buildSyntheticHelperPrelude,
3532
- checkSyntheticTagApplication,
3533
- collectCompatiblePathTargets,
3534
- collectReferencedTypeAnnotations,
3535
- collectReferencedTypeConstraints,
3536
938
  computeFormSpecTextHash,
3537
- dereferenceAnalysisType,
3538
- extractPathTarget,
3539
- findCommentTagAtOffset,
3540
- findDeclarationForCommentOffset,
3541
- findEnclosingDocComment,
3542
- formatConstraintTargetName,
3543
- formatPathTarget,
3544
- getAllTagDefinitions,
3545
- getCommentCompletionContextAtOffset,
3546
- getCommentCursorTargetAtOffset,
3547
- getCommentHoverInfoAtOffset,
3548
- getCommentTagSemanticContext,
3549
- getConstraintTagDefinitions,
3550
939
  getFormSpecManifestPath,
3551
940
  getFormSpecWorkspaceId,
3552
941
  getFormSpecWorkspaceRuntimeDirectory,
3553
- getHostType,
3554
- getLastLeadingDocCommentRange,
3555
- getMatchingTagSignatures,
3556
- getSemanticCommentCompletionContextAtOffset,
3557
- getSubjectType,
3558
- getTagCompletionPrefixAtOffset,
3559
- getTagDefinition,
3560
- getTagHoverMarkdown,
3561
- getTypeSemanticCapabilities,
3562
- hasTypeSemanticCapability,
3563
942
  isFormSpecAnalysisManifest,
3564
943
  isFormSpecSemanticQuery,
3565
944
  isFormSpecSemanticResponse,
3566
- lowerTagApplicationToSyntheticCall,
3567
- normalizeFormSpecTagName,
3568
- parseCommentBlock,
3569
- parseConstraintTagValue,
3570
- parseDefaultValueTagValue,
3571
- parseTagSyntax,
3572
- resolveConstraintTargetState,
3573
- resolveDeclarationPlacement,
3574
- resolvePathTargetType,
3575
945
  serializeCommentTagSemanticContext,
3576
946
  serializeCommentTargetSpecifier,
3577
947
  serializeCompletionContext,
3578
948
  serializeHoverInfo,
3579
- serializeParsedCommentTag,
3580
- sliceCommentSpan
949
+ serializeParsedCommentTag
3581
950
  });
3582
951
  //# sourceMappingURL=index.cjs.map