@formspec/build 0.1.0-alpha.15 → 0.1.0-alpha.17
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/__tests__/fixtures/edge-cases.d.ts +11 -0
- package/dist/__tests__/fixtures/edge-cases.d.ts.map +1 -1
- package/dist/__tests__/fixtures/example-numeric-extension.d.ts +20 -0
- package/dist/__tests__/fixtures/example-numeric-extension.d.ts.map +1 -0
- package/dist/__tests__/fixtures/mixed-authoring-shipping-address.d.ts +31 -0
- package/dist/__tests__/fixtures/mixed-authoring-shipping-address.d.ts.map +1 -0
- package/dist/__tests__/mixed-authoring.test.d.ts +2 -0
- package/dist/__tests__/mixed-authoring.test.d.ts.map +1 -0
- package/dist/__tests__/numeric-extension.integration.test.d.ts +2 -0
- package/dist/__tests__/numeric-extension.integration.test.d.ts.map +1 -0
- package/dist/__tests__/parity/utils.d.ts +5 -3
- package/dist/__tests__/parity/utils.d.ts.map +1 -1
- package/dist/analyzer/class-analyzer.d.ts +8 -5
- package/dist/analyzer/class-analyzer.d.ts.map +1 -1
- package/dist/analyzer/jsdoc-constraints.d.ts +3 -2
- package/dist/analyzer/jsdoc-constraints.d.ts.map +1 -1
- package/dist/analyzer/tsdoc-parser.d.ts +38 -4
- package/dist/analyzer/tsdoc-parser.d.ts.map +1 -1
- package/dist/browser.cjs +371 -21
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +371 -21
- package/dist/browser.js.map +1 -1
- package/dist/build.d.ts +67 -3
- package/dist/canonicalize/tsdoc-canonicalizer.d.ts.map +1 -1
- package/dist/cli.cjs +1159 -150
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1159 -150
- package/dist/cli.js.map +1 -1
- package/dist/extensions/registry.d.ts +25 -1
- package/dist/extensions/registry.d.ts.map +1 -1
- package/dist/generators/class-schema.d.ts +4 -4
- package/dist/generators/class-schema.d.ts.map +1 -1
- package/dist/generators/mixed-authoring.d.ts +45 -0
- package/dist/generators/mixed-authoring.d.ts.map +1 -0
- package/dist/index.cjs +1146 -149
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1145 -149
- package/dist/index.js.map +1 -1
- package/dist/internals.cjs +1156 -149
- package/dist/internals.cjs.map +1 -1
- package/dist/internals.js +1154 -147
- package/dist/internals.js.map +1 -1
- package/dist/json-schema/ir-generator.d.ts +3 -2
- package/dist/json-schema/ir-generator.d.ts.map +1 -1
- package/dist/ui-schema/ir-generator.d.ts.map +1 -1
- package/dist/validate/constraint-validator.d.ts.map +1 -1
- package/package.json +3 -3
- package/dist/__tests__/jsdoc-constraints.test.d.ts +0 -9
- package/dist/__tests__/jsdoc-constraints.test.d.ts.map +0 -1
package/dist/internals.cjs
CHANGED
|
@@ -376,6 +376,7 @@ function canonicalizeTSDoc(analysis, source) {
|
|
|
376
376
|
irVersion: import_core2.IR_VERSION,
|
|
377
377
|
elements,
|
|
378
378
|
typeRegistry: analysis.typeRegistry,
|
|
379
|
+
...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { annotations: analysis.annotations },
|
|
379
380
|
provenance
|
|
380
381
|
};
|
|
381
382
|
}
|
|
@@ -540,8 +541,8 @@ var LENGTH_CONSTRAINT_MAP = {
|
|
|
540
541
|
minItems: "minItems",
|
|
541
542
|
maxItems: "maxItems"
|
|
542
543
|
};
|
|
543
|
-
var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions"]);
|
|
544
|
-
function createFormSpecTSDocConfig() {
|
|
544
|
+
var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions", "defaultValue"]);
|
|
545
|
+
function createFormSpecTSDocConfig(extensionTagNames = []) {
|
|
545
546
|
const config = new import_tsdoc.TSDocConfiguration();
|
|
546
547
|
for (const tagName of Object.keys(import_core3.BUILTIN_CONSTRAINT_DEFINITIONS)) {
|
|
547
548
|
config.addTagDefinition(
|
|
@@ -552,7 +553,16 @@ function createFormSpecTSDocConfig() {
|
|
|
552
553
|
})
|
|
553
554
|
);
|
|
554
555
|
}
|
|
555
|
-
for (const tagName of ["displayName", "description"]) {
|
|
556
|
+
for (const tagName of ["displayName", "description", "format", "placeholder"]) {
|
|
557
|
+
config.addTagDefinition(
|
|
558
|
+
new import_tsdoc.TSDocTagDefinition({
|
|
559
|
+
tagName: "@" + tagName,
|
|
560
|
+
syntaxKind: import_tsdoc.TSDocTagSyntaxKind.BlockTag,
|
|
561
|
+
allowMultiple: true
|
|
562
|
+
})
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
for (const tagName of extensionTagNames) {
|
|
556
566
|
config.addTagDefinition(
|
|
557
567
|
new import_tsdoc.TSDocTagDefinition({
|
|
558
568
|
tagName: "@" + tagName,
|
|
@@ -563,14 +573,31 @@ function createFormSpecTSDocConfig() {
|
|
|
563
573
|
}
|
|
564
574
|
return config;
|
|
565
575
|
}
|
|
566
|
-
var
|
|
567
|
-
function getParser() {
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
576
|
+
var parserCache = /* @__PURE__ */ new Map();
|
|
577
|
+
function getParser(options) {
|
|
578
|
+
const extensionTagNames = [
|
|
579
|
+
...options?.extensionRegistry?.extensions.flatMap(
|
|
580
|
+
(extension) => (extension.constraintTags ?? []).map((tag) => tag.tagName)
|
|
581
|
+
) ?? []
|
|
582
|
+
].sort();
|
|
583
|
+
const cacheKey = extensionTagNames.join("|");
|
|
584
|
+
const existing = parserCache.get(cacheKey);
|
|
585
|
+
if (existing) {
|
|
586
|
+
return existing;
|
|
587
|
+
}
|
|
588
|
+
const parser = new import_tsdoc.TSDocParser(createFormSpecTSDocConfig(extensionTagNames));
|
|
589
|
+
parserCache.set(cacheKey, parser);
|
|
590
|
+
return parser;
|
|
591
|
+
}
|
|
592
|
+
function parseTSDocTags(node, file = "", options) {
|
|
572
593
|
const constraints = [];
|
|
573
594
|
const annotations = [];
|
|
595
|
+
let displayName;
|
|
596
|
+
let description;
|
|
597
|
+
let placeholder;
|
|
598
|
+
let displayNameProvenance;
|
|
599
|
+
let descriptionProvenance;
|
|
600
|
+
let placeholderProvenance;
|
|
574
601
|
const sourceFile = node.getSourceFile();
|
|
575
602
|
const sourceText = sourceFile.getFullText();
|
|
576
603
|
const commentRanges = ts2.getLeadingCommentRanges(sourceText, node.getFullStart());
|
|
@@ -583,52 +610,92 @@ function parseTSDocTags(node, file = "") {
|
|
|
583
610
|
if (!commentText.startsWith("/**")) {
|
|
584
611
|
continue;
|
|
585
612
|
}
|
|
586
|
-
const parser = getParser();
|
|
613
|
+
const parser = getParser(options);
|
|
587
614
|
const parserContext = parser.parseRange(
|
|
588
615
|
import_tsdoc.TextRange.fromStringRange(sourceText, range.pos, range.end)
|
|
589
616
|
);
|
|
590
617
|
const docComment = parserContext.docComment;
|
|
591
618
|
for (const block of docComment.customBlocks) {
|
|
592
619
|
const tagName = (0, import_core3.normalizeConstraintTagName)(block.blockTag.tagName.substring(1));
|
|
593
|
-
if (tagName === "displayName" || tagName === "description") {
|
|
620
|
+
if (tagName === "displayName" || tagName === "description" || tagName === "format" || tagName === "placeholder") {
|
|
594
621
|
const text2 = extractBlockText(block).trim();
|
|
595
622
|
if (text2 === "") continue;
|
|
596
623
|
const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
|
|
597
624
|
if (tagName === "displayName") {
|
|
625
|
+
if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
|
|
626
|
+
displayName = text2;
|
|
627
|
+
displayNameProvenance = provenance2;
|
|
628
|
+
}
|
|
629
|
+
} else if (tagName === "format") {
|
|
598
630
|
annotations.push({
|
|
599
631
|
kind: "annotation",
|
|
600
|
-
annotationKind: "
|
|
632
|
+
annotationKind: "format",
|
|
601
633
|
value: text2,
|
|
602
634
|
provenance: provenance2
|
|
603
635
|
});
|
|
604
636
|
} else {
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
637
|
+
if (tagName === "description" && description === void 0) {
|
|
638
|
+
description = text2;
|
|
639
|
+
descriptionProvenance = provenance2;
|
|
640
|
+
} else if (tagName === "placeholder" && placeholder === void 0) {
|
|
641
|
+
placeholder = text2;
|
|
642
|
+
placeholderProvenance = provenance2;
|
|
643
|
+
}
|
|
611
644
|
}
|
|
612
645
|
continue;
|
|
613
646
|
}
|
|
614
647
|
if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
615
648
|
const text = extractBlockText(block).trim();
|
|
616
|
-
|
|
649
|
+
const expectedType = (0, import_core3.isBuiltinConstraintName)(tagName) ? import_core3.BUILTIN_CONSTRAINT_DEFINITIONS[tagName] : void 0;
|
|
650
|
+
if (text === "" && expectedType !== "boolean") continue;
|
|
617
651
|
const provenance = provenanceForComment(range, sourceFile, file, tagName);
|
|
618
|
-
const constraintNode = parseConstraintValue(tagName, text, provenance);
|
|
652
|
+
const constraintNode = parseConstraintValue(tagName, text, provenance, options);
|
|
619
653
|
if (constraintNode) {
|
|
620
654
|
constraints.push(constraintNode);
|
|
621
655
|
}
|
|
622
656
|
}
|
|
623
657
|
if (docComment.deprecatedBlock !== void 0) {
|
|
658
|
+
const message = extractBlockText(docComment.deprecatedBlock).trim();
|
|
624
659
|
annotations.push({
|
|
625
660
|
kind: "annotation",
|
|
626
661
|
annotationKind: "deprecated",
|
|
662
|
+
...message !== "" && { message },
|
|
627
663
|
provenance: provenanceForComment(range, sourceFile, file, "deprecated")
|
|
628
664
|
});
|
|
629
665
|
}
|
|
666
|
+
if (description === void 0 && docComment.remarksBlock !== void 0) {
|
|
667
|
+
const remarks = extractBlockText(docComment.remarksBlock).trim();
|
|
668
|
+
if (remarks !== "") {
|
|
669
|
+
description = remarks;
|
|
670
|
+
descriptionProvenance = provenanceForComment(range, sourceFile, file, "remarks");
|
|
671
|
+
}
|
|
672
|
+
}
|
|
630
673
|
}
|
|
631
674
|
}
|
|
675
|
+
if (displayName !== void 0 && displayNameProvenance !== void 0) {
|
|
676
|
+
annotations.push({
|
|
677
|
+
kind: "annotation",
|
|
678
|
+
annotationKind: "displayName",
|
|
679
|
+
value: displayName,
|
|
680
|
+
provenance: displayNameProvenance
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
if (description !== void 0 && descriptionProvenance !== void 0) {
|
|
684
|
+
annotations.push({
|
|
685
|
+
kind: "annotation",
|
|
686
|
+
annotationKind: "description",
|
|
687
|
+
value: description,
|
|
688
|
+
provenance: descriptionProvenance
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
if (placeholder !== void 0 && placeholderProvenance !== void 0) {
|
|
692
|
+
annotations.push({
|
|
693
|
+
kind: "annotation",
|
|
694
|
+
annotationKind: "placeholder",
|
|
695
|
+
value: placeholder,
|
|
696
|
+
provenance: placeholderProvenance
|
|
697
|
+
});
|
|
698
|
+
}
|
|
632
699
|
const jsDocTagsAll = ts2.getJSDocTags(node);
|
|
633
700
|
for (const tag of jsDocTagsAll) {
|
|
634
701
|
const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
|
|
@@ -637,13 +704,40 @@ function parseTSDocTags(node, file = "") {
|
|
|
637
704
|
if (commentText === void 0 || commentText.trim() === "") continue;
|
|
638
705
|
const text = commentText.trim();
|
|
639
706
|
const provenance = provenanceForJSDocTag(tag, file);
|
|
640
|
-
|
|
707
|
+
if (tagName === "defaultValue") {
|
|
708
|
+
const defaultValueNode = parseDefaultValueValue(text, provenance);
|
|
709
|
+
annotations.push(defaultValueNode);
|
|
710
|
+
continue;
|
|
711
|
+
}
|
|
712
|
+
const constraintNode = parseConstraintValue(tagName, text, provenance, options);
|
|
641
713
|
if (constraintNode) {
|
|
642
714
|
constraints.push(constraintNode);
|
|
643
715
|
}
|
|
644
716
|
}
|
|
645
717
|
return { constraints, annotations };
|
|
646
718
|
}
|
|
719
|
+
function extractDisplayNameMetadata(node) {
|
|
720
|
+
let displayName;
|
|
721
|
+
const memberDisplayNames = /* @__PURE__ */ new Map();
|
|
722
|
+
for (const tag of ts2.getJSDocTags(node)) {
|
|
723
|
+
const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
|
|
724
|
+
if (tagName !== "displayName") continue;
|
|
725
|
+
const commentText = getTagCommentText(tag);
|
|
726
|
+
if (commentText === void 0) continue;
|
|
727
|
+
const text = commentText.trim();
|
|
728
|
+
if (text === "") continue;
|
|
729
|
+
const memberTarget = parseMemberTargetDisplayName(text);
|
|
730
|
+
if (memberTarget) {
|
|
731
|
+
memberDisplayNames.set(memberTarget.target, memberTarget.label);
|
|
732
|
+
continue;
|
|
733
|
+
}
|
|
734
|
+
displayName ??= text;
|
|
735
|
+
}
|
|
736
|
+
return {
|
|
737
|
+
...displayName !== void 0 && { displayName },
|
|
738
|
+
memberDisplayNames
|
|
739
|
+
};
|
|
740
|
+
}
|
|
647
741
|
function extractPathTarget(text) {
|
|
648
742
|
const trimmed = text.trimStart();
|
|
649
743
|
const match = /^:([a-zA-Z_]\w*)\s+([\s\S]*)$/.exec(trimmed);
|
|
@@ -671,7 +765,11 @@ function extractPlainText(node) {
|
|
|
671
765
|
}
|
|
672
766
|
return result;
|
|
673
767
|
}
|
|
674
|
-
function parseConstraintValue(tagName, text, provenance) {
|
|
768
|
+
function parseConstraintValue(tagName, text, provenance, options) {
|
|
769
|
+
const customConstraint = parseExtensionConstraintValue(tagName, text, provenance, options);
|
|
770
|
+
if (customConstraint) {
|
|
771
|
+
return customConstraint;
|
|
772
|
+
}
|
|
675
773
|
if (!(0, import_core3.isBuiltinConstraintName)(tagName)) {
|
|
676
774
|
return null;
|
|
677
775
|
}
|
|
@@ -706,7 +804,45 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
706
804
|
}
|
|
707
805
|
return null;
|
|
708
806
|
}
|
|
807
|
+
if (expectedType === "boolean") {
|
|
808
|
+
const trimmed = effectiveText.trim();
|
|
809
|
+
if (trimmed !== "" && trimmed !== "true") {
|
|
810
|
+
return null;
|
|
811
|
+
}
|
|
812
|
+
if (tagName === "uniqueItems") {
|
|
813
|
+
return {
|
|
814
|
+
kind: "constraint",
|
|
815
|
+
constraintKind: "uniqueItems",
|
|
816
|
+
value: true,
|
|
817
|
+
...path2 && { path: path2 },
|
|
818
|
+
provenance
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
return null;
|
|
822
|
+
}
|
|
709
823
|
if (expectedType === "json") {
|
|
824
|
+
if (tagName === "const") {
|
|
825
|
+
const trimmedText = effectiveText.trim();
|
|
826
|
+
if (trimmedText === "") return null;
|
|
827
|
+
try {
|
|
828
|
+
const parsed2 = JSON.parse(trimmedText);
|
|
829
|
+
return {
|
|
830
|
+
kind: "constraint",
|
|
831
|
+
constraintKind: "const",
|
|
832
|
+
value: parsed2,
|
|
833
|
+
...path2 && { path: path2 },
|
|
834
|
+
provenance
|
|
835
|
+
};
|
|
836
|
+
} catch {
|
|
837
|
+
return {
|
|
838
|
+
kind: "constraint",
|
|
839
|
+
constraintKind: "const",
|
|
840
|
+
value: trimmedText,
|
|
841
|
+
...path2 && { path: path2 },
|
|
842
|
+
provenance
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
}
|
|
710
846
|
const parsed = tryParseJson(effectiveText);
|
|
711
847
|
if (!Array.isArray(parsed)) {
|
|
712
848
|
return null;
|
|
@@ -738,6 +874,111 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
738
874
|
provenance
|
|
739
875
|
};
|
|
740
876
|
}
|
|
877
|
+
function parseExtensionConstraintValue(tagName, text, provenance, options) {
|
|
878
|
+
const pathResult = extractPathTarget(text);
|
|
879
|
+
const effectiveText = pathResult ? pathResult.remainingText : text;
|
|
880
|
+
const path2 = pathResult?.path;
|
|
881
|
+
const registry = options?.extensionRegistry;
|
|
882
|
+
if (registry === void 0) {
|
|
883
|
+
return null;
|
|
884
|
+
}
|
|
885
|
+
const directTag = registry.findConstraintTag(tagName);
|
|
886
|
+
if (directTag !== void 0) {
|
|
887
|
+
return makeCustomConstraintNode(
|
|
888
|
+
directTag.extensionId,
|
|
889
|
+
directTag.registration.constraintName,
|
|
890
|
+
directTag.registration.parseValue(effectiveText),
|
|
891
|
+
provenance,
|
|
892
|
+
path2,
|
|
893
|
+
registry
|
|
894
|
+
);
|
|
895
|
+
}
|
|
896
|
+
if (!(0, import_core3.isBuiltinConstraintName)(tagName)) {
|
|
897
|
+
return null;
|
|
898
|
+
}
|
|
899
|
+
const broadenedTypeId = getBroadenedCustomTypeId(options?.fieldType);
|
|
900
|
+
if (broadenedTypeId === void 0) {
|
|
901
|
+
return null;
|
|
902
|
+
}
|
|
903
|
+
const broadened = registry.findBuiltinConstraintBroadening(broadenedTypeId, tagName);
|
|
904
|
+
if (broadened === void 0) {
|
|
905
|
+
return null;
|
|
906
|
+
}
|
|
907
|
+
return makeCustomConstraintNode(
|
|
908
|
+
broadened.extensionId,
|
|
909
|
+
broadened.registration.constraintName,
|
|
910
|
+
broadened.registration.parseValue(effectiveText),
|
|
911
|
+
provenance,
|
|
912
|
+
path2,
|
|
913
|
+
registry
|
|
914
|
+
);
|
|
915
|
+
}
|
|
916
|
+
function getBroadenedCustomTypeId(fieldType) {
|
|
917
|
+
if (fieldType?.kind === "custom") {
|
|
918
|
+
return fieldType.typeId;
|
|
919
|
+
}
|
|
920
|
+
if (fieldType?.kind !== "union") {
|
|
921
|
+
return void 0;
|
|
922
|
+
}
|
|
923
|
+
const customMembers = fieldType.members.filter(
|
|
924
|
+
(member) => member.kind === "custom"
|
|
925
|
+
);
|
|
926
|
+
if (customMembers.length !== 1) {
|
|
927
|
+
return void 0;
|
|
928
|
+
}
|
|
929
|
+
const nonCustomMembers = fieldType.members.filter((member) => member.kind !== "custom");
|
|
930
|
+
const allOtherMembersAreNull = nonCustomMembers.every(
|
|
931
|
+
(member) => member.kind === "primitive" && member.primitiveKind === "null"
|
|
932
|
+
);
|
|
933
|
+
const customMember = customMembers[0];
|
|
934
|
+
return allOtherMembersAreNull && customMember !== void 0 ? customMember.typeId : void 0;
|
|
935
|
+
}
|
|
936
|
+
function makeCustomConstraintNode(extensionId, constraintName, payload, provenance, path2, registry) {
|
|
937
|
+
const constraintId = `${extensionId}/${constraintName}`;
|
|
938
|
+
const registration = registry.findConstraint(constraintId);
|
|
939
|
+
if (registration === void 0) {
|
|
940
|
+
throw new Error(
|
|
941
|
+
`Custom TSDoc tag resolved to unregistered constraint "${constraintId}". Register the constraint before using its tag.`
|
|
942
|
+
);
|
|
943
|
+
}
|
|
944
|
+
return {
|
|
945
|
+
kind: "constraint",
|
|
946
|
+
constraintKind: "custom",
|
|
947
|
+
constraintId,
|
|
948
|
+
payload,
|
|
949
|
+
compositionRule: registration.compositionRule,
|
|
950
|
+
...path2 && { path: path2 },
|
|
951
|
+
provenance
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
function parseDefaultValueValue(text, provenance) {
|
|
955
|
+
const trimmed = text.trim();
|
|
956
|
+
let value;
|
|
957
|
+
if (trimmed === "null") {
|
|
958
|
+
value = null;
|
|
959
|
+
} else if (trimmed === "true") {
|
|
960
|
+
value = true;
|
|
961
|
+
} else if (trimmed === "false") {
|
|
962
|
+
value = false;
|
|
963
|
+
} else {
|
|
964
|
+
const parsed = tryParseJson(trimmed);
|
|
965
|
+
value = parsed !== null ? parsed : trimmed;
|
|
966
|
+
}
|
|
967
|
+
return {
|
|
968
|
+
kind: "annotation",
|
|
969
|
+
annotationKind: "defaultValue",
|
|
970
|
+
value,
|
|
971
|
+
provenance
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
function isMemberTargetDisplayName(text) {
|
|
975
|
+
return parseMemberTargetDisplayName(text) !== null;
|
|
976
|
+
}
|
|
977
|
+
function parseMemberTargetDisplayName(text) {
|
|
978
|
+
const match = /^:([^\s]+)\s+([\s\S]+)$/.exec(text);
|
|
979
|
+
if (!match?.[1] || !match[2]) return null;
|
|
980
|
+
return { target: match[1], label: match[2].trim() };
|
|
981
|
+
}
|
|
741
982
|
function provenanceForComment(range, sourceFile, file, tagName) {
|
|
742
983
|
const { line, character } = sourceFile.getLineAndCharacterOfPosition(range.pos);
|
|
743
984
|
return {
|
|
@@ -770,12 +1011,12 @@ function getTagCommentText(tag) {
|
|
|
770
1011
|
}
|
|
771
1012
|
|
|
772
1013
|
// src/analyzer/jsdoc-constraints.ts
|
|
773
|
-
function extractJSDocConstraintNodes(node, file = "") {
|
|
774
|
-
const result = parseTSDocTags(node, file);
|
|
1014
|
+
function extractJSDocConstraintNodes(node, file = "", options) {
|
|
1015
|
+
const result = parseTSDocTags(node, file, options);
|
|
775
1016
|
return [...result.constraints];
|
|
776
1017
|
}
|
|
777
|
-
function extractJSDocAnnotationNodes(node, file = "") {
|
|
778
|
-
const result = parseTSDocTags(node, file);
|
|
1018
|
+
function extractJSDocAnnotationNodes(node, file = "", options) {
|
|
1019
|
+
const result = parseTSDocTags(node, file, options);
|
|
779
1020
|
return [...result.annotations];
|
|
780
1021
|
}
|
|
781
1022
|
function extractDefaultValueAnnotation(initializer, file = "") {
|
|
@@ -819,17 +1060,43 @@ function isObjectType(type) {
|
|
|
819
1060
|
function isTypeReference(type) {
|
|
820
1061
|
return !!(type.flags & ts4.TypeFlags.Object) && !!(type.objectFlags & ts4.ObjectFlags.Reference);
|
|
821
1062
|
}
|
|
822
|
-
|
|
1063
|
+
var RESOLVING_TYPE_PLACEHOLDER = {
|
|
1064
|
+
kind: "object",
|
|
1065
|
+
properties: [],
|
|
1066
|
+
additionalProperties: true
|
|
1067
|
+
};
|
|
1068
|
+
function makeParseOptions(extensionRegistry, fieldType) {
|
|
1069
|
+
if (extensionRegistry === void 0 && fieldType === void 0) {
|
|
1070
|
+
return void 0;
|
|
1071
|
+
}
|
|
1072
|
+
return {
|
|
1073
|
+
...extensionRegistry !== void 0 && { extensionRegistry },
|
|
1074
|
+
...fieldType !== void 0 && { fieldType }
|
|
1075
|
+
};
|
|
1076
|
+
}
|
|
1077
|
+
function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
823
1078
|
const name = classDecl.name?.text ?? "AnonymousClass";
|
|
824
1079
|
const fields = [];
|
|
825
1080
|
const fieldLayouts = [];
|
|
826
1081
|
const typeRegistry = {};
|
|
1082
|
+
const annotations = extractJSDocAnnotationNodes(
|
|
1083
|
+
classDecl,
|
|
1084
|
+
file,
|
|
1085
|
+
makeParseOptions(extensionRegistry)
|
|
1086
|
+
);
|
|
827
1087
|
const visiting = /* @__PURE__ */ new Set();
|
|
828
1088
|
const instanceMethods = [];
|
|
829
1089
|
const staticMethods = [];
|
|
830
1090
|
for (const member of classDecl.members) {
|
|
831
1091
|
if (ts4.isPropertyDeclaration(member)) {
|
|
832
|
-
const fieldNode = analyzeFieldToIR(
|
|
1092
|
+
const fieldNode = analyzeFieldToIR(
|
|
1093
|
+
member,
|
|
1094
|
+
checker,
|
|
1095
|
+
file,
|
|
1096
|
+
typeRegistry,
|
|
1097
|
+
visiting,
|
|
1098
|
+
extensionRegistry
|
|
1099
|
+
);
|
|
833
1100
|
if (fieldNode) {
|
|
834
1101
|
fields.push(fieldNode);
|
|
835
1102
|
fieldLayouts.push({});
|
|
@@ -846,25 +1113,53 @@ function analyzeClassToIR(classDecl, checker, file = "") {
|
|
|
846
1113
|
}
|
|
847
1114
|
}
|
|
848
1115
|
}
|
|
849
|
-
return {
|
|
1116
|
+
return {
|
|
1117
|
+
name,
|
|
1118
|
+
fields,
|
|
1119
|
+
fieldLayouts,
|
|
1120
|
+
typeRegistry,
|
|
1121
|
+
...annotations.length > 0 && { annotations },
|
|
1122
|
+
instanceMethods,
|
|
1123
|
+
staticMethods
|
|
1124
|
+
};
|
|
850
1125
|
}
|
|
851
|
-
function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
|
|
1126
|
+
function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegistry) {
|
|
852
1127
|
const name = interfaceDecl.name.text;
|
|
853
1128
|
const fields = [];
|
|
854
1129
|
const typeRegistry = {};
|
|
1130
|
+
const annotations = extractJSDocAnnotationNodes(
|
|
1131
|
+
interfaceDecl,
|
|
1132
|
+
file,
|
|
1133
|
+
makeParseOptions(extensionRegistry)
|
|
1134
|
+
);
|
|
855
1135
|
const visiting = /* @__PURE__ */ new Set();
|
|
856
1136
|
for (const member of interfaceDecl.members) {
|
|
857
1137
|
if (ts4.isPropertySignature(member)) {
|
|
858
|
-
const fieldNode = analyzeInterfacePropertyToIR(
|
|
1138
|
+
const fieldNode = analyzeInterfacePropertyToIR(
|
|
1139
|
+
member,
|
|
1140
|
+
checker,
|
|
1141
|
+
file,
|
|
1142
|
+
typeRegistry,
|
|
1143
|
+
visiting,
|
|
1144
|
+
extensionRegistry
|
|
1145
|
+
);
|
|
859
1146
|
if (fieldNode) {
|
|
860
1147
|
fields.push(fieldNode);
|
|
861
1148
|
}
|
|
862
1149
|
}
|
|
863
1150
|
}
|
|
864
1151
|
const fieldLayouts = fields.map(() => ({}));
|
|
865
|
-
return {
|
|
1152
|
+
return {
|
|
1153
|
+
name,
|
|
1154
|
+
fields,
|
|
1155
|
+
fieldLayouts,
|
|
1156
|
+
typeRegistry,
|
|
1157
|
+
...annotations.length > 0 && { annotations },
|
|
1158
|
+
instanceMethods: [],
|
|
1159
|
+
staticMethods: []
|
|
1160
|
+
};
|
|
866
1161
|
}
|
|
867
|
-
function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
1162
|
+
function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry) {
|
|
868
1163
|
if (!ts4.isTypeLiteralNode(typeAlias.type)) {
|
|
869
1164
|
const sourceFile = typeAlias.getSourceFile();
|
|
870
1165
|
const { line } = sourceFile.getLineAndCharacterOfPosition(typeAlias.getStart());
|
|
@@ -877,10 +1172,22 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
|
877
1172
|
const name = typeAlias.name.text;
|
|
878
1173
|
const fields = [];
|
|
879
1174
|
const typeRegistry = {};
|
|
1175
|
+
const annotations = extractJSDocAnnotationNodes(
|
|
1176
|
+
typeAlias,
|
|
1177
|
+
file,
|
|
1178
|
+
makeParseOptions(extensionRegistry)
|
|
1179
|
+
);
|
|
880
1180
|
const visiting = /* @__PURE__ */ new Set();
|
|
881
1181
|
for (const member of typeAlias.type.members) {
|
|
882
1182
|
if (ts4.isPropertySignature(member)) {
|
|
883
|
-
const fieldNode = analyzeInterfacePropertyToIR(
|
|
1183
|
+
const fieldNode = analyzeInterfacePropertyToIR(
|
|
1184
|
+
member,
|
|
1185
|
+
checker,
|
|
1186
|
+
file,
|
|
1187
|
+
typeRegistry,
|
|
1188
|
+
visiting,
|
|
1189
|
+
extensionRegistry
|
|
1190
|
+
);
|
|
884
1191
|
if (fieldNode) {
|
|
885
1192
|
fields.push(fieldNode);
|
|
886
1193
|
}
|
|
@@ -893,12 +1200,13 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
|
893
1200
|
fields,
|
|
894
1201
|
fieldLayouts: fields.map(() => ({})),
|
|
895
1202
|
typeRegistry,
|
|
1203
|
+
...annotations.length > 0 && { annotations },
|
|
896
1204
|
instanceMethods: [],
|
|
897
1205
|
staticMethods: []
|
|
898
1206
|
}
|
|
899
1207
|
};
|
|
900
1208
|
}
|
|
901
|
-
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
|
|
1209
|
+
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
902
1210
|
if (!ts4.isIdentifier(prop.name)) {
|
|
903
1211
|
return null;
|
|
904
1212
|
}
|
|
@@ -906,16 +1214,28 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
|
|
|
906
1214
|
const tsType = checker.getTypeAtLocation(prop);
|
|
907
1215
|
const optional = prop.questionToken !== void 0;
|
|
908
1216
|
const provenance = provenanceForNode(prop, file);
|
|
909
|
-
let type = resolveTypeNode(
|
|
1217
|
+
let type = resolveTypeNode(
|
|
1218
|
+
tsType,
|
|
1219
|
+
checker,
|
|
1220
|
+
file,
|
|
1221
|
+
typeRegistry,
|
|
1222
|
+
visiting,
|
|
1223
|
+
prop,
|
|
1224
|
+
extensionRegistry
|
|
1225
|
+
);
|
|
910
1226
|
const constraints = [];
|
|
911
1227
|
if (prop.type) {
|
|
912
|
-
constraints.push(
|
|
1228
|
+
constraints.push(
|
|
1229
|
+
...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
|
|
1230
|
+
);
|
|
913
1231
|
}
|
|
914
|
-
constraints.push(...extractJSDocConstraintNodes(prop, file));
|
|
1232
|
+
constraints.push(...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type)));
|
|
915
1233
|
let annotations = [];
|
|
916
|
-
annotations.push(
|
|
1234
|
+
annotations.push(
|
|
1235
|
+
...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
1236
|
+
);
|
|
917
1237
|
const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
|
|
918
|
-
if (defaultAnnotation) {
|
|
1238
|
+
if (defaultAnnotation && !annotations.some((a) => a.annotationKind === "defaultValue")) {
|
|
919
1239
|
annotations.push(defaultAnnotation);
|
|
920
1240
|
}
|
|
921
1241
|
({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
|
|
@@ -929,7 +1249,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
|
|
|
929
1249
|
provenance
|
|
930
1250
|
};
|
|
931
1251
|
}
|
|
932
|
-
function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting) {
|
|
1252
|
+
function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
933
1253
|
if (!ts4.isIdentifier(prop.name)) {
|
|
934
1254
|
return null;
|
|
935
1255
|
}
|
|
@@ -937,14 +1257,26 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
|
|
|
937
1257
|
const tsType = checker.getTypeAtLocation(prop);
|
|
938
1258
|
const optional = prop.questionToken !== void 0;
|
|
939
1259
|
const provenance = provenanceForNode(prop, file);
|
|
940
|
-
let type = resolveTypeNode(
|
|
1260
|
+
let type = resolveTypeNode(
|
|
1261
|
+
tsType,
|
|
1262
|
+
checker,
|
|
1263
|
+
file,
|
|
1264
|
+
typeRegistry,
|
|
1265
|
+
visiting,
|
|
1266
|
+
prop,
|
|
1267
|
+
extensionRegistry
|
|
1268
|
+
);
|
|
941
1269
|
const constraints = [];
|
|
942
1270
|
if (prop.type) {
|
|
943
|
-
constraints.push(
|
|
1271
|
+
constraints.push(
|
|
1272
|
+
...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
|
|
1273
|
+
);
|
|
944
1274
|
}
|
|
945
|
-
constraints.push(...extractJSDocConstraintNodes(prop, file));
|
|
1275
|
+
constraints.push(...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type)));
|
|
946
1276
|
let annotations = [];
|
|
947
|
-
annotations.push(
|
|
1277
|
+
annotations.push(
|
|
1278
|
+
...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
1279
|
+
);
|
|
948
1280
|
({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
|
|
949
1281
|
return {
|
|
950
1282
|
kind: "field",
|
|
@@ -1018,7 +1350,66 @@ function parseEnumMemberDisplayName(value) {
|
|
|
1018
1350
|
if (label === "") return null;
|
|
1019
1351
|
return { value: match[1], label };
|
|
1020
1352
|
}
|
|
1021
|
-
function
|
|
1353
|
+
function resolveRegisteredCustomType(sourceNode, extensionRegistry, checker) {
|
|
1354
|
+
if (sourceNode === void 0 || extensionRegistry === void 0) {
|
|
1355
|
+
return null;
|
|
1356
|
+
}
|
|
1357
|
+
const typeNode = extractTypeNodeFromSource(sourceNode);
|
|
1358
|
+
if (typeNode === void 0) {
|
|
1359
|
+
return null;
|
|
1360
|
+
}
|
|
1361
|
+
return resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker);
|
|
1362
|
+
}
|
|
1363
|
+
function resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker) {
|
|
1364
|
+
if (ts4.isParenthesizedTypeNode(typeNode)) {
|
|
1365
|
+
return resolveRegisteredCustomTypeFromTypeNode(typeNode.type, extensionRegistry, checker);
|
|
1366
|
+
}
|
|
1367
|
+
const typeName = getTypeNodeRegistrationName(typeNode);
|
|
1368
|
+
if (typeName === null) {
|
|
1369
|
+
return null;
|
|
1370
|
+
}
|
|
1371
|
+
const registration = extensionRegistry.findTypeByName(typeName);
|
|
1372
|
+
if (registration !== void 0) {
|
|
1373
|
+
return {
|
|
1374
|
+
kind: "custom",
|
|
1375
|
+
typeId: `${registration.extensionId}/${registration.registration.typeName}`,
|
|
1376
|
+
payload: null
|
|
1377
|
+
};
|
|
1378
|
+
}
|
|
1379
|
+
if (ts4.isTypeReferenceNode(typeNode) && ts4.isIdentifier(typeNode.typeName)) {
|
|
1380
|
+
const aliasDecl = checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts4.isTypeAliasDeclaration);
|
|
1381
|
+
if (aliasDecl !== void 0) {
|
|
1382
|
+
return resolveRegisteredCustomTypeFromTypeNode(aliasDecl.type, extensionRegistry, checker);
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
return null;
|
|
1386
|
+
}
|
|
1387
|
+
function extractTypeNodeFromSource(sourceNode) {
|
|
1388
|
+
if (ts4.isPropertyDeclaration(sourceNode) || ts4.isPropertySignature(sourceNode) || ts4.isParameter(sourceNode) || ts4.isTypeAliasDeclaration(sourceNode)) {
|
|
1389
|
+
return sourceNode.type;
|
|
1390
|
+
}
|
|
1391
|
+
if (ts4.isTypeNode(sourceNode)) {
|
|
1392
|
+
return sourceNode;
|
|
1393
|
+
}
|
|
1394
|
+
return void 0;
|
|
1395
|
+
}
|
|
1396
|
+
function getTypeNodeRegistrationName(typeNode) {
|
|
1397
|
+
if (ts4.isTypeReferenceNode(typeNode)) {
|
|
1398
|
+
return ts4.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : typeNode.typeName.right.text;
|
|
1399
|
+
}
|
|
1400
|
+
if (ts4.isParenthesizedTypeNode(typeNode)) {
|
|
1401
|
+
return getTypeNodeRegistrationName(typeNode.type);
|
|
1402
|
+
}
|
|
1403
|
+
if (typeNode.kind === ts4.SyntaxKind.BigIntKeyword || typeNode.kind === ts4.SyntaxKind.StringKeyword || typeNode.kind === ts4.SyntaxKind.NumberKeyword || typeNode.kind === ts4.SyntaxKind.BooleanKeyword) {
|
|
1404
|
+
return typeNode.getText();
|
|
1405
|
+
}
|
|
1406
|
+
return null;
|
|
1407
|
+
}
|
|
1408
|
+
function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
1409
|
+
const customType = resolveRegisteredCustomType(sourceNode, extensionRegistry, checker);
|
|
1410
|
+
if (customType) {
|
|
1411
|
+
return customType;
|
|
1412
|
+
}
|
|
1022
1413
|
if (type.flags & ts4.TypeFlags.String) {
|
|
1023
1414
|
return { kind: "primitive", primitiveKind: "string" };
|
|
1024
1415
|
}
|
|
@@ -1047,88 +1438,162 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
|
|
|
1047
1438
|
};
|
|
1048
1439
|
}
|
|
1049
1440
|
if (type.isUnion()) {
|
|
1050
|
-
return resolveUnionType(
|
|
1441
|
+
return resolveUnionType(
|
|
1442
|
+
type,
|
|
1443
|
+
checker,
|
|
1444
|
+
file,
|
|
1445
|
+
typeRegistry,
|
|
1446
|
+
visiting,
|
|
1447
|
+
sourceNode,
|
|
1448
|
+
extensionRegistry
|
|
1449
|
+
);
|
|
1051
1450
|
}
|
|
1052
1451
|
if (checker.isArrayType(type)) {
|
|
1053
|
-
return resolveArrayType(
|
|
1452
|
+
return resolveArrayType(
|
|
1453
|
+
type,
|
|
1454
|
+
checker,
|
|
1455
|
+
file,
|
|
1456
|
+
typeRegistry,
|
|
1457
|
+
visiting,
|
|
1458
|
+
sourceNode,
|
|
1459
|
+
extensionRegistry
|
|
1460
|
+
);
|
|
1054
1461
|
}
|
|
1055
1462
|
if (isObjectType(type)) {
|
|
1056
|
-
return resolveObjectType(type, checker, file, typeRegistry, visiting);
|
|
1463
|
+
return resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry);
|
|
1057
1464
|
}
|
|
1058
1465
|
return { kind: "primitive", primitiveKind: "string" };
|
|
1059
1466
|
}
|
|
1060
|
-
function resolveUnionType(type, checker, file, typeRegistry, visiting) {
|
|
1467
|
+
function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
1468
|
+
const typeName = getNamedTypeName(type);
|
|
1469
|
+
const namedDecl = getNamedTypeDeclaration(type);
|
|
1470
|
+
if (typeName && typeName in typeRegistry) {
|
|
1471
|
+
return { kind: "reference", name: typeName, typeArguments: [] };
|
|
1472
|
+
}
|
|
1061
1473
|
const allTypes = type.types;
|
|
1474
|
+
const unionMemberTypeNodes = extractUnionMemberTypeNodes(sourceNode, checker);
|
|
1475
|
+
const nonNullSourceNodes = unionMemberTypeNodes.filter(
|
|
1476
|
+
(memberTypeNode) => !isNullishTypeNode(resolveAliasedTypeNode(memberTypeNode, checker))
|
|
1477
|
+
);
|
|
1062
1478
|
const nonNullTypes = allTypes.filter(
|
|
1063
|
-
(
|
|
1479
|
+
(memberType) => !(memberType.flags & (ts4.TypeFlags.Null | ts4.TypeFlags.Undefined))
|
|
1064
1480
|
);
|
|
1481
|
+
const nonNullMembers = nonNullTypes.map((memberType, index) => ({
|
|
1482
|
+
memberType,
|
|
1483
|
+
sourceNode: nonNullSourceNodes.length === nonNullTypes.length ? nonNullSourceNodes[index] : void 0
|
|
1484
|
+
}));
|
|
1065
1485
|
const hasNull = allTypes.some((t) => t.flags & ts4.TypeFlags.Null);
|
|
1486
|
+
const memberDisplayNames = /* @__PURE__ */ new Map();
|
|
1487
|
+
if (namedDecl) {
|
|
1488
|
+
for (const [value, label] of extractDisplayNameMetadata(namedDecl).memberDisplayNames) {
|
|
1489
|
+
memberDisplayNames.set(value, label);
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
if (sourceNode) {
|
|
1493
|
+
for (const [value, label] of extractDisplayNameMetadata(sourceNode).memberDisplayNames) {
|
|
1494
|
+
memberDisplayNames.set(value, label);
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
const registerNamed = (result) => {
|
|
1498
|
+
if (!typeName) {
|
|
1499
|
+
return result;
|
|
1500
|
+
}
|
|
1501
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
1502
|
+
typeRegistry[typeName] = {
|
|
1503
|
+
name: typeName,
|
|
1504
|
+
type: result,
|
|
1505
|
+
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
1506
|
+
provenance: provenanceForDeclaration(namedDecl ?? sourceNode, file)
|
|
1507
|
+
};
|
|
1508
|
+
return { kind: "reference", name: typeName, typeArguments: [] };
|
|
1509
|
+
};
|
|
1510
|
+
const applyMemberLabels = (members2) => members2.map((value) => {
|
|
1511
|
+
const displayName = memberDisplayNames.get(String(value));
|
|
1512
|
+
return displayName !== void 0 ? { value, displayName } : { value };
|
|
1513
|
+
});
|
|
1066
1514
|
const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts4.TypeFlags.BooleanLiteral);
|
|
1067
1515
|
if (isBooleanUnion2) {
|
|
1068
1516
|
const boolNode = { kind: "primitive", primitiveKind: "boolean" };
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
}
|
|
1075
|
-
return boolNode;
|
|
1517
|
+
const result = hasNull ? {
|
|
1518
|
+
kind: "union",
|
|
1519
|
+
members: [boolNode, { kind: "primitive", primitiveKind: "null" }]
|
|
1520
|
+
} : boolNode;
|
|
1521
|
+
return registerNamed(result);
|
|
1076
1522
|
}
|
|
1077
1523
|
const allStringLiterals = nonNullTypes.every((t) => t.isStringLiteral());
|
|
1078
1524
|
if (allStringLiterals && nonNullTypes.length > 0) {
|
|
1079
1525
|
const stringTypes = nonNullTypes.filter((t) => t.isStringLiteral());
|
|
1080
1526
|
const enumNode = {
|
|
1081
1527
|
kind: "enum",
|
|
1082
|
-
members: stringTypes.map((t) =>
|
|
1528
|
+
members: applyMemberLabels(stringTypes.map((t) => t.value))
|
|
1083
1529
|
};
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
}
|
|
1090
|
-
return enumNode;
|
|
1530
|
+
const result = hasNull ? {
|
|
1531
|
+
kind: "union",
|
|
1532
|
+
members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
|
|
1533
|
+
} : enumNode;
|
|
1534
|
+
return registerNamed(result);
|
|
1091
1535
|
}
|
|
1092
1536
|
const allNumberLiterals = nonNullTypes.every((t) => t.isNumberLiteral());
|
|
1093
1537
|
if (allNumberLiterals && nonNullTypes.length > 0) {
|
|
1094
1538
|
const numberTypes = nonNullTypes.filter((t) => t.isNumberLiteral());
|
|
1095
1539
|
const enumNode = {
|
|
1096
1540
|
kind: "enum",
|
|
1097
|
-
members: numberTypes.map((t) =>
|
|
1541
|
+
members: applyMemberLabels(numberTypes.map((t) => t.value))
|
|
1098
1542
|
};
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1543
|
+
const result = hasNull ? {
|
|
1544
|
+
kind: "union",
|
|
1545
|
+
members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
|
|
1546
|
+
} : enumNode;
|
|
1547
|
+
return registerNamed(result);
|
|
1548
|
+
}
|
|
1549
|
+
if (nonNullMembers.length === 1 && nonNullMembers[0]) {
|
|
1550
|
+
const inner = resolveTypeNode(
|
|
1551
|
+
nonNullMembers[0].memberType,
|
|
1552
|
+
checker,
|
|
1553
|
+
file,
|
|
1554
|
+
typeRegistry,
|
|
1555
|
+
visiting,
|
|
1556
|
+
nonNullMembers[0].sourceNode ?? sourceNode,
|
|
1557
|
+
extensionRegistry
|
|
1558
|
+
);
|
|
1559
|
+
const result = hasNull ? {
|
|
1560
|
+
kind: "union",
|
|
1561
|
+
members: [inner, { kind: "primitive", primitiveKind: "null" }]
|
|
1562
|
+
} : inner;
|
|
1563
|
+
return registerNamed(result);
|
|
1564
|
+
}
|
|
1565
|
+
const members = nonNullMembers.map(
|
|
1566
|
+
({ memberType, sourceNode: memberSourceNode }) => resolveTypeNode(
|
|
1567
|
+
memberType,
|
|
1568
|
+
checker,
|
|
1569
|
+
file,
|
|
1570
|
+
typeRegistry,
|
|
1571
|
+
visiting,
|
|
1572
|
+
memberSourceNode ?? sourceNode,
|
|
1573
|
+
extensionRegistry
|
|
1574
|
+
)
|
|
1119
1575
|
);
|
|
1120
1576
|
if (hasNull) {
|
|
1121
1577
|
members.push({ kind: "primitive", primitiveKind: "null" });
|
|
1122
1578
|
}
|
|
1123
|
-
return { kind: "union", members };
|
|
1579
|
+
return registerNamed({ kind: "union", members });
|
|
1124
1580
|
}
|
|
1125
|
-
function resolveArrayType(type, checker, file, typeRegistry, visiting) {
|
|
1581
|
+
function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
1126
1582
|
const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
|
|
1127
1583
|
const elementType = typeArgs?.[0];
|
|
1128
|
-
const
|
|
1584
|
+
const elementSourceNode = extractArrayElementTypeNode(sourceNode, checker);
|
|
1585
|
+
const items = elementType ? resolveTypeNode(
|
|
1586
|
+
elementType,
|
|
1587
|
+
checker,
|
|
1588
|
+
file,
|
|
1589
|
+
typeRegistry,
|
|
1590
|
+
visiting,
|
|
1591
|
+
elementSourceNode,
|
|
1592
|
+
extensionRegistry
|
|
1593
|
+
) : { kind: "primitive", primitiveKind: "string" };
|
|
1129
1594
|
return { kind: "array", items };
|
|
1130
1595
|
}
|
|
1131
|
-
function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
|
|
1596
|
+
function tryResolveRecordType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1132
1597
|
if (type.getProperties().length > 0) {
|
|
1133
1598
|
return null;
|
|
1134
1599
|
}
|
|
@@ -1136,39 +1601,123 @@ function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
|
|
|
1136
1601
|
if (!indexInfo) {
|
|
1137
1602
|
return null;
|
|
1138
1603
|
}
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
}
|
|
1604
|
+
const valueType = resolveTypeNode(
|
|
1605
|
+
indexInfo.type,
|
|
1606
|
+
checker,
|
|
1607
|
+
file,
|
|
1608
|
+
typeRegistry,
|
|
1609
|
+
visiting,
|
|
1610
|
+
void 0,
|
|
1611
|
+
extensionRegistry
|
|
1612
|
+
);
|
|
1613
|
+
return { kind: "record", valueType };
|
|
1149
1614
|
}
|
|
1150
|
-
function
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1615
|
+
function typeNodeContainsReference(type, targetName) {
|
|
1616
|
+
switch (type.kind) {
|
|
1617
|
+
case "reference":
|
|
1618
|
+
return type.name === targetName;
|
|
1619
|
+
case "array":
|
|
1620
|
+
return typeNodeContainsReference(type.items, targetName);
|
|
1621
|
+
case "record":
|
|
1622
|
+
return typeNodeContainsReference(type.valueType, targetName);
|
|
1623
|
+
case "union":
|
|
1624
|
+
return type.members.some((member) => typeNodeContainsReference(member, targetName));
|
|
1625
|
+
case "object":
|
|
1626
|
+
return type.properties.some(
|
|
1627
|
+
(property) => typeNodeContainsReference(property.type, targetName)
|
|
1628
|
+
);
|
|
1629
|
+
case "primitive":
|
|
1630
|
+
case "enum":
|
|
1631
|
+
case "dynamic":
|
|
1632
|
+
case "custom":
|
|
1633
|
+
return false;
|
|
1634
|
+
default: {
|
|
1635
|
+
const _exhaustive = type;
|
|
1636
|
+
return _exhaustive;
|
|
1637
|
+
}
|
|
1154
1638
|
}
|
|
1639
|
+
}
|
|
1640
|
+
function resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1641
|
+
const typeName = getNamedTypeName(type);
|
|
1642
|
+
const namedTypeName = typeName ?? void 0;
|
|
1643
|
+
const namedDecl = getNamedTypeDeclaration(type);
|
|
1644
|
+
const shouldRegisterNamedType = namedTypeName !== void 0 && !(namedTypeName === "Record" && namedDecl?.getSourceFile().fileName !== file);
|
|
1645
|
+
const clearNamedTypeRegistration = () => {
|
|
1646
|
+
if (namedTypeName === void 0 || !shouldRegisterNamedType) {
|
|
1647
|
+
return;
|
|
1648
|
+
}
|
|
1649
|
+
Reflect.deleteProperty(typeRegistry, namedTypeName);
|
|
1650
|
+
};
|
|
1155
1651
|
if (visiting.has(type)) {
|
|
1652
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
1653
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
1654
|
+
}
|
|
1156
1655
|
return { kind: "object", properties: [], additionalProperties: false };
|
|
1157
1656
|
}
|
|
1657
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[namedTypeName]) {
|
|
1658
|
+
typeRegistry[namedTypeName] = {
|
|
1659
|
+
name: namedTypeName,
|
|
1660
|
+
type: RESOLVING_TYPE_PLACEHOLDER,
|
|
1661
|
+
provenance: provenanceForDeclaration(namedDecl, file)
|
|
1662
|
+
};
|
|
1663
|
+
}
|
|
1158
1664
|
visiting.add(type);
|
|
1159
|
-
|
|
1160
|
-
|
|
1665
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType && typeRegistry[namedTypeName]?.type !== void 0) {
|
|
1666
|
+
if (typeRegistry[namedTypeName].type !== RESOLVING_TYPE_PLACEHOLDER) {
|
|
1667
|
+
visiting.delete(type);
|
|
1668
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
const recordNode = tryResolveRecordType(
|
|
1672
|
+
type,
|
|
1673
|
+
checker,
|
|
1674
|
+
file,
|
|
1675
|
+
typeRegistry,
|
|
1676
|
+
visiting,
|
|
1677
|
+
extensionRegistry
|
|
1678
|
+
);
|
|
1679
|
+
if (recordNode) {
|
|
1161
1680
|
visiting.delete(type);
|
|
1162
|
-
|
|
1681
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
1682
|
+
const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, namedTypeName);
|
|
1683
|
+
if (!isRecursiveRecord) {
|
|
1684
|
+
clearNamedTypeRegistration();
|
|
1685
|
+
return recordNode;
|
|
1686
|
+
}
|
|
1687
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
1688
|
+
typeRegistry[namedTypeName] = {
|
|
1689
|
+
name: namedTypeName,
|
|
1690
|
+
type: recordNode,
|
|
1691
|
+
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
1692
|
+
provenance: provenanceForDeclaration(namedDecl, file)
|
|
1693
|
+
};
|
|
1694
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
1695
|
+
}
|
|
1696
|
+
return recordNode;
|
|
1163
1697
|
}
|
|
1164
1698
|
const properties = [];
|
|
1165
|
-
const fieldInfoMap = getNamedTypeFieldNodeInfoMap(
|
|
1699
|
+
const fieldInfoMap = getNamedTypeFieldNodeInfoMap(
|
|
1700
|
+
type,
|
|
1701
|
+
checker,
|
|
1702
|
+
file,
|
|
1703
|
+
typeRegistry,
|
|
1704
|
+
visiting,
|
|
1705
|
+
extensionRegistry
|
|
1706
|
+
);
|
|
1166
1707
|
for (const prop of type.getProperties()) {
|
|
1167
1708
|
const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
|
|
1168
1709
|
if (!declaration) continue;
|
|
1169
1710
|
const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
|
|
1170
1711
|
const optional = !!(prop.flags & ts4.SymbolFlags.Optional);
|
|
1171
|
-
const propTypeNode = resolveTypeNode(
|
|
1712
|
+
const propTypeNode = resolveTypeNode(
|
|
1713
|
+
propType,
|
|
1714
|
+
checker,
|
|
1715
|
+
file,
|
|
1716
|
+
typeRegistry,
|
|
1717
|
+
visiting,
|
|
1718
|
+
declaration,
|
|
1719
|
+
extensionRegistry
|
|
1720
|
+
);
|
|
1172
1721
|
const fieldNodeInfo = fieldInfoMap?.get(prop.name);
|
|
1173
1722
|
properties.push({
|
|
1174
1723
|
name: prop.name,
|
|
@@ -1185,17 +1734,19 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
1185
1734
|
properties,
|
|
1186
1735
|
additionalProperties: true
|
|
1187
1736
|
};
|
|
1188
|
-
if (
|
|
1189
|
-
|
|
1190
|
-
|
|
1737
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
1738
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
1739
|
+
typeRegistry[namedTypeName] = {
|
|
1740
|
+
name: namedTypeName,
|
|
1191
1741
|
type: objectNode,
|
|
1192
|
-
|
|
1742
|
+
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
1743
|
+
provenance: provenanceForDeclaration(namedDecl, file)
|
|
1193
1744
|
};
|
|
1194
|
-
return { kind: "reference", name:
|
|
1745
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
1195
1746
|
}
|
|
1196
1747
|
return objectNode;
|
|
1197
1748
|
}
|
|
1198
|
-
function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting) {
|
|
1749
|
+
function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1199
1750
|
const symbols = [type.getSymbol(), type.aliasSymbol].filter(
|
|
1200
1751
|
(s) => s?.declarations != null && s.declarations.length > 0
|
|
1201
1752
|
);
|
|
@@ -1207,7 +1758,14 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
1207
1758
|
const map = /* @__PURE__ */ new Map();
|
|
1208
1759
|
for (const member of classDecl.members) {
|
|
1209
1760
|
if (ts4.isPropertyDeclaration(member) && ts4.isIdentifier(member.name)) {
|
|
1210
|
-
const fieldNode = analyzeFieldToIR(
|
|
1761
|
+
const fieldNode = analyzeFieldToIR(
|
|
1762
|
+
member,
|
|
1763
|
+
checker,
|
|
1764
|
+
file,
|
|
1765
|
+
typeRegistry,
|
|
1766
|
+
visiting,
|
|
1767
|
+
extensionRegistry
|
|
1768
|
+
);
|
|
1211
1769
|
if (fieldNode) {
|
|
1212
1770
|
map.set(fieldNode.name, {
|
|
1213
1771
|
constraints: [...fieldNode.constraints],
|
|
@@ -1221,7 +1779,14 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
1221
1779
|
}
|
|
1222
1780
|
const interfaceDecl = declarations.find(ts4.isInterfaceDeclaration);
|
|
1223
1781
|
if (interfaceDecl) {
|
|
1224
|
-
return buildFieldNodeInfoMap(
|
|
1782
|
+
return buildFieldNodeInfoMap(
|
|
1783
|
+
interfaceDecl.members,
|
|
1784
|
+
checker,
|
|
1785
|
+
file,
|
|
1786
|
+
typeRegistry,
|
|
1787
|
+
visiting,
|
|
1788
|
+
extensionRegistry
|
|
1789
|
+
);
|
|
1225
1790
|
}
|
|
1226
1791
|
const typeAliasDecl = declarations.find(ts4.isTypeAliasDeclaration);
|
|
1227
1792
|
if (typeAliasDecl && ts4.isTypeLiteralNode(typeAliasDecl.type)) {
|
|
@@ -1230,17 +1795,68 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
1230
1795
|
checker,
|
|
1231
1796
|
file,
|
|
1232
1797
|
typeRegistry,
|
|
1233
|
-
visiting
|
|
1798
|
+
visiting,
|
|
1799
|
+
extensionRegistry
|
|
1234
1800
|
);
|
|
1235
1801
|
}
|
|
1236
1802
|
}
|
|
1237
1803
|
return null;
|
|
1238
1804
|
}
|
|
1239
|
-
function
|
|
1805
|
+
function extractArrayElementTypeNode(sourceNode, checker) {
|
|
1806
|
+
const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
|
|
1807
|
+
if (typeNode === void 0) {
|
|
1808
|
+
return void 0;
|
|
1809
|
+
}
|
|
1810
|
+
const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
|
|
1811
|
+
if (ts4.isArrayTypeNode(resolvedTypeNode)) {
|
|
1812
|
+
return resolvedTypeNode.elementType;
|
|
1813
|
+
}
|
|
1814
|
+
if (ts4.isTypeReferenceNode(resolvedTypeNode) && ts4.isIdentifier(resolvedTypeNode.typeName) && resolvedTypeNode.typeName.text === "Array" && resolvedTypeNode.typeArguments?.[0]) {
|
|
1815
|
+
return resolvedTypeNode.typeArguments[0];
|
|
1816
|
+
}
|
|
1817
|
+
return void 0;
|
|
1818
|
+
}
|
|
1819
|
+
function extractUnionMemberTypeNodes(sourceNode, checker) {
|
|
1820
|
+
const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
|
|
1821
|
+
if (!typeNode) {
|
|
1822
|
+
return [];
|
|
1823
|
+
}
|
|
1824
|
+
const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
|
|
1825
|
+
return ts4.isUnionTypeNode(resolvedTypeNode) ? [...resolvedTypeNode.types] : [];
|
|
1826
|
+
}
|
|
1827
|
+
function resolveAliasedTypeNode(typeNode, checker, visited = /* @__PURE__ */ new Set()) {
|
|
1828
|
+
if (ts4.isParenthesizedTypeNode(typeNode)) {
|
|
1829
|
+
return resolveAliasedTypeNode(typeNode.type, checker, visited);
|
|
1830
|
+
}
|
|
1831
|
+
if (!ts4.isTypeReferenceNode(typeNode) || !ts4.isIdentifier(typeNode.typeName)) {
|
|
1832
|
+
return typeNode;
|
|
1833
|
+
}
|
|
1834
|
+
const symbol = checker.getSymbolAtLocation(typeNode.typeName);
|
|
1835
|
+
const aliasDecl = symbol?.declarations?.find(ts4.isTypeAliasDeclaration);
|
|
1836
|
+
if (aliasDecl === void 0 || visited.has(aliasDecl)) {
|
|
1837
|
+
return typeNode;
|
|
1838
|
+
}
|
|
1839
|
+
visited.add(aliasDecl);
|
|
1840
|
+
return resolveAliasedTypeNode(aliasDecl.type, checker, visited);
|
|
1841
|
+
}
|
|
1842
|
+
function isNullishTypeNode(typeNode) {
|
|
1843
|
+
if (typeNode.kind === ts4.SyntaxKind.NullKeyword || typeNode.kind === ts4.SyntaxKind.UndefinedKeyword) {
|
|
1844
|
+
return true;
|
|
1845
|
+
}
|
|
1846
|
+
return ts4.isLiteralTypeNode(typeNode) && (typeNode.literal.kind === ts4.SyntaxKind.NullKeyword || typeNode.literal.kind === ts4.SyntaxKind.UndefinedKeyword);
|
|
1847
|
+
}
|
|
1848
|
+
function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1240
1849
|
const map = /* @__PURE__ */ new Map();
|
|
1241
1850
|
for (const member of members) {
|
|
1242
1851
|
if (ts4.isPropertySignature(member)) {
|
|
1243
|
-
const fieldNode = analyzeInterfacePropertyToIR(
|
|
1852
|
+
const fieldNode = analyzeInterfacePropertyToIR(
|
|
1853
|
+
member,
|
|
1854
|
+
checker,
|
|
1855
|
+
file,
|
|
1856
|
+
typeRegistry,
|
|
1857
|
+
visiting,
|
|
1858
|
+
extensionRegistry
|
|
1859
|
+
);
|
|
1244
1860
|
if (fieldNode) {
|
|
1245
1861
|
map.set(fieldNode.name, {
|
|
1246
1862
|
constraints: [...fieldNode.constraints],
|
|
@@ -1253,7 +1869,7 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting) {
|
|
|
1253
1869
|
return map;
|
|
1254
1870
|
}
|
|
1255
1871
|
var MAX_ALIAS_CHAIN_DEPTH = 8;
|
|
1256
|
-
function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
|
|
1872
|
+
function extractTypeAliasConstraintNodes(typeNode, checker, file, extensionRegistry, depth = 0) {
|
|
1257
1873
|
if (!ts4.isTypeReferenceNode(typeNode)) return [];
|
|
1258
1874
|
if (depth >= MAX_ALIAS_CHAIN_DEPTH) {
|
|
1259
1875
|
const aliasName = typeNode.typeName.getText();
|
|
@@ -1266,8 +1882,29 @@ function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
|
|
|
1266
1882
|
const aliasDecl = symbol.declarations.find(ts4.isTypeAliasDeclaration);
|
|
1267
1883
|
if (!aliasDecl) return [];
|
|
1268
1884
|
if (ts4.isTypeLiteralNode(aliasDecl.type)) return [];
|
|
1269
|
-
const
|
|
1270
|
-
|
|
1885
|
+
const aliasFieldType = resolveTypeNode(
|
|
1886
|
+
checker.getTypeAtLocation(aliasDecl.type),
|
|
1887
|
+
checker,
|
|
1888
|
+
file,
|
|
1889
|
+
{},
|
|
1890
|
+
/* @__PURE__ */ new Set(),
|
|
1891
|
+
aliasDecl.type,
|
|
1892
|
+
extensionRegistry
|
|
1893
|
+
);
|
|
1894
|
+
const constraints = extractJSDocConstraintNodes(
|
|
1895
|
+
aliasDecl,
|
|
1896
|
+
file,
|
|
1897
|
+
makeParseOptions(extensionRegistry, aliasFieldType)
|
|
1898
|
+
);
|
|
1899
|
+
constraints.push(
|
|
1900
|
+
...extractTypeAliasConstraintNodes(
|
|
1901
|
+
aliasDecl.type,
|
|
1902
|
+
checker,
|
|
1903
|
+
file,
|
|
1904
|
+
extensionRegistry,
|
|
1905
|
+
depth + 1
|
|
1906
|
+
)
|
|
1907
|
+
);
|
|
1271
1908
|
return constraints;
|
|
1272
1909
|
}
|
|
1273
1910
|
function provenanceForNode(node, file) {
|
|
@@ -1283,6 +1920,12 @@ function provenanceForNode(node, file) {
|
|
|
1283
1920
|
function provenanceForFile(file) {
|
|
1284
1921
|
return { surface: "tsdoc", file, line: 0, column: 0 };
|
|
1285
1922
|
}
|
|
1923
|
+
function provenanceForDeclaration(node, file) {
|
|
1924
|
+
if (!node) {
|
|
1925
|
+
return provenanceForFile(file);
|
|
1926
|
+
}
|
|
1927
|
+
return provenanceForNode(node, file);
|
|
1928
|
+
}
|
|
1286
1929
|
function getNamedTypeName(type) {
|
|
1287
1930
|
const symbol = type.getSymbol();
|
|
1288
1931
|
if (symbol?.declarations) {
|
|
@@ -1301,6 +1944,20 @@ function getNamedTypeName(type) {
|
|
|
1301
1944
|
}
|
|
1302
1945
|
return null;
|
|
1303
1946
|
}
|
|
1947
|
+
function getNamedTypeDeclaration(type) {
|
|
1948
|
+
const symbol = type.getSymbol();
|
|
1949
|
+
if (symbol?.declarations) {
|
|
1950
|
+
const decl = symbol.declarations[0];
|
|
1951
|
+
if (decl && (ts4.isClassDeclaration(decl) || ts4.isInterfaceDeclaration(decl) || ts4.isTypeAliasDeclaration(decl))) {
|
|
1952
|
+
return decl;
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
const aliasSymbol = type.aliasSymbol;
|
|
1956
|
+
if (aliasSymbol?.declarations) {
|
|
1957
|
+
return aliasSymbol.declarations.find(ts4.isTypeAliasDeclaration);
|
|
1958
|
+
}
|
|
1959
|
+
return void 0;
|
|
1960
|
+
}
|
|
1304
1961
|
function analyzeMethod(method, checker) {
|
|
1305
1962
|
if (!ts4.isIdentifier(method.name)) {
|
|
1306
1963
|
return null;
|
|
@@ -1360,6 +2017,9 @@ function generateJsonSchemaFromIR(ir, options) {
|
|
|
1360
2017
|
const ctx = makeContext(options);
|
|
1361
2018
|
for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
|
|
1362
2019
|
ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
|
|
2020
|
+
if (typeDef.annotations && typeDef.annotations.length > 0) {
|
|
2021
|
+
applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
|
|
2022
|
+
}
|
|
1363
2023
|
}
|
|
1364
2024
|
const properties = {};
|
|
1365
2025
|
const required = [];
|
|
@@ -1371,6 +2031,9 @@ function generateJsonSchemaFromIR(ir, options) {
|
|
|
1371
2031
|
properties,
|
|
1372
2032
|
...uniqueRequired.length > 0 && { required: uniqueRequired }
|
|
1373
2033
|
};
|
|
2034
|
+
if (ir.annotations && ir.annotations.length > 0) {
|
|
2035
|
+
applyAnnotations(result, ir.annotations, ctx);
|
|
2036
|
+
}
|
|
1374
2037
|
if (Object.keys(ctx.defs).length > 0) {
|
|
1375
2038
|
result.$defs = ctx.defs;
|
|
1376
2039
|
}
|
|
@@ -1400,22 +2063,51 @@ function collectFields(elements, properties, required, ctx) {
|
|
|
1400
2063
|
}
|
|
1401
2064
|
function generateFieldSchema(field, ctx) {
|
|
1402
2065
|
const schema = generateTypeNode(field.type, ctx);
|
|
2066
|
+
const itemStringSchema = schema.type === "array" && schema.items?.type === "string" ? schema.items : void 0;
|
|
1403
2067
|
const directConstraints = [];
|
|
2068
|
+
const itemConstraints = [];
|
|
1404
2069
|
const pathConstraints = [];
|
|
1405
2070
|
for (const c of field.constraints) {
|
|
1406
2071
|
if (c.path) {
|
|
1407
2072
|
pathConstraints.push(c);
|
|
2073
|
+
} else if (itemStringSchema !== void 0 && isStringItemConstraint(c)) {
|
|
2074
|
+
itemConstraints.push(c);
|
|
1408
2075
|
} else {
|
|
1409
2076
|
directConstraints.push(c);
|
|
1410
2077
|
}
|
|
1411
2078
|
}
|
|
1412
2079
|
applyConstraints(schema, directConstraints, ctx);
|
|
1413
|
-
|
|
2080
|
+
if (itemStringSchema !== void 0) {
|
|
2081
|
+
applyConstraints(itemStringSchema, itemConstraints, ctx);
|
|
2082
|
+
}
|
|
2083
|
+
const rootAnnotations = [];
|
|
2084
|
+
const itemAnnotations = [];
|
|
2085
|
+
for (const annotation of field.annotations) {
|
|
2086
|
+
if (itemStringSchema !== void 0 && annotation.annotationKind === "format") {
|
|
2087
|
+
itemAnnotations.push(annotation);
|
|
2088
|
+
} else {
|
|
2089
|
+
rootAnnotations.push(annotation);
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
applyAnnotations(schema, rootAnnotations, ctx);
|
|
2093
|
+
if (itemStringSchema !== void 0) {
|
|
2094
|
+
applyAnnotations(itemStringSchema, itemAnnotations, ctx);
|
|
2095
|
+
}
|
|
1414
2096
|
if (pathConstraints.length === 0) {
|
|
1415
2097
|
return schema;
|
|
1416
2098
|
}
|
|
1417
2099
|
return applyPathTargetedConstraints(schema, pathConstraints, ctx);
|
|
1418
2100
|
}
|
|
2101
|
+
function isStringItemConstraint(constraint) {
|
|
2102
|
+
switch (constraint.constraintKind) {
|
|
2103
|
+
case "minLength":
|
|
2104
|
+
case "maxLength":
|
|
2105
|
+
case "pattern":
|
|
2106
|
+
return true;
|
|
2107
|
+
default:
|
|
2108
|
+
return false;
|
|
2109
|
+
}
|
|
2110
|
+
}
|
|
1419
2111
|
function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
|
|
1420
2112
|
if (schema.type === "array" && schema.items) {
|
|
1421
2113
|
schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx);
|
|
@@ -1633,6 +2325,9 @@ function applyConstraints(schema, constraints, ctx) {
|
|
|
1633
2325
|
case "uniqueItems":
|
|
1634
2326
|
schema.uniqueItems = constraint.value;
|
|
1635
2327
|
break;
|
|
2328
|
+
case "const":
|
|
2329
|
+
schema.const = constraint.value;
|
|
2330
|
+
break;
|
|
1636
2331
|
case "allowedMembers":
|
|
1637
2332
|
break;
|
|
1638
2333
|
case "custom":
|
|
@@ -1657,8 +2352,14 @@ function applyAnnotations(schema, annotations, ctx) {
|
|
|
1657
2352
|
case "defaultValue":
|
|
1658
2353
|
schema.default = annotation.value;
|
|
1659
2354
|
break;
|
|
2355
|
+
case "format":
|
|
2356
|
+
schema.format = annotation.value;
|
|
2357
|
+
break;
|
|
1660
2358
|
case "deprecated":
|
|
1661
2359
|
schema.deprecated = true;
|
|
2360
|
+
if (annotation.message !== void 0 && annotation.message !== "") {
|
|
2361
|
+
schema["x-formspec-deprecation-description"] = annotation.message;
|
|
2362
|
+
}
|
|
1662
2363
|
break;
|
|
1663
2364
|
case "placeholder":
|
|
1664
2365
|
break;
|
|
@@ -1690,7 +2391,12 @@ function applyCustomConstraint(schema, constraint, ctx) {
|
|
|
1690
2391
|
`Cannot generate JSON Schema for custom constraint "${constraint.constraintId}" without a matching extension registration`
|
|
1691
2392
|
);
|
|
1692
2393
|
}
|
|
1693
|
-
|
|
2394
|
+
assignVendorPrefixedExtensionKeywords(
|
|
2395
|
+
schema,
|
|
2396
|
+
registration.toJsonSchema(constraint.payload, ctx.vendorPrefix),
|
|
2397
|
+
ctx.vendorPrefix,
|
|
2398
|
+
`custom constraint "${constraint.constraintId}"`
|
|
2399
|
+
);
|
|
1694
2400
|
}
|
|
1695
2401
|
function applyCustomAnnotation(schema, annotation, ctx) {
|
|
1696
2402
|
const registration = ctx.extensionRegistry?.findAnnotation(annotation.annotationId);
|
|
@@ -1702,7 +2408,22 @@ function applyCustomAnnotation(schema, annotation, ctx) {
|
|
|
1702
2408
|
if (registration.toJsonSchema === void 0) {
|
|
1703
2409
|
return;
|
|
1704
2410
|
}
|
|
1705
|
-
|
|
2411
|
+
assignVendorPrefixedExtensionKeywords(
|
|
2412
|
+
schema,
|
|
2413
|
+
registration.toJsonSchema(annotation.value, ctx.vendorPrefix),
|
|
2414
|
+
ctx.vendorPrefix,
|
|
2415
|
+
`custom annotation "${annotation.annotationId}"`
|
|
2416
|
+
);
|
|
2417
|
+
}
|
|
2418
|
+
function assignVendorPrefixedExtensionKeywords(schema, extensionSchema, vendorPrefix, source) {
|
|
2419
|
+
for (const [key, value] of Object.entries(extensionSchema)) {
|
|
2420
|
+
if (!key.startsWith(`${vendorPrefix}-`)) {
|
|
2421
|
+
throw new Error(
|
|
2422
|
+
`Cannot apply ${source}: extension hooks may only emit "${vendorPrefix}-*" JSON Schema keywords`
|
|
2423
|
+
);
|
|
2424
|
+
}
|
|
2425
|
+
schema[key] = value;
|
|
2426
|
+
}
|
|
1706
2427
|
}
|
|
1707
2428
|
|
|
1708
2429
|
// src/ui-schema/schema.ts
|
|
@@ -1840,25 +2561,31 @@ function createShowRule(fieldName, value) {
|
|
|
1840
2561
|
}
|
|
1841
2562
|
};
|
|
1842
2563
|
}
|
|
2564
|
+
function flattenConditionSchema(scope, schema) {
|
|
2565
|
+
if (schema.allOf === void 0) {
|
|
2566
|
+
if (scope === "#") {
|
|
2567
|
+
return [schema];
|
|
2568
|
+
}
|
|
2569
|
+
const fieldName = scope.replace("#/properties/", "");
|
|
2570
|
+
return [
|
|
2571
|
+
{
|
|
2572
|
+
properties: {
|
|
2573
|
+
[fieldName]: schema
|
|
2574
|
+
}
|
|
2575
|
+
}
|
|
2576
|
+
];
|
|
2577
|
+
}
|
|
2578
|
+
return schema.allOf.flatMap((member) => flattenConditionSchema(scope, member));
|
|
2579
|
+
}
|
|
1843
2580
|
function combineRules(parentRule, childRule) {
|
|
1844
|
-
const parentCondition = parentRule.condition;
|
|
1845
|
-
const childCondition = childRule.condition;
|
|
1846
2581
|
return {
|
|
1847
2582
|
effect: "SHOW",
|
|
1848
2583
|
condition: {
|
|
1849
2584
|
scope: "#",
|
|
1850
2585
|
schema: {
|
|
1851
2586
|
allOf: [
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
[parentCondition.scope.replace("#/properties/", "")]: parentCondition.schema
|
|
1855
|
-
}
|
|
1856
|
-
},
|
|
1857
|
-
{
|
|
1858
|
-
properties: {
|
|
1859
|
-
[childCondition.scope.replace("#/properties/", "")]: childCondition.schema
|
|
1860
|
-
}
|
|
1861
|
-
}
|
|
2587
|
+
...flattenConditionSchema(parentRule.condition.scope, parentRule.condition.schema),
|
|
2588
|
+
...flattenConditionSchema(childRule.condition.scope, childRule.condition.schema)
|
|
1862
2589
|
]
|
|
1863
2590
|
}
|
|
1864
2591
|
}
|
|
@@ -1866,10 +2593,14 @@ function combineRules(parentRule, childRule) {
|
|
|
1866
2593
|
}
|
|
1867
2594
|
function fieldNodeToControl(field, parentRule) {
|
|
1868
2595
|
const displayNameAnnotation = field.annotations.find((a) => a.annotationKind === "displayName");
|
|
2596
|
+
const placeholderAnnotation = field.annotations.find((a) => a.annotationKind === "placeholder");
|
|
1869
2597
|
const control = {
|
|
1870
2598
|
type: "Control",
|
|
1871
2599
|
scope: fieldToScope(field.name),
|
|
1872
2600
|
...displayNameAnnotation !== void 0 && { label: displayNameAnnotation.value },
|
|
2601
|
+
...placeholderAnnotation !== void 0 && {
|
|
2602
|
+
options: { placeholder: placeholderAnnotation.value }
|
|
2603
|
+
},
|
|
1873
2604
|
...parentRule !== void 0 && { rule: parentRule }
|
|
1874
2605
|
};
|
|
1875
2606
|
return control;
|
|
@@ -1919,15 +2650,16 @@ function generateUiSchemaFromIR(ir) {
|
|
|
1919
2650
|
}
|
|
1920
2651
|
|
|
1921
2652
|
// src/generators/class-schema.ts
|
|
1922
|
-
function generateClassSchemas(analysis, source) {
|
|
2653
|
+
function generateClassSchemas(analysis, source, options) {
|
|
1923
2654
|
const ir = canonicalizeTSDoc(analysis, source);
|
|
1924
2655
|
return {
|
|
1925
|
-
jsonSchema: generateJsonSchemaFromIR(ir),
|
|
2656
|
+
jsonSchema: generateJsonSchemaFromIR(ir, options),
|
|
1926
2657
|
uiSchema: generateUiSchemaFromIR(ir)
|
|
1927
2658
|
};
|
|
1928
2659
|
}
|
|
1929
2660
|
|
|
1930
2661
|
// src/validate/constraint-validator.ts
|
|
2662
|
+
var import_core4 = require("@formspec/core");
|
|
1931
2663
|
function addContradiction(ctx, message, primary, related) {
|
|
1932
2664
|
ctx.diagnostics.push({
|
|
1933
2665
|
code: "CONTRADICTING_CONSTRAINTS",
|
|
@@ -1955,6 +2687,15 @@ function addUnknownExtension(ctx, message, primary) {
|
|
|
1955
2687
|
relatedLocations: []
|
|
1956
2688
|
});
|
|
1957
2689
|
}
|
|
2690
|
+
function addUnknownPathTarget(ctx, message, primary) {
|
|
2691
|
+
ctx.diagnostics.push({
|
|
2692
|
+
code: "UNKNOWN_PATH_TARGET",
|
|
2693
|
+
message,
|
|
2694
|
+
severity: "error",
|
|
2695
|
+
primaryLocation: primary,
|
|
2696
|
+
relatedLocations: []
|
|
2697
|
+
});
|
|
2698
|
+
}
|
|
1958
2699
|
function addConstraintBroadening(ctx, message, primary, related) {
|
|
1959
2700
|
ctx.diagnostics.push({
|
|
1960
2701
|
code: "CONSTRAINT_BROADENING",
|
|
@@ -1964,6 +2705,13 @@ function addConstraintBroadening(ctx, message, primary, related) {
|
|
|
1964
2705
|
relatedLocations: [related]
|
|
1965
2706
|
});
|
|
1966
2707
|
}
|
|
2708
|
+
function getExtensionIdFromConstraintId(constraintId) {
|
|
2709
|
+
const separator = constraintId.lastIndexOf("/");
|
|
2710
|
+
if (separator <= 0) {
|
|
2711
|
+
return null;
|
|
2712
|
+
}
|
|
2713
|
+
return constraintId.slice(0, separator);
|
|
2714
|
+
}
|
|
1967
2715
|
function findNumeric(constraints, constraintKind) {
|
|
1968
2716
|
return constraints.find((c) => c.constraintKind === constraintKind);
|
|
1969
2717
|
}
|
|
@@ -1975,6 +2723,45 @@ function findAllowedMembers(constraints) {
|
|
|
1975
2723
|
(c) => c.constraintKind === "allowedMembers"
|
|
1976
2724
|
);
|
|
1977
2725
|
}
|
|
2726
|
+
function findConstConstraints(constraints) {
|
|
2727
|
+
return constraints.filter(
|
|
2728
|
+
(c) => c.constraintKind === "const"
|
|
2729
|
+
);
|
|
2730
|
+
}
|
|
2731
|
+
function jsonValueEquals(left, right) {
|
|
2732
|
+
if (left === right) {
|
|
2733
|
+
return true;
|
|
2734
|
+
}
|
|
2735
|
+
if (Array.isArray(left) || Array.isArray(right)) {
|
|
2736
|
+
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
|
|
2737
|
+
return false;
|
|
2738
|
+
}
|
|
2739
|
+
return left.every((item, index) => jsonValueEquals(item, right[index]));
|
|
2740
|
+
}
|
|
2741
|
+
if (isJsonObject(left) || isJsonObject(right)) {
|
|
2742
|
+
if (!isJsonObject(left) || !isJsonObject(right)) {
|
|
2743
|
+
return false;
|
|
2744
|
+
}
|
|
2745
|
+
const leftKeys = Object.keys(left).sort();
|
|
2746
|
+
const rightKeys = Object.keys(right).sort();
|
|
2747
|
+
if (leftKeys.length !== rightKeys.length) {
|
|
2748
|
+
return false;
|
|
2749
|
+
}
|
|
2750
|
+
return leftKeys.every((key, index) => {
|
|
2751
|
+
const rightKey = rightKeys[index];
|
|
2752
|
+
if (rightKey !== key) {
|
|
2753
|
+
return false;
|
|
2754
|
+
}
|
|
2755
|
+
const leftValue = left[key];
|
|
2756
|
+
const rightValue = right[rightKey];
|
|
2757
|
+
return leftValue !== void 0 && rightValue !== void 0 && jsonValueEquals(leftValue, rightValue);
|
|
2758
|
+
});
|
|
2759
|
+
}
|
|
2760
|
+
return false;
|
|
2761
|
+
}
|
|
2762
|
+
function isJsonObject(value) {
|
|
2763
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2764
|
+
}
|
|
1978
2765
|
function isOrderedBoundConstraint(constraint) {
|
|
1979
2766
|
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";
|
|
1980
2767
|
}
|
|
@@ -2095,6 +2882,112 @@ function checkConstraintBroadening(ctx, fieldName, constraints) {
|
|
|
2095
2882
|
strongestByKey.set(key, constraint);
|
|
2096
2883
|
}
|
|
2097
2884
|
}
|
|
2885
|
+
function compareCustomConstraintStrength(current, previous) {
|
|
2886
|
+
const order = current.comparePayloads(current.constraint.payload, previous.constraint.payload);
|
|
2887
|
+
const equalPayloadTiebreaker = order === 0 ? compareSemanticInclusivity(current.role.inclusive, previous.role.inclusive) : order;
|
|
2888
|
+
switch (current.role.bound) {
|
|
2889
|
+
case "lower":
|
|
2890
|
+
return equalPayloadTiebreaker;
|
|
2891
|
+
case "upper":
|
|
2892
|
+
return equalPayloadTiebreaker === 0 ? 0 : -equalPayloadTiebreaker;
|
|
2893
|
+
case "exact":
|
|
2894
|
+
return order === 0 ? 0 : Number.NaN;
|
|
2895
|
+
default: {
|
|
2896
|
+
const _exhaustive = current.role.bound;
|
|
2897
|
+
return _exhaustive;
|
|
2898
|
+
}
|
|
2899
|
+
}
|
|
2900
|
+
}
|
|
2901
|
+
function compareSemanticInclusivity(currentInclusive, previousInclusive) {
|
|
2902
|
+
if (currentInclusive === previousInclusive) {
|
|
2903
|
+
return 0;
|
|
2904
|
+
}
|
|
2905
|
+
return currentInclusive ? -1 : 1;
|
|
2906
|
+
}
|
|
2907
|
+
function customConstraintsContradict(lower, upper) {
|
|
2908
|
+
const order = lower.comparePayloads(lower.constraint.payload, upper.constraint.payload);
|
|
2909
|
+
if (order > 0) {
|
|
2910
|
+
return true;
|
|
2911
|
+
}
|
|
2912
|
+
if (order < 0) {
|
|
2913
|
+
return false;
|
|
2914
|
+
}
|
|
2915
|
+
return !lower.role.inclusive || !upper.role.inclusive;
|
|
2916
|
+
}
|
|
2917
|
+
function describeCustomConstraintTag(constraint) {
|
|
2918
|
+
return constraint.provenance.tagName ?? constraint.constraintId;
|
|
2919
|
+
}
|
|
2920
|
+
function checkCustomConstraintSemantics(ctx, fieldName, constraints) {
|
|
2921
|
+
if (ctx.extensionRegistry === void 0) {
|
|
2922
|
+
return;
|
|
2923
|
+
}
|
|
2924
|
+
const strongestByKey = /* @__PURE__ */ new Map();
|
|
2925
|
+
const lowerByFamily = /* @__PURE__ */ new Map();
|
|
2926
|
+
const upperByFamily = /* @__PURE__ */ new Map();
|
|
2927
|
+
for (const constraint of constraints) {
|
|
2928
|
+
if (constraint.constraintKind !== "custom") {
|
|
2929
|
+
continue;
|
|
2930
|
+
}
|
|
2931
|
+
const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
|
|
2932
|
+
if (registration?.comparePayloads === void 0 || registration.semanticRole === void 0) {
|
|
2933
|
+
continue;
|
|
2934
|
+
}
|
|
2935
|
+
const entry = {
|
|
2936
|
+
constraint,
|
|
2937
|
+
comparePayloads: registration.comparePayloads,
|
|
2938
|
+
role: registration.semanticRole
|
|
2939
|
+
};
|
|
2940
|
+
const familyKey = `${registration.semanticRole.family}:${pathKey(constraint)}`;
|
|
2941
|
+
const boundKey = `${familyKey}:${registration.semanticRole.bound}`;
|
|
2942
|
+
const previous = strongestByKey.get(boundKey);
|
|
2943
|
+
if (previous !== void 0) {
|
|
2944
|
+
const strength = compareCustomConstraintStrength(entry, previous);
|
|
2945
|
+
if (Number.isNaN(strength)) {
|
|
2946
|
+
addContradiction(
|
|
2947
|
+
ctx,
|
|
2948
|
+
`Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} conflicts with ${describeCustomConstraintTag(previous.constraint)}`,
|
|
2949
|
+
constraint.provenance,
|
|
2950
|
+
previous.constraint.provenance
|
|
2951
|
+
);
|
|
2952
|
+
continue;
|
|
2953
|
+
}
|
|
2954
|
+
if (strength < 0) {
|
|
2955
|
+
addConstraintBroadening(
|
|
2956
|
+
ctx,
|
|
2957
|
+
`Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} is broader than earlier ${describeCustomConstraintTag(previous.constraint)}. Constraints can only narrow.`,
|
|
2958
|
+
constraint.provenance,
|
|
2959
|
+
previous.constraint.provenance
|
|
2960
|
+
);
|
|
2961
|
+
continue;
|
|
2962
|
+
}
|
|
2963
|
+
if (strength > 0) {
|
|
2964
|
+
strongestByKey.set(boundKey, entry);
|
|
2965
|
+
}
|
|
2966
|
+
} else {
|
|
2967
|
+
strongestByKey.set(boundKey, entry);
|
|
2968
|
+
}
|
|
2969
|
+
if (registration.semanticRole.bound === "lower") {
|
|
2970
|
+
lowerByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
|
|
2971
|
+
} else if (registration.semanticRole.bound === "upper") {
|
|
2972
|
+
upperByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
|
|
2973
|
+
}
|
|
2974
|
+
}
|
|
2975
|
+
for (const [familyKey, lower] of lowerByFamily) {
|
|
2976
|
+
const upper = upperByFamily.get(familyKey);
|
|
2977
|
+
if (upper === void 0) {
|
|
2978
|
+
continue;
|
|
2979
|
+
}
|
|
2980
|
+
if (!customConstraintsContradict(lower, upper)) {
|
|
2981
|
+
continue;
|
|
2982
|
+
}
|
|
2983
|
+
addContradiction(
|
|
2984
|
+
ctx,
|
|
2985
|
+
`Field "${formatPathTargetFieldName(fieldName, lower.constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(lower.constraint)} contradicts ${describeCustomConstraintTag(upper.constraint)}`,
|
|
2986
|
+
lower.constraint.provenance,
|
|
2987
|
+
upper.constraint.provenance
|
|
2988
|
+
);
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2098
2991
|
function checkNumericContradictions(ctx, fieldName, constraints) {
|
|
2099
2992
|
const min = findNumeric(constraints, "minimum");
|
|
2100
2993
|
const max = findNumeric(constraints, "maximum");
|
|
@@ -2181,6 +3074,25 @@ function checkAllowedMembersContradiction(ctx, fieldName, constraints) {
|
|
|
2181
3074
|
}
|
|
2182
3075
|
}
|
|
2183
3076
|
}
|
|
3077
|
+
function checkConstContradictions(ctx, fieldName, constraints) {
|
|
3078
|
+
const constConstraints = findConstConstraints(constraints);
|
|
3079
|
+
if (constConstraints.length < 2) return;
|
|
3080
|
+
const first = constConstraints[0];
|
|
3081
|
+
if (first === void 0) return;
|
|
3082
|
+
for (let i = 1; i < constConstraints.length; i++) {
|
|
3083
|
+
const current = constConstraints[i];
|
|
3084
|
+
if (current === void 0) continue;
|
|
3085
|
+
if (jsonValueEquals(first.value, current.value)) {
|
|
3086
|
+
continue;
|
|
3087
|
+
}
|
|
3088
|
+
addContradiction(
|
|
3089
|
+
ctx,
|
|
3090
|
+
`Field "${fieldName}": conflicting @const constraints require both ${JSON.stringify(first.value)} and ${JSON.stringify(current.value)}`,
|
|
3091
|
+
first.provenance,
|
|
3092
|
+
current.provenance
|
|
3093
|
+
);
|
|
3094
|
+
}
|
|
3095
|
+
}
|
|
2184
3096
|
function typeLabel(type) {
|
|
2185
3097
|
switch (type.kind) {
|
|
2186
3098
|
case "primitive":
|
|
@@ -2253,6 +3165,8 @@ function checkConstraintOnType(ctx, fieldName, type, constraint) {
|
|
|
2253
3165
|
const isString = effectiveType.kind === "primitive" && effectiveType.primitiveKind === "string";
|
|
2254
3166
|
const isArray = effectiveType.kind === "array";
|
|
2255
3167
|
const isEnum = effectiveType.kind === "enum";
|
|
3168
|
+
const arrayItemType = effectiveType.kind === "array" ? dereferenceType(ctx, effectiveType.items) : void 0;
|
|
3169
|
+
const isStringArray = arrayItemType?.kind === "primitive" && arrayItemType.primitiveKind === "string";
|
|
2256
3170
|
const label = typeLabel(effectiveType);
|
|
2257
3171
|
const ck = constraint.constraintKind;
|
|
2258
3172
|
switch (ck) {
|
|
@@ -2273,10 +3187,10 @@ function checkConstraintOnType(ctx, fieldName, type, constraint) {
|
|
|
2273
3187
|
case "minLength":
|
|
2274
3188
|
case "maxLength":
|
|
2275
3189
|
case "pattern": {
|
|
2276
|
-
if (!isString) {
|
|
3190
|
+
if (!isString && !isStringArray) {
|
|
2277
3191
|
addTypeMismatch(
|
|
2278
3192
|
ctx,
|
|
2279
|
-
`Field "${fieldName}": constraint "${ck}" is only valid on string fields, but field type is "${label}"`,
|
|
3193
|
+
`Field "${fieldName}": constraint "${ck}" is only valid on string fields or string array items, but field type is "${label}"`,
|
|
2280
3194
|
constraint.provenance
|
|
2281
3195
|
);
|
|
2282
3196
|
}
|
|
@@ -2304,6 +3218,37 @@ function checkConstraintOnType(ctx, fieldName, type, constraint) {
|
|
|
2304
3218
|
}
|
|
2305
3219
|
break;
|
|
2306
3220
|
}
|
|
3221
|
+
case "const": {
|
|
3222
|
+
const isPrimitiveConstType = effectiveType.kind === "primitive" && ["string", "number", "boolean", "null"].includes(effectiveType.primitiveKind) || effectiveType.kind === "enum";
|
|
3223
|
+
if (!isPrimitiveConstType) {
|
|
3224
|
+
addTypeMismatch(
|
|
3225
|
+
ctx,
|
|
3226
|
+
`Field "${fieldName}": constraint "const" is only valid on primitive or enum fields, but field type is "${label}"`,
|
|
3227
|
+
constraint.provenance
|
|
3228
|
+
);
|
|
3229
|
+
break;
|
|
3230
|
+
}
|
|
3231
|
+
if (effectiveType.kind === "primitive") {
|
|
3232
|
+
const valueType = constraint.value === null ? "null" : Array.isArray(constraint.value) ? "array" : typeof constraint.value;
|
|
3233
|
+
if (valueType !== effectiveType.primitiveKind) {
|
|
3234
|
+
addTypeMismatch(
|
|
3235
|
+
ctx,
|
|
3236
|
+
`Field "${fieldName}": @const value type "${valueType}" is incompatible with field type "${effectiveType.primitiveKind}"`,
|
|
3237
|
+
constraint.provenance
|
|
3238
|
+
);
|
|
3239
|
+
}
|
|
3240
|
+
break;
|
|
3241
|
+
}
|
|
3242
|
+
const memberValues = effectiveType.members.map((member) => member.value);
|
|
3243
|
+
if (!memberValues.some((member) => jsonValueEquals(member, constraint.value))) {
|
|
3244
|
+
addTypeMismatch(
|
|
3245
|
+
ctx,
|
|
3246
|
+
`Field "${fieldName}": @const value ${JSON.stringify(constraint.value)} is not one of the enum members`,
|
|
3247
|
+
constraint.provenance
|
|
3248
|
+
);
|
|
3249
|
+
}
|
|
3250
|
+
break;
|
|
3251
|
+
}
|
|
2307
3252
|
case "custom": {
|
|
2308
3253
|
checkCustomConstraint(ctx, fieldName, effectiveType, constraint);
|
|
2309
3254
|
break;
|
|
@@ -2322,9 +3267,9 @@ function checkTypeApplicability(ctx, fieldName, type, constraints) {
|
|
|
2322
3267
|
const resolution = resolvePathTargetType(ctx, type, constraint.path.segments);
|
|
2323
3268
|
const targetFieldName = formatPathTargetFieldName(fieldName, constraint.path.segments);
|
|
2324
3269
|
if (resolution.kind === "missing-property") {
|
|
2325
|
-
|
|
3270
|
+
addUnknownPathTarget(
|
|
2326
3271
|
ctx,
|
|
2327
|
-
`Field "${
|
|
3272
|
+
`Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" references unknown path segment "${resolution.segment}"`,
|
|
2328
3273
|
constraint.provenance
|
|
2329
3274
|
);
|
|
2330
3275
|
continue;
|
|
@@ -2354,8 +3299,30 @@ function checkCustomConstraint(ctx, fieldName, type, constraint) {
|
|
|
2354
3299
|
);
|
|
2355
3300
|
return;
|
|
2356
3301
|
}
|
|
2357
|
-
|
|
2358
|
-
if (
|
|
3302
|
+
const normalizedTagName = constraint.provenance.tagName === void 0 ? void 0 : (0, import_core4.normalizeConstraintTagName)(constraint.provenance.tagName.replace(/^@/, ""));
|
|
3303
|
+
if (normalizedTagName !== void 0) {
|
|
3304
|
+
const tagRegistration = ctx.extensionRegistry.findConstraintTag(normalizedTagName);
|
|
3305
|
+
const extensionId = getExtensionIdFromConstraintId(constraint.constraintId);
|
|
3306
|
+
if (extensionId !== null && tagRegistration?.extensionId === extensionId && tagRegistration.registration.constraintName === registration.constraintName && tagRegistration.registration.isApplicableToType?.(type) === false) {
|
|
3307
|
+
addTypeMismatch(
|
|
3308
|
+
ctx,
|
|
3309
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
3310
|
+
constraint.provenance
|
|
3311
|
+
);
|
|
3312
|
+
return;
|
|
3313
|
+
}
|
|
3314
|
+
}
|
|
3315
|
+
if (registration.applicableTypes === null) {
|
|
3316
|
+
if (registration.isApplicableToType?.(type) === false) {
|
|
3317
|
+
addTypeMismatch(
|
|
3318
|
+
ctx,
|
|
3319
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
3320
|
+
constraint.provenance
|
|
3321
|
+
);
|
|
3322
|
+
}
|
|
3323
|
+
return;
|
|
3324
|
+
}
|
|
3325
|
+
if (!registration.applicableTypes.includes(type.kind) || registration.isApplicableToType?.(type) === false) {
|
|
2359
3326
|
addTypeMismatch(
|
|
2360
3327
|
ctx,
|
|
2361
3328
|
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
@@ -2384,7 +3351,9 @@ function validateConstraints(ctx, name, type, constraints) {
|
|
|
2384
3351
|
checkNumericContradictions(ctx, name, constraints);
|
|
2385
3352
|
checkLengthContradictions(ctx, name, constraints);
|
|
2386
3353
|
checkAllowedMembersContradiction(ctx, name, constraints);
|
|
3354
|
+
checkConstContradictions(ctx, name, constraints);
|
|
2387
3355
|
checkConstraintBroadening(ctx, name, constraints);
|
|
3356
|
+
checkCustomConstraintSemantics(ctx, name, constraints);
|
|
2388
3357
|
checkTypeApplicability(ctx, name, type, constraints);
|
|
2389
3358
|
}
|
|
2390
3359
|
function validateElement(ctx, element) {
|
|
@@ -2426,7 +3395,10 @@ function validateIR(ir, options) {
|
|
|
2426
3395
|
// src/extensions/registry.ts
|
|
2427
3396
|
function createExtensionRegistry(extensions) {
|
|
2428
3397
|
const typeMap = /* @__PURE__ */ new Map();
|
|
3398
|
+
const typeNameMap = /* @__PURE__ */ new Map();
|
|
2429
3399
|
const constraintMap = /* @__PURE__ */ new Map();
|
|
3400
|
+
const constraintTagMap = /* @__PURE__ */ new Map();
|
|
3401
|
+
const builtinBroadeningMap = /* @__PURE__ */ new Map();
|
|
2430
3402
|
const annotationMap = /* @__PURE__ */ new Map();
|
|
2431
3403
|
for (const ext of extensions) {
|
|
2432
3404
|
if (ext.types !== void 0) {
|
|
@@ -2436,6 +3408,27 @@ function createExtensionRegistry(extensions) {
|
|
|
2436
3408
|
throw new Error(`Duplicate custom type ID: "${qualifiedId}"`);
|
|
2437
3409
|
}
|
|
2438
3410
|
typeMap.set(qualifiedId, type);
|
|
3411
|
+
for (const sourceTypeName of type.tsTypeNames ?? [type.typeName]) {
|
|
3412
|
+
if (typeNameMap.has(sourceTypeName)) {
|
|
3413
|
+
throw new Error(`Duplicate custom type source name: "${sourceTypeName}"`);
|
|
3414
|
+
}
|
|
3415
|
+
typeNameMap.set(sourceTypeName, {
|
|
3416
|
+
extensionId: ext.extensionId,
|
|
3417
|
+
registration: type
|
|
3418
|
+
});
|
|
3419
|
+
}
|
|
3420
|
+
if (type.builtinConstraintBroadenings !== void 0) {
|
|
3421
|
+
for (const broadening of type.builtinConstraintBroadenings) {
|
|
3422
|
+
const key = `${qualifiedId}:${broadening.tagName}`;
|
|
3423
|
+
if (builtinBroadeningMap.has(key)) {
|
|
3424
|
+
throw new Error(`Duplicate built-in constraint broadening: "${key}"`);
|
|
3425
|
+
}
|
|
3426
|
+
builtinBroadeningMap.set(key, {
|
|
3427
|
+
extensionId: ext.extensionId,
|
|
3428
|
+
registration: broadening
|
|
3429
|
+
});
|
|
3430
|
+
}
|
|
3431
|
+
}
|
|
2439
3432
|
}
|
|
2440
3433
|
}
|
|
2441
3434
|
if (ext.constraints !== void 0) {
|
|
@@ -2447,6 +3440,17 @@ function createExtensionRegistry(extensions) {
|
|
|
2447
3440
|
constraintMap.set(qualifiedId, constraint);
|
|
2448
3441
|
}
|
|
2449
3442
|
}
|
|
3443
|
+
if (ext.constraintTags !== void 0) {
|
|
3444
|
+
for (const tag of ext.constraintTags) {
|
|
3445
|
+
if (constraintTagMap.has(tag.tagName)) {
|
|
3446
|
+
throw new Error(`Duplicate custom constraint tag: "@${tag.tagName}"`);
|
|
3447
|
+
}
|
|
3448
|
+
constraintTagMap.set(tag.tagName, {
|
|
3449
|
+
extensionId: ext.extensionId,
|
|
3450
|
+
registration: tag
|
|
3451
|
+
});
|
|
3452
|
+
}
|
|
3453
|
+
}
|
|
2450
3454
|
if (ext.annotations !== void 0) {
|
|
2451
3455
|
for (const annotation of ext.annotations) {
|
|
2452
3456
|
const qualifiedId = `${ext.extensionId}/${annotation.annotationName}`;
|
|
@@ -2460,13 +3464,16 @@ function createExtensionRegistry(extensions) {
|
|
|
2460
3464
|
return {
|
|
2461
3465
|
extensions,
|
|
2462
3466
|
findType: (typeId) => typeMap.get(typeId),
|
|
3467
|
+
findTypeByName: (typeName) => typeNameMap.get(typeName),
|
|
2463
3468
|
findConstraint: (constraintId) => constraintMap.get(constraintId),
|
|
3469
|
+
findConstraintTag: (tagName) => constraintTagMap.get(tagName),
|
|
3470
|
+
findBuiltinConstraintBroadening: (typeId, tagName) => builtinBroadeningMap.get(`${typeId}:${tagName}`),
|
|
2464
3471
|
findAnnotation: (annotationId) => annotationMap.get(annotationId)
|
|
2465
3472
|
};
|
|
2466
3473
|
}
|
|
2467
3474
|
|
|
2468
3475
|
// src/generators/method-schema.ts
|
|
2469
|
-
var
|
|
3476
|
+
var import_core5 = require("@formspec/core");
|
|
2470
3477
|
function typeToJsonSchema(type, checker) {
|
|
2471
3478
|
const typeRegistry = {};
|
|
2472
3479
|
const visiting = /* @__PURE__ */ new Set();
|
|
@@ -2474,7 +3481,7 @@ function typeToJsonSchema(type, checker) {
|
|
|
2474
3481
|
const fieldProvenance = { surface: "tsdoc", file: "", line: 0, column: 0 };
|
|
2475
3482
|
const ir = {
|
|
2476
3483
|
kind: "form-ir",
|
|
2477
|
-
irVersion:
|
|
3484
|
+
irVersion: import_core5.IR_VERSION,
|
|
2478
3485
|
elements: [
|
|
2479
3486
|
{
|
|
2480
3487
|
kind: "field",
|