@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.js
CHANGED
|
@@ -325,6 +325,7 @@ function canonicalizeTSDoc(analysis, source) {
|
|
|
325
325
|
irVersion: IR_VERSION2,
|
|
326
326
|
elements,
|
|
327
327
|
typeRegistry: analysis.typeRegistry,
|
|
328
|
+
...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { annotations: analysis.annotations },
|
|
328
329
|
provenance
|
|
329
330
|
};
|
|
330
331
|
}
|
|
@@ -501,8 +502,8 @@ var LENGTH_CONSTRAINT_MAP = {
|
|
|
501
502
|
minItems: "minItems",
|
|
502
503
|
maxItems: "maxItems"
|
|
503
504
|
};
|
|
504
|
-
var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions"]);
|
|
505
|
-
function createFormSpecTSDocConfig() {
|
|
505
|
+
var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions", "defaultValue"]);
|
|
506
|
+
function createFormSpecTSDocConfig(extensionTagNames = []) {
|
|
506
507
|
const config = new TSDocConfiguration();
|
|
507
508
|
for (const tagName of Object.keys(BUILTIN_CONSTRAINT_DEFINITIONS)) {
|
|
508
509
|
config.addTagDefinition(
|
|
@@ -513,7 +514,16 @@ function createFormSpecTSDocConfig() {
|
|
|
513
514
|
})
|
|
514
515
|
);
|
|
515
516
|
}
|
|
516
|
-
for (const tagName of ["displayName", "description"]) {
|
|
517
|
+
for (const tagName of ["displayName", "description", "format", "placeholder"]) {
|
|
518
|
+
config.addTagDefinition(
|
|
519
|
+
new TSDocTagDefinition({
|
|
520
|
+
tagName: "@" + tagName,
|
|
521
|
+
syntaxKind: TSDocTagSyntaxKind.BlockTag,
|
|
522
|
+
allowMultiple: true
|
|
523
|
+
})
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
for (const tagName of extensionTagNames) {
|
|
517
527
|
config.addTagDefinition(
|
|
518
528
|
new TSDocTagDefinition({
|
|
519
529
|
tagName: "@" + tagName,
|
|
@@ -524,14 +534,31 @@ function createFormSpecTSDocConfig() {
|
|
|
524
534
|
}
|
|
525
535
|
return config;
|
|
526
536
|
}
|
|
527
|
-
var
|
|
528
|
-
function getParser() {
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
537
|
+
var parserCache = /* @__PURE__ */ new Map();
|
|
538
|
+
function getParser(options) {
|
|
539
|
+
const extensionTagNames = [
|
|
540
|
+
...options?.extensionRegistry?.extensions.flatMap(
|
|
541
|
+
(extension) => (extension.constraintTags ?? []).map((tag) => tag.tagName)
|
|
542
|
+
) ?? []
|
|
543
|
+
].sort();
|
|
544
|
+
const cacheKey = extensionTagNames.join("|");
|
|
545
|
+
const existing = parserCache.get(cacheKey);
|
|
546
|
+
if (existing) {
|
|
547
|
+
return existing;
|
|
548
|
+
}
|
|
549
|
+
const parser = new TSDocParser(createFormSpecTSDocConfig(extensionTagNames));
|
|
550
|
+
parserCache.set(cacheKey, parser);
|
|
551
|
+
return parser;
|
|
552
|
+
}
|
|
553
|
+
function parseTSDocTags(node, file = "", options) {
|
|
533
554
|
const constraints = [];
|
|
534
555
|
const annotations = [];
|
|
556
|
+
let displayName;
|
|
557
|
+
let description;
|
|
558
|
+
let placeholder;
|
|
559
|
+
let displayNameProvenance;
|
|
560
|
+
let descriptionProvenance;
|
|
561
|
+
let placeholderProvenance;
|
|
535
562
|
const sourceFile = node.getSourceFile();
|
|
536
563
|
const sourceText = sourceFile.getFullText();
|
|
537
564
|
const commentRanges = ts2.getLeadingCommentRanges(sourceText, node.getFullStart());
|
|
@@ -544,52 +571,92 @@ function parseTSDocTags(node, file = "") {
|
|
|
544
571
|
if (!commentText.startsWith("/**")) {
|
|
545
572
|
continue;
|
|
546
573
|
}
|
|
547
|
-
const parser = getParser();
|
|
574
|
+
const parser = getParser(options);
|
|
548
575
|
const parserContext = parser.parseRange(
|
|
549
576
|
TextRange.fromStringRange(sourceText, range.pos, range.end)
|
|
550
577
|
);
|
|
551
578
|
const docComment = parserContext.docComment;
|
|
552
579
|
for (const block of docComment.customBlocks) {
|
|
553
580
|
const tagName = normalizeConstraintTagName(block.blockTag.tagName.substring(1));
|
|
554
|
-
if (tagName === "displayName" || tagName === "description") {
|
|
581
|
+
if (tagName === "displayName" || tagName === "description" || tagName === "format" || tagName === "placeholder") {
|
|
555
582
|
const text2 = extractBlockText(block).trim();
|
|
556
583
|
if (text2 === "") continue;
|
|
557
584
|
const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
|
|
558
585
|
if (tagName === "displayName") {
|
|
586
|
+
if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
|
|
587
|
+
displayName = text2;
|
|
588
|
+
displayNameProvenance = provenance2;
|
|
589
|
+
}
|
|
590
|
+
} else if (tagName === "format") {
|
|
559
591
|
annotations.push({
|
|
560
592
|
kind: "annotation",
|
|
561
|
-
annotationKind: "
|
|
593
|
+
annotationKind: "format",
|
|
562
594
|
value: text2,
|
|
563
595
|
provenance: provenance2
|
|
564
596
|
});
|
|
565
597
|
} else {
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
598
|
+
if (tagName === "description" && description === void 0) {
|
|
599
|
+
description = text2;
|
|
600
|
+
descriptionProvenance = provenance2;
|
|
601
|
+
} else if (tagName === "placeholder" && placeholder === void 0) {
|
|
602
|
+
placeholder = text2;
|
|
603
|
+
placeholderProvenance = provenance2;
|
|
604
|
+
}
|
|
572
605
|
}
|
|
573
606
|
continue;
|
|
574
607
|
}
|
|
575
608
|
if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
576
609
|
const text = extractBlockText(block).trim();
|
|
577
|
-
|
|
610
|
+
const expectedType = isBuiltinConstraintName(tagName) ? BUILTIN_CONSTRAINT_DEFINITIONS[tagName] : void 0;
|
|
611
|
+
if (text === "" && expectedType !== "boolean") continue;
|
|
578
612
|
const provenance = provenanceForComment(range, sourceFile, file, tagName);
|
|
579
|
-
const constraintNode = parseConstraintValue(tagName, text, provenance);
|
|
613
|
+
const constraintNode = parseConstraintValue(tagName, text, provenance, options);
|
|
580
614
|
if (constraintNode) {
|
|
581
615
|
constraints.push(constraintNode);
|
|
582
616
|
}
|
|
583
617
|
}
|
|
584
618
|
if (docComment.deprecatedBlock !== void 0) {
|
|
619
|
+
const message = extractBlockText(docComment.deprecatedBlock).trim();
|
|
585
620
|
annotations.push({
|
|
586
621
|
kind: "annotation",
|
|
587
622
|
annotationKind: "deprecated",
|
|
623
|
+
...message !== "" && { message },
|
|
588
624
|
provenance: provenanceForComment(range, sourceFile, file, "deprecated")
|
|
589
625
|
});
|
|
590
626
|
}
|
|
627
|
+
if (description === void 0 && docComment.remarksBlock !== void 0) {
|
|
628
|
+
const remarks = extractBlockText(docComment.remarksBlock).trim();
|
|
629
|
+
if (remarks !== "") {
|
|
630
|
+
description = remarks;
|
|
631
|
+
descriptionProvenance = provenanceForComment(range, sourceFile, file, "remarks");
|
|
632
|
+
}
|
|
633
|
+
}
|
|
591
634
|
}
|
|
592
635
|
}
|
|
636
|
+
if (displayName !== void 0 && displayNameProvenance !== void 0) {
|
|
637
|
+
annotations.push({
|
|
638
|
+
kind: "annotation",
|
|
639
|
+
annotationKind: "displayName",
|
|
640
|
+
value: displayName,
|
|
641
|
+
provenance: displayNameProvenance
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
if (description !== void 0 && descriptionProvenance !== void 0) {
|
|
645
|
+
annotations.push({
|
|
646
|
+
kind: "annotation",
|
|
647
|
+
annotationKind: "description",
|
|
648
|
+
value: description,
|
|
649
|
+
provenance: descriptionProvenance
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
if (placeholder !== void 0 && placeholderProvenance !== void 0) {
|
|
653
|
+
annotations.push({
|
|
654
|
+
kind: "annotation",
|
|
655
|
+
annotationKind: "placeholder",
|
|
656
|
+
value: placeholder,
|
|
657
|
+
provenance: placeholderProvenance
|
|
658
|
+
});
|
|
659
|
+
}
|
|
593
660
|
const jsDocTagsAll = ts2.getJSDocTags(node);
|
|
594
661
|
for (const tag of jsDocTagsAll) {
|
|
595
662
|
const tagName = normalizeConstraintTagName(tag.tagName.text);
|
|
@@ -598,13 +665,40 @@ function parseTSDocTags(node, file = "") {
|
|
|
598
665
|
if (commentText === void 0 || commentText.trim() === "") continue;
|
|
599
666
|
const text = commentText.trim();
|
|
600
667
|
const provenance = provenanceForJSDocTag(tag, file);
|
|
601
|
-
|
|
668
|
+
if (tagName === "defaultValue") {
|
|
669
|
+
const defaultValueNode = parseDefaultValueValue(text, provenance);
|
|
670
|
+
annotations.push(defaultValueNode);
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
673
|
+
const constraintNode = parseConstraintValue(tagName, text, provenance, options);
|
|
602
674
|
if (constraintNode) {
|
|
603
675
|
constraints.push(constraintNode);
|
|
604
676
|
}
|
|
605
677
|
}
|
|
606
678
|
return { constraints, annotations };
|
|
607
679
|
}
|
|
680
|
+
function extractDisplayNameMetadata(node) {
|
|
681
|
+
let displayName;
|
|
682
|
+
const memberDisplayNames = /* @__PURE__ */ new Map();
|
|
683
|
+
for (const tag of ts2.getJSDocTags(node)) {
|
|
684
|
+
const tagName = normalizeConstraintTagName(tag.tagName.text);
|
|
685
|
+
if (tagName !== "displayName") continue;
|
|
686
|
+
const commentText = getTagCommentText(tag);
|
|
687
|
+
if (commentText === void 0) continue;
|
|
688
|
+
const text = commentText.trim();
|
|
689
|
+
if (text === "") continue;
|
|
690
|
+
const memberTarget = parseMemberTargetDisplayName(text);
|
|
691
|
+
if (memberTarget) {
|
|
692
|
+
memberDisplayNames.set(memberTarget.target, memberTarget.label);
|
|
693
|
+
continue;
|
|
694
|
+
}
|
|
695
|
+
displayName ??= text;
|
|
696
|
+
}
|
|
697
|
+
return {
|
|
698
|
+
...displayName !== void 0 && { displayName },
|
|
699
|
+
memberDisplayNames
|
|
700
|
+
};
|
|
701
|
+
}
|
|
608
702
|
function extractPathTarget(text) {
|
|
609
703
|
const trimmed = text.trimStart();
|
|
610
704
|
const match = /^:([a-zA-Z_]\w*)\s+([\s\S]*)$/.exec(trimmed);
|
|
@@ -632,7 +726,11 @@ function extractPlainText(node) {
|
|
|
632
726
|
}
|
|
633
727
|
return result;
|
|
634
728
|
}
|
|
635
|
-
function parseConstraintValue(tagName, text, provenance) {
|
|
729
|
+
function parseConstraintValue(tagName, text, provenance, options) {
|
|
730
|
+
const customConstraint = parseExtensionConstraintValue(tagName, text, provenance, options);
|
|
731
|
+
if (customConstraint) {
|
|
732
|
+
return customConstraint;
|
|
733
|
+
}
|
|
636
734
|
if (!isBuiltinConstraintName(tagName)) {
|
|
637
735
|
return null;
|
|
638
736
|
}
|
|
@@ -667,7 +765,45 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
667
765
|
}
|
|
668
766
|
return null;
|
|
669
767
|
}
|
|
768
|
+
if (expectedType === "boolean") {
|
|
769
|
+
const trimmed = effectiveText.trim();
|
|
770
|
+
if (trimmed !== "" && trimmed !== "true") {
|
|
771
|
+
return null;
|
|
772
|
+
}
|
|
773
|
+
if (tagName === "uniqueItems") {
|
|
774
|
+
return {
|
|
775
|
+
kind: "constraint",
|
|
776
|
+
constraintKind: "uniqueItems",
|
|
777
|
+
value: true,
|
|
778
|
+
...path2 && { path: path2 },
|
|
779
|
+
provenance
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
return null;
|
|
783
|
+
}
|
|
670
784
|
if (expectedType === "json") {
|
|
785
|
+
if (tagName === "const") {
|
|
786
|
+
const trimmedText = effectiveText.trim();
|
|
787
|
+
if (trimmedText === "") return null;
|
|
788
|
+
try {
|
|
789
|
+
const parsed2 = JSON.parse(trimmedText);
|
|
790
|
+
return {
|
|
791
|
+
kind: "constraint",
|
|
792
|
+
constraintKind: "const",
|
|
793
|
+
value: parsed2,
|
|
794
|
+
...path2 && { path: path2 },
|
|
795
|
+
provenance
|
|
796
|
+
};
|
|
797
|
+
} catch {
|
|
798
|
+
return {
|
|
799
|
+
kind: "constraint",
|
|
800
|
+
constraintKind: "const",
|
|
801
|
+
value: trimmedText,
|
|
802
|
+
...path2 && { path: path2 },
|
|
803
|
+
provenance
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
}
|
|
671
807
|
const parsed = tryParseJson(effectiveText);
|
|
672
808
|
if (!Array.isArray(parsed)) {
|
|
673
809
|
return null;
|
|
@@ -699,6 +835,111 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
699
835
|
provenance
|
|
700
836
|
};
|
|
701
837
|
}
|
|
838
|
+
function parseExtensionConstraintValue(tagName, text, provenance, options) {
|
|
839
|
+
const pathResult = extractPathTarget(text);
|
|
840
|
+
const effectiveText = pathResult ? pathResult.remainingText : text;
|
|
841
|
+
const path2 = pathResult?.path;
|
|
842
|
+
const registry = options?.extensionRegistry;
|
|
843
|
+
if (registry === void 0) {
|
|
844
|
+
return null;
|
|
845
|
+
}
|
|
846
|
+
const directTag = registry.findConstraintTag(tagName);
|
|
847
|
+
if (directTag !== void 0) {
|
|
848
|
+
return makeCustomConstraintNode(
|
|
849
|
+
directTag.extensionId,
|
|
850
|
+
directTag.registration.constraintName,
|
|
851
|
+
directTag.registration.parseValue(effectiveText),
|
|
852
|
+
provenance,
|
|
853
|
+
path2,
|
|
854
|
+
registry
|
|
855
|
+
);
|
|
856
|
+
}
|
|
857
|
+
if (!isBuiltinConstraintName(tagName)) {
|
|
858
|
+
return null;
|
|
859
|
+
}
|
|
860
|
+
const broadenedTypeId = getBroadenedCustomTypeId(options?.fieldType);
|
|
861
|
+
if (broadenedTypeId === void 0) {
|
|
862
|
+
return null;
|
|
863
|
+
}
|
|
864
|
+
const broadened = registry.findBuiltinConstraintBroadening(broadenedTypeId, tagName);
|
|
865
|
+
if (broadened === void 0) {
|
|
866
|
+
return null;
|
|
867
|
+
}
|
|
868
|
+
return makeCustomConstraintNode(
|
|
869
|
+
broadened.extensionId,
|
|
870
|
+
broadened.registration.constraintName,
|
|
871
|
+
broadened.registration.parseValue(effectiveText),
|
|
872
|
+
provenance,
|
|
873
|
+
path2,
|
|
874
|
+
registry
|
|
875
|
+
);
|
|
876
|
+
}
|
|
877
|
+
function getBroadenedCustomTypeId(fieldType) {
|
|
878
|
+
if (fieldType?.kind === "custom") {
|
|
879
|
+
return fieldType.typeId;
|
|
880
|
+
}
|
|
881
|
+
if (fieldType?.kind !== "union") {
|
|
882
|
+
return void 0;
|
|
883
|
+
}
|
|
884
|
+
const customMembers = fieldType.members.filter(
|
|
885
|
+
(member) => member.kind === "custom"
|
|
886
|
+
);
|
|
887
|
+
if (customMembers.length !== 1) {
|
|
888
|
+
return void 0;
|
|
889
|
+
}
|
|
890
|
+
const nonCustomMembers = fieldType.members.filter((member) => member.kind !== "custom");
|
|
891
|
+
const allOtherMembersAreNull = nonCustomMembers.every(
|
|
892
|
+
(member) => member.kind === "primitive" && member.primitiveKind === "null"
|
|
893
|
+
);
|
|
894
|
+
const customMember = customMembers[0];
|
|
895
|
+
return allOtherMembersAreNull && customMember !== void 0 ? customMember.typeId : void 0;
|
|
896
|
+
}
|
|
897
|
+
function makeCustomConstraintNode(extensionId, constraintName, payload, provenance, path2, registry) {
|
|
898
|
+
const constraintId = `${extensionId}/${constraintName}`;
|
|
899
|
+
const registration = registry.findConstraint(constraintId);
|
|
900
|
+
if (registration === void 0) {
|
|
901
|
+
throw new Error(
|
|
902
|
+
`Custom TSDoc tag resolved to unregistered constraint "${constraintId}". Register the constraint before using its tag.`
|
|
903
|
+
);
|
|
904
|
+
}
|
|
905
|
+
return {
|
|
906
|
+
kind: "constraint",
|
|
907
|
+
constraintKind: "custom",
|
|
908
|
+
constraintId,
|
|
909
|
+
payload,
|
|
910
|
+
compositionRule: registration.compositionRule,
|
|
911
|
+
...path2 && { path: path2 },
|
|
912
|
+
provenance
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
function parseDefaultValueValue(text, provenance) {
|
|
916
|
+
const trimmed = text.trim();
|
|
917
|
+
let value;
|
|
918
|
+
if (trimmed === "null") {
|
|
919
|
+
value = null;
|
|
920
|
+
} else if (trimmed === "true") {
|
|
921
|
+
value = true;
|
|
922
|
+
} else if (trimmed === "false") {
|
|
923
|
+
value = false;
|
|
924
|
+
} else {
|
|
925
|
+
const parsed = tryParseJson(trimmed);
|
|
926
|
+
value = parsed !== null ? parsed : trimmed;
|
|
927
|
+
}
|
|
928
|
+
return {
|
|
929
|
+
kind: "annotation",
|
|
930
|
+
annotationKind: "defaultValue",
|
|
931
|
+
value,
|
|
932
|
+
provenance
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
function isMemberTargetDisplayName(text) {
|
|
936
|
+
return parseMemberTargetDisplayName(text) !== null;
|
|
937
|
+
}
|
|
938
|
+
function parseMemberTargetDisplayName(text) {
|
|
939
|
+
const match = /^:([^\s]+)\s+([\s\S]+)$/.exec(text);
|
|
940
|
+
if (!match?.[1] || !match[2]) return null;
|
|
941
|
+
return { target: match[1], label: match[2].trim() };
|
|
942
|
+
}
|
|
702
943
|
function provenanceForComment(range, sourceFile, file, tagName) {
|
|
703
944
|
const { line, character } = sourceFile.getLineAndCharacterOfPosition(range.pos);
|
|
704
945
|
return {
|
|
@@ -731,12 +972,12 @@ function getTagCommentText(tag) {
|
|
|
731
972
|
}
|
|
732
973
|
|
|
733
974
|
// src/analyzer/jsdoc-constraints.ts
|
|
734
|
-
function extractJSDocConstraintNodes(node, file = "") {
|
|
735
|
-
const result = parseTSDocTags(node, file);
|
|
975
|
+
function extractJSDocConstraintNodes(node, file = "", options) {
|
|
976
|
+
const result = parseTSDocTags(node, file, options);
|
|
736
977
|
return [...result.constraints];
|
|
737
978
|
}
|
|
738
|
-
function extractJSDocAnnotationNodes(node, file = "") {
|
|
739
|
-
const result = parseTSDocTags(node, file);
|
|
979
|
+
function extractJSDocAnnotationNodes(node, file = "", options) {
|
|
980
|
+
const result = parseTSDocTags(node, file, options);
|
|
740
981
|
return [...result.annotations];
|
|
741
982
|
}
|
|
742
983
|
function extractDefaultValueAnnotation(initializer, file = "") {
|
|
@@ -780,17 +1021,43 @@ function isObjectType(type) {
|
|
|
780
1021
|
function isTypeReference(type) {
|
|
781
1022
|
return !!(type.flags & ts4.TypeFlags.Object) && !!(type.objectFlags & ts4.ObjectFlags.Reference);
|
|
782
1023
|
}
|
|
783
|
-
|
|
1024
|
+
var RESOLVING_TYPE_PLACEHOLDER = {
|
|
1025
|
+
kind: "object",
|
|
1026
|
+
properties: [],
|
|
1027
|
+
additionalProperties: true
|
|
1028
|
+
};
|
|
1029
|
+
function makeParseOptions(extensionRegistry, fieldType) {
|
|
1030
|
+
if (extensionRegistry === void 0 && fieldType === void 0) {
|
|
1031
|
+
return void 0;
|
|
1032
|
+
}
|
|
1033
|
+
return {
|
|
1034
|
+
...extensionRegistry !== void 0 && { extensionRegistry },
|
|
1035
|
+
...fieldType !== void 0 && { fieldType }
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
1038
|
+
function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
784
1039
|
const name = classDecl.name?.text ?? "AnonymousClass";
|
|
785
1040
|
const fields = [];
|
|
786
1041
|
const fieldLayouts = [];
|
|
787
1042
|
const typeRegistry = {};
|
|
1043
|
+
const annotations = extractJSDocAnnotationNodes(
|
|
1044
|
+
classDecl,
|
|
1045
|
+
file,
|
|
1046
|
+
makeParseOptions(extensionRegistry)
|
|
1047
|
+
);
|
|
788
1048
|
const visiting = /* @__PURE__ */ new Set();
|
|
789
1049
|
const instanceMethods = [];
|
|
790
1050
|
const staticMethods = [];
|
|
791
1051
|
for (const member of classDecl.members) {
|
|
792
1052
|
if (ts4.isPropertyDeclaration(member)) {
|
|
793
|
-
const fieldNode = analyzeFieldToIR(
|
|
1053
|
+
const fieldNode = analyzeFieldToIR(
|
|
1054
|
+
member,
|
|
1055
|
+
checker,
|
|
1056
|
+
file,
|
|
1057
|
+
typeRegistry,
|
|
1058
|
+
visiting,
|
|
1059
|
+
extensionRegistry
|
|
1060
|
+
);
|
|
794
1061
|
if (fieldNode) {
|
|
795
1062
|
fields.push(fieldNode);
|
|
796
1063
|
fieldLayouts.push({});
|
|
@@ -807,25 +1074,53 @@ function analyzeClassToIR(classDecl, checker, file = "") {
|
|
|
807
1074
|
}
|
|
808
1075
|
}
|
|
809
1076
|
}
|
|
810
|
-
return {
|
|
1077
|
+
return {
|
|
1078
|
+
name,
|
|
1079
|
+
fields,
|
|
1080
|
+
fieldLayouts,
|
|
1081
|
+
typeRegistry,
|
|
1082
|
+
...annotations.length > 0 && { annotations },
|
|
1083
|
+
instanceMethods,
|
|
1084
|
+
staticMethods
|
|
1085
|
+
};
|
|
811
1086
|
}
|
|
812
|
-
function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
|
|
1087
|
+
function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegistry) {
|
|
813
1088
|
const name = interfaceDecl.name.text;
|
|
814
1089
|
const fields = [];
|
|
815
1090
|
const typeRegistry = {};
|
|
1091
|
+
const annotations = extractJSDocAnnotationNodes(
|
|
1092
|
+
interfaceDecl,
|
|
1093
|
+
file,
|
|
1094
|
+
makeParseOptions(extensionRegistry)
|
|
1095
|
+
);
|
|
816
1096
|
const visiting = /* @__PURE__ */ new Set();
|
|
817
1097
|
for (const member of interfaceDecl.members) {
|
|
818
1098
|
if (ts4.isPropertySignature(member)) {
|
|
819
|
-
const fieldNode = analyzeInterfacePropertyToIR(
|
|
1099
|
+
const fieldNode = analyzeInterfacePropertyToIR(
|
|
1100
|
+
member,
|
|
1101
|
+
checker,
|
|
1102
|
+
file,
|
|
1103
|
+
typeRegistry,
|
|
1104
|
+
visiting,
|
|
1105
|
+
extensionRegistry
|
|
1106
|
+
);
|
|
820
1107
|
if (fieldNode) {
|
|
821
1108
|
fields.push(fieldNode);
|
|
822
1109
|
}
|
|
823
1110
|
}
|
|
824
1111
|
}
|
|
825
1112
|
const fieldLayouts = fields.map(() => ({}));
|
|
826
|
-
return {
|
|
1113
|
+
return {
|
|
1114
|
+
name,
|
|
1115
|
+
fields,
|
|
1116
|
+
fieldLayouts,
|
|
1117
|
+
typeRegistry,
|
|
1118
|
+
...annotations.length > 0 && { annotations },
|
|
1119
|
+
instanceMethods: [],
|
|
1120
|
+
staticMethods: []
|
|
1121
|
+
};
|
|
827
1122
|
}
|
|
828
|
-
function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
1123
|
+
function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry) {
|
|
829
1124
|
if (!ts4.isTypeLiteralNode(typeAlias.type)) {
|
|
830
1125
|
const sourceFile = typeAlias.getSourceFile();
|
|
831
1126
|
const { line } = sourceFile.getLineAndCharacterOfPosition(typeAlias.getStart());
|
|
@@ -838,10 +1133,22 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
|
838
1133
|
const name = typeAlias.name.text;
|
|
839
1134
|
const fields = [];
|
|
840
1135
|
const typeRegistry = {};
|
|
1136
|
+
const annotations = extractJSDocAnnotationNodes(
|
|
1137
|
+
typeAlias,
|
|
1138
|
+
file,
|
|
1139
|
+
makeParseOptions(extensionRegistry)
|
|
1140
|
+
);
|
|
841
1141
|
const visiting = /* @__PURE__ */ new Set();
|
|
842
1142
|
for (const member of typeAlias.type.members) {
|
|
843
1143
|
if (ts4.isPropertySignature(member)) {
|
|
844
|
-
const fieldNode = analyzeInterfacePropertyToIR(
|
|
1144
|
+
const fieldNode = analyzeInterfacePropertyToIR(
|
|
1145
|
+
member,
|
|
1146
|
+
checker,
|
|
1147
|
+
file,
|
|
1148
|
+
typeRegistry,
|
|
1149
|
+
visiting,
|
|
1150
|
+
extensionRegistry
|
|
1151
|
+
);
|
|
845
1152
|
if (fieldNode) {
|
|
846
1153
|
fields.push(fieldNode);
|
|
847
1154
|
}
|
|
@@ -854,12 +1161,13 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
|
854
1161
|
fields,
|
|
855
1162
|
fieldLayouts: fields.map(() => ({})),
|
|
856
1163
|
typeRegistry,
|
|
1164
|
+
...annotations.length > 0 && { annotations },
|
|
857
1165
|
instanceMethods: [],
|
|
858
1166
|
staticMethods: []
|
|
859
1167
|
}
|
|
860
1168
|
};
|
|
861
1169
|
}
|
|
862
|
-
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
|
|
1170
|
+
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
863
1171
|
if (!ts4.isIdentifier(prop.name)) {
|
|
864
1172
|
return null;
|
|
865
1173
|
}
|
|
@@ -867,16 +1175,28 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
|
|
|
867
1175
|
const tsType = checker.getTypeAtLocation(prop);
|
|
868
1176
|
const optional = prop.questionToken !== void 0;
|
|
869
1177
|
const provenance = provenanceForNode(prop, file);
|
|
870
|
-
let type = resolveTypeNode(
|
|
1178
|
+
let type = resolveTypeNode(
|
|
1179
|
+
tsType,
|
|
1180
|
+
checker,
|
|
1181
|
+
file,
|
|
1182
|
+
typeRegistry,
|
|
1183
|
+
visiting,
|
|
1184
|
+
prop,
|
|
1185
|
+
extensionRegistry
|
|
1186
|
+
);
|
|
871
1187
|
const constraints = [];
|
|
872
1188
|
if (prop.type) {
|
|
873
|
-
constraints.push(
|
|
1189
|
+
constraints.push(
|
|
1190
|
+
...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
|
|
1191
|
+
);
|
|
874
1192
|
}
|
|
875
|
-
constraints.push(...extractJSDocConstraintNodes(prop, file));
|
|
1193
|
+
constraints.push(...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type)));
|
|
876
1194
|
let annotations = [];
|
|
877
|
-
annotations.push(
|
|
1195
|
+
annotations.push(
|
|
1196
|
+
...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
1197
|
+
);
|
|
878
1198
|
const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
|
|
879
|
-
if (defaultAnnotation) {
|
|
1199
|
+
if (defaultAnnotation && !annotations.some((a) => a.annotationKind === "defaultValue")) {
|
|
880
1200
|
annotations.push(defaultAnnotation);
|
|
881
1201
|
}
|
|
882
1202
|
({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
|
|
@@ -890,7 +1210,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
|
|
|
890
1210
|
provenance
|
|
891
1211
|
};
|
|
892
1212
|
}
|
|
893
|
-
function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting) {
|
|
1213
|
+
function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
894
1214
|
if (!ts4.isIdentifier(prop.name)) {
|
|
895
1215
|
return null;
|
|
896
1216
|
}
|
|
@@ -898,14 +1218,26 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
|
|
|
898
1218
|
const tsType = checker.getTypeAtLocation(prop);
|
|
899
1219
|
const optional = prop.questionToken !== void 0;
|
|
900
1220
|
const provenance = provenanceForNode(prop, file);
|
|
901
|
-
let type = resolveTypeNode(
|
|
1221
|
+
let type = resolveTypeNode(
|
|
1222
|
+
tsType,
|
|
1223
|
+
checker,
|
|
1224
|
+
file,
|
|
1225
|
+
typeRegistry,
|
|
1226
|
+
visiting,
|
|
1227
|
+
prop,
|
|
1228
|
+
extensionRegistry
|
|
1229
|
+
);
|
|
902
1230
|
const constraints = [];
|
|
903
1231
|
if (prop.type) {
|
|
904
|
-
constraints.push(
|
|
1232
|
+
constraints.push(
|
|
1233
|
+
...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
|
|
1234
|
+
);
|
|
905
1235
|
}
|
|
906
|
-
constraints.push(...extractJSDocConstraintNodes(prop, file));
|
|
1236
|
+
constraints.push(...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type)));
|
|
907
1237
|
let annotations = [];
|
|
908
|
-
annotations.push(
|
|
1238
|
+
annotations.push(
|
|
1239
|
+
...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
1240
|
+
);
|
|
909
1241
|
({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
|
|
910
1242
|
return {
|
|
911
1243
|
kind: "field",
|
|
@@ -979,7 +1311,66 @@ function parseEnumMemberDisplayName(value) {
|
|
|
979
1311
|
if (label === "") return null;
|
|
980
1312
|
return { value: match[1], label };
|
|
981
1313
|
}
|
|
982
|
-
function
|
|
1314
|
+
function resolveRegisteredCustomType(sourceNode, extensionRegistry, checker) {
|
|
1315
|
+
if (sourceNode === void 0 || extensionRegistry === void 0) {
|
|
1316
|
+
return null;
|
|
1317
|
+
}
|
|
1318
|
+
const typeNode = extractTypeNodeFromSource(sourceNode);
|
|
1319
|
+
if (typeNode === void 0) {
|
|
1320
|
+
return null;
|
|
1321
|
+
}
|
|
1322
|
+
return resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker);
|
|
1323
|
+
}
|
|
1324
|
+
function resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker) {
|
|
1325
|
+
if (ts4.isParenthesizedTypeNode(typeNode)) {
|
|
1326
|
+
return resolveRegisteredCustomTypeFromTypeNode(typeNode.type, extensionRegistry, checker);
|
|
1327
|
+
}
|
|
1328
|
+
const typeName = getTypeNodeRegistrationName(typeNode);
|
|
1329
|
+
if (typeName === null) {
|
|
1330
|
+
return null;
|
|
1331
|
+
}
|
|
1332
|
+
const registration = extensionRegistry.findTypeByName(typeName);
|
|
1333
|
+
if (registration !== void 0) {
|
|
1334
|
+
return {
|
|
1335
|
+
kind: "custom",
|
|
1336
|
+
typeId: `${registration.extensionId}/${registration.registration.typeName}`,
|
|
1337
|
+
payload: null
|
|
1338
|
+
};
|
|
1339
|
+
}
|
|
1340
|
+
if (ts4.isTypeReferenceNode(typeNode) && ts4.isIdentifier(typeNode.typeName)) {
|
|
1341
|
+
const aliasDecl = checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts4.isTypeAliasDeclaration);
|
|
1342
|
+
if (aliasDecl !== void 0) {
|
|
1343
|
+
return resolveRegisteredCustomTypeFromTypeNode(aliasDecl.type, extensionRegistry, checker);
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
return null;
|
|
1347
|
+
}
|
|
1348
|
+
function extractTypeNodeFromSource(sourceNode) {
|
|
1349
|
+
if (ts4.isPropertyDeclaration(sourceNode) || ts4.isPropertySignature(sourceNode) || ts4.isParameter(sourceNode) || ts4.isTypeAliasDeclaration(sourceNode)) {
|
|
1350
|
+
return sourceNode.type;
|
|
1351
|
+
}
|
|
1352
|
+
if (ts4.isTypeNode(sourceNode)) {
|
|
1353
|
+
return sourceNode;
|
|
1354
|
+
}
|
|
1355
|
+
return void 0;
|
|
1356
|
+
}
|
|
1357
|
+
function getTypeNodeRegistrationName(typeNode) {
|
|
1358
|
+
if (ts4.isTypeReferenceNode(typeNode)) {
|
|
1359
|
+
return ts4.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : typeNode.typeName.right.text;
|
|
1360
|
+
}
|
|
1361
|
+
if (ts4.isParenthesizedTypeNode(typeNode)) {
|
|
1362
|
+
return getTypeNodeRegistrationName(typeNode.type);
|
|
1363
|
+
}
|
|
1364
|
+
if (typeNode.kind === ts4.SyntaxKind.BigIntKeyword || typeNode.kind === ts4.SyntaxKind.StringKeyword || typeNode.kind === ts4.SyntaxKind.NumberKeyword || typeNode.kind === ts4.SyntaxKind.BooleanKeyword) {
|
|
1365
|
+
return typeNode.getText();
|
|
1366
|
+
}
|
|
1367
|
+
return null;
|
|
1368
|
+
}
|
|
1369
|
+
function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
1370
|
+
const customType = resolveRegisteredCustomType(sourceNode, extensionRegistry, checker);
|
|
1371
|
+
if (customType) {
|
|
1372
|
+
return customType;
|
|
1373
|
+
}
|
|
983
1374
|
if (type.flags & ts4.TypeFlags.String) {
|
|
984
1375
|
return { kind: "primitive", primitiveKind: "string" };
|
|
985
1376
|
}
|
|
@@ -1008,88 +1399,162 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
|
|
|
1008
1399
|
};
|
|
1009
1400
|
}
|
|
1010
1401
|
if (type.isUnion()) {
|
|
1011
|
-
return resolveUnionType(
|
|
1402
|
+
return resolveUnionType(
|
|
1403
|
+
type,
|
|
1404
|
+
checker,
|
|
1405
|
+
file,
|
|
1406
|
+
typeRegistry,
|
|
1407
|
+
visiting,
|
|
1408
|
+
sourceNode,
|
|
1409
|
+
extensionRegistry
|
|
1410
|
+
);
|
|
1012
1411
|
}
|
|
1013
1412
|
if (checker.isArrayType(type)) {
|
|
1014
|
-
return resolveArrayType(
|
|
1413
|
+
return resolveArrayType(
|
|
1414
|
+
type,
|
|
1415
|
+
checker,
|
|
1416
|
+
file,
|
|
1417
|
+
typeRegistry,
|
|
1418
|
+
visiting,
|
|
1419
|
+
sourceNode,
|
|
1420
|
+
extensionRegistry
|
|
1421
|
+
);
|
|
1015
1422
|
}
|
|
1016
1423
|
if (isObjectType(type)) {
|
|
1017
|
-
return resolveObjectType(type, checker, file, typeRegistry, visiting);
|
|
1424
|
+
return resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry);
|
|
1018
1425
|
}
|
|
1019
1426
|
return { kind: "primitive", primitiveKind: "string" };
|
|
1020
1427
|
}
|
|
1021
|
-
function resolveUnionType(type, checker, file, typeRegistry, visiting) {
|
|
1428
|
+
function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
1429
|
+
const typeName = getNamedTypeName(type);
|
|
1430
|
+
const namedDecl = getNamedTypeDeclaration(type);
|
|
1431
|
+
if (typeName && typeName in typeRegistry) {
|
|
1432
|
+
return { kind: "reference", name: typeName, typeArguments: [] };
|
|
1433
|
+
}
|
|
1022
1434
|
const allTypes = type.types;
|
|
1435
|
+
const unionMemberTypeNodes = extractUnionMemberTypeNodes(sourceNode, checker);
|
|
1436
|
+
const nonNullSourceNodes = unionMemberTypeNodes.filter(
|
|
1437
|
+
(memberTypeNode) => !isNullishTypeNode(resolveAliasedTypeNode(memberTypeNode, checker))
|
|
1438
|
+
);
|
|
1023
1439
|
const nonNullTypes = allTypes.filter(
|
|
1024
|
-
(
|
|
1440
|
+
(memberType) => !(memberType.flags & (ts4.TypeFlags.Null | ts4.TypeFlags.Undefined))
|
|
1025
1441
|
);
|
|
1442
|
+
const nonNullMembers = nonNullTypes.map((memberType, index) => ({
|
|
1443
|
+
memberType,
|
|
1444
|
+
sourceNode: nonNullSourceNodes.length === nonNullTypes.length ? nonNullSourceNodes[index] : void 0
|
|
1445
|
+
}));
|
|
1026
1446
|
const hasNull = allTypes.some((t) => t.flags & ts4.TypeFlags.Null);
|
|
1447
|
+
const memberDisplayNames = /* @__PURE__ */ new Map();
|
|
1448
|
+
if (namedDecl) {
|
|
1449
|
+
for (const [value, label] of extractDisplayNameMetadata(namedDecl).memberDisplayNames) {
|
|
1450
|
+
memberDisplayNames.set(value, label);
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
if (sourceNode) {
|
|
1454
|
+
for (const [value, label] of extractDisplayNameMetadata(sourceNode).memberDisplayNames) {
|
|
1455
|
+
memberDisplayNames.set(value, label);
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
const registerNamed = (result) => {
|
|
1459
|
+
if (!typeName) {
|
|
1460
|
+
return result;
|
|
1461
|
+
}
|
|
1462
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
1463
|
+
typeRegistry[typeName] = {
|
|
1464
|
+
name: typeName,
|
|
1465
|
+
type: result,
|
|
1466
|
+
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
1467
|
+
provenance: provenanceForDeclaration(namedDecl ?? sourceNode, file)
|
|
1468
|
+
};
|
|
1469
|
+
return { kind: "reference", name: typeName, typeArguments: [] };
|
|
1470
|
+
};
|
|
1471
|
+
const applyMemberLabels = (members2) => members2.map((value) => {
|
|
1472
|
+
const displayName = memberDisplayNames.get(String(value));
|
|
1473
|
+
return displayName !== void 0 ? { value, displayName } : { value };
|
|
1474
|
+
});
|
|
1027
1475
|
const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts4.TypeFlags.BooleanLiteral);
|
|
1028
1476
|
if (isBooleanUnion2) {
|
|
1029
1477
|
const boolNode = { kind: "primitive", primitiveKind: "boolean" };
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
}
|
|
1036
|
-
return boolNode;
|
|
1478
|
+
const result = hasNull ? {
|
|
1479
|
+
kind: "union",
|
|
1480
|
+
members: [boolNode, { kind: "primitive", primitiveKind: "null" }]
|
|
1481
|
+
} : boolNode;
|
|
1482
|
+
return registerNamed(result);
|
|
1037
1483
|
}
|
|
1038
1484
|
const allStringLiterals = nonNullTypes.every((t) => t.isStringLiteral());
|
|
1039
1485
|
if (allStringLiterals && nonNullTypes.length > 0) {
|
|
1040
1486
|
const stringTypes = nonNullTypes.filter((t) => t.isStringLiteral());
|
|
1041
1487
|
const enumNode = {
|
|
1042
1488
|
kind: "enum",
|
|
1043
|
-
members: stringTypes.map((t) =>
|
|
1489
|
+
members: applyMemberLabels(stringTypes.map((t) => t.value))
|
|
1044
1490
|
};
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
}
|
|
1051
|
-
return enumNode;
|
|
1491
|
+
const result = hasNull ? {
|
|
1492
|
+
kind: "union",
|
|
1493
|
+
members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
|
|
1494
|
+
} : enumNode;
|
|
1495
|
+
return registerNamed(result);
|
|
1052
1496
|
}
|
|
1053
1497
|
const allNumberLiterals = nonNullTypes.every((t) => t.isNumberLiteral());
|
|
1054
1498
|
if (allNumberLiterals && nonNullTypes.length > 0) {
|
|
1055
1499
|
const numberTypes = nonNullTypes.filter((t) => t.isNumberLiteral());
|
|
1056
1500
|
const enumNode = {
|
|
1057
1501
|
kind: "enum",
|
|
1058
|
-
members: numberTypes.map((t) =>
|
|
1502
|
+
members: applyMemberLabels(numberTypes.map((t) => t.value))
|
|
1059
1503
|
};
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1504
|
+
const result = hasNull ? {
|
|
1505
|
+
kind: "union",
|
|
1506
|
+
members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
|
|
1507
|
+
} : enumNode;
|
|
1508
|
+
return registerNamed(result);
|
|
1509
|
+
}
|
|
1510
|
+
if (nonNullMembers.length === 1 && nonNullMembers[0]) {
|
|
1511
|
+
const inner = resolveTypeNode(
|
|
1512
|
+
nonNullMembers[0].memberType,
|
|
1513
|
+
checker,
|
|
1514
|
+
file,
|
|
1515
|
+
typeRegistry,
|
|
1516
|
+
visiting,
|
|
1517
|
+
nonNullMembers[0].sourceNode ?? sourceNode,
|
|
1518
|
+
extensionRegistry
|
|
1519
|
+
);
|
|
1520
|
+
const result = hasNull ? {
|
|
1521
|
+
kind: "union",
|
|
1522
|
+
members: [inner, { kind: "primitive", primitiveKind: "null" }]
|
|
1523
|
+
} : inner;
|
|
1524
|
+
return registerNamed(result);
|
|
1525
|
+
}
|
|
1526
|
+
const members = nonNullMembers.map(
|
|
1527
|
+
({ memberType, sourceNode: memberSourceNode }) => resolveTypeNode(
|
|
1528
|
+
memberType,
|
|
1529
|
+
checker,
|
|
1530
|
+
file,
|
|
1531
|
+
typeRegistry,
|
|
1532
|
+
visiting,
|
|
1533
|
+
memberSourceNode ?? sourceNode,
|
|
1534
|
+
extensionRegistry
|
|
1535
|
+
)
|
|
1080
1536
|
);
|
|
1081
1537
|
if (hasNull) {
|
|
1082
1538
|
members.push({ kind: "primitive", primitiveKind: "null" });
|
|
1083
1539
|
}
|
|
1084
|
-
return { kind: "union", members };
|
|
1540
|
+
return registerNamed({ kind: "union", members });
|
|
1085
1541
|
}
|
|
1086
|
-
function resolveArrayType(type, checker, file, typeRegistry, visiting) {
|
|
1542
|
+
function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
1087
1543
|
const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
|
|
1088
1544
|
const elementType = typeArgs?.[0];
|
|
1089
|
-
const
|
|
1545
|
+
const elementSourceNode = extractArrayElementTypeNode(sourceNode, checker);
|
|
1546
|
+
const items = elementType ? resolveTypeNode(
|
|
1547
|
+
elementType,
|
|
1548
|
+
checker,
|
|
1549
|
+
file,
|
|
1550
|
+
typeRegistry,
|
|
1551
|
+
visiting,
|
|
1552
|
+
elementSourceNode,
|
|
1553
|
+
extensionRegistry
|
|
1554
|
+
) : { kind: "primitive", primitiveKind: "string" };
|
|
1090
1555
|
return { kind: "array", items };
|
|
1091
1556
|
}
|
|
1092
|
-
function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
|
|
1557
|
+
function tryResolveRecordType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1093
1558
|
if (type.getProperties().length > 0) {
|
|
1094
1559
|
return null;
|
|
1095
1560
|
}
|
|
@@ -1097,39 +1562,123 @@ function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
|
|
|
1097
1562
|
if (!indexInfo) {
|
|
1098
1563
|
return null;
|
|
1099
1564
|
}
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
}
|
|
1565
|
+
const valueType = resolveTypeNode(
|
|
1566
|
+
indexInfo.type,
|
|
1567
|
+
checker,
|
|
1568
|
+
file,
|
|
1569
|
+
typeRegistry,
|
|
1570
|
+
visiting,
|
|
1571
|
+
void 0,
|
|
1572
|
+
extensionRegistry
|
|
1573
|
+
);
|
|
1574
|
+
return { kind: "record", valueType };
|
|
1110
1575
|
}
|
|
1111
|
-
function
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1576
|
+
function typeNodeContainsReference(type, targetName) {
|
|
1577
|
+
switch (type.kind) {
|
|
1578
|
+
case "reference":
|
|
1579
|
+
return type.name === targetName;
|
|
1580
|
+
case "array":
|
|
1581
|
+
return typeNodeContainsReference(type.items, targetName);
|
|
1582
|
+
case "record":
|
|
1583
|
+
return typeNodeContainsReference(type.valueType, targetName);
|
|
1584
|
+
case "union":
|
|
1585
|
+
return type.members.some((member) => typeNodeContainsReference(member, targetName));
|
|
1586
|
+
case "object":
|
|
1587
|
+
return type.properties.some(
|
|
1588
|
+
(property) => typeNodeContainsReference(property.type, targetName)
|
|
1589
|
+
);
|
|
1590
|
+
case "primitive":
|
|
1591
|
+
case "enum":
|
|
1592
|
+
case "dynamic":
|
|
1593
|
+
case "custom":
|
|
1594
|
+
return false;
|
|
1595
|
+
default: {
|
|
1596
|
+
const _exhaustive = type;
|
|
1597
|
+
return _exhaustive;
|
|
1598
|
+
}
|
|
1115
1599
|
}
|
|
1600
|
+
}
|
|
1601
|
+
function resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1602
|
+
const typeName = getNamedTypeName(type);
|
|
1603
|
+
const namedTypeName = typeName ?? void 0;
|
|
1604
|
+
const namedDecl = getNamedTypeDeclaration(type);
|
|
1605
|
+
const shouldRegisterNamedType = namedTypeName !== void 0 && !(namedTypeName === "Record" && namedDecl?.getSourceFile().fileName !== file);
|
|
1606
|
+
const clearNamedTypeRegistration = () => {
|
|
1607
|
+
if (namedTypeName === void 0 || !shouldRegisterNamedType) {
|
|
1608
|
+
return;
|
|
1609
|
+
}
|
|
1610
|
+
Reflect.deleteProperty(typeRegistry, namedTypeName);
|
|
1611
|
+
};
|
|
1116
1612
|
if (visiting.has(type)) {
|
|
1613
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
1614
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
1615
|
+
}
|
|
1117
1616
|
return { kind: "object", properties: [], additionalProperties: false };
|
|
1118
1617
|
}
|
|
1618
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[namedTypeName]) {
|
|
1619
|
+
typeRegistry[namedTypeName] = {
|
|
1620
|
+
name: namedTypeName,
|
|
1621
|
+
type: RESOLVING_TYPE_PLACEHOLDER,
|
|
1622
|
+
provenance: provenanceForDeclaration(namedDecl, file)
|
|
1623
|
+
};
|
|
1624
|
+
}
|
|
1119
1625
|
visiting.add(type);
|
|
1120
|
-
|
|
1121
|
-
|
|
1626
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType && typeRegistry[namedTypeName]?.type !== void 0) {
|
|
1627
|
+
if (typeRegistry[namedTypeName].type !== RESOLVING_TYPE_PLACEHOLDER) {
|
|
1628
|
+
visiting.delete(type);
|
|
1629
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
const recordNode = tryResolveRecordType(
|
|
1633
|
+
type,
|
|
1634
|
+
checker,
|
|
1635
|
+
file,
|
|
1636
|
+
typeRegistry,
|
|
1637
|
+
visiting,
|
|
1638
|
+
extensionRegistry
|
|
1639
|
+
);
|
|
1640
|
+
if (recordNode) {
|
|
1122
1641
|
visiting.delete(type);
|
|
1123
|
-
|
|
1642
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
1643
|
+
const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, namedTypeName);
|
|
1644
|
+
if (!isRecursiveRecord) {
|
|
1645
|
+
clearNamedTypeRegistration();
|
|
1646
|
+
return recordNode;
|
|
1647
|
+
}
|
|
1648
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
1649
|
+
typeRegistry[namedTypeName] = {
|
|
1650
|
+
name: namedTypeName,
|
|
1651
|
+
type: recordNode,
|
|
1652
|
+
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
1653
|
+
provenance: provenanceForDeclaration(namedDecl, file)
|
|
1654
|
+
};
|
|
1655
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
1656
|
+
}
|
|
1657
|
+
return recordNode;
|
|
1124
1658
|
}
|
|
1125
1659
|
const properties = [];
|
|
1126
|
-
const fieldInfoMap = getNamedTypeFieldNodeInfoMap(
|
|
1660
|
+
const fieldInfoMap = getNamedTypeFieldNodeInfoMap(
|
|
1661
|
+
type,
|
|
1662
|
+
checker,
|
|
1663
|
+
file,
|
|
1664
|
+
typeRegistry,
|
|
1665
|
+
visiting,
|
|
1666
|
+
extensionRegistry
|
|
1667
|
+
);
|
|
1127
1668
|
for (const prop of type.getProperties()) {
|
|
1128
1669
|
const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
|
|
1129
1670
|
if (!declaration) continue;
|
|
1130
1671
|
const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
|
|
1131
1672
|
const optional = !!(prop.flags & ts4.SymbolFlags.Optional);
|
|
1132
|
-
const propTypeNode = resolveTypeNode(
|
|
1673
|
+
const propTypeNode = resolveTypeNode(
|
|
1674
|
+
propType,
|
|
1675
|
+
checker,
|
|
1676
|
+
file,
|
|
1677
|
+
typeRegistry,
|
|
1678
|
+
visiting,
|
|
1679
|
+
declaration,
|
|
1680
|
+
extensionRegistry
|
|
1681
|
+
);
|
|
1133
1682
|
const fieldNodeInfo = fieldInfoMap?.get(prop.name);
|
|
1134
1683
|
properties.push({
|
|
1135
1684
|
name: prop.name,
|
|
@@ -1146,17 +1695,19 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
1146
1695
|
properties,
|
|
1147
1696
|
additionalProperties: true
|
|
1148
1697
|
};
|
|
1149
|
-
if (
|
|
1150
|
-
|
|
1151
|
-
|
|
1698
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
1699
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
1700
|
+
typeRegistry[namedTypeName] = {
|
|
1701
|
+
name: namedTypeName,
|
|
1152
1702
|
type: objectNode,
|
|
1153
|
-
|
|
1703
|
+
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
1704
|
+
provenance: provenanceForDeclaration(namedDecl, file)
|
|
1154
1705
|
};
|
|
1155
|
-
return { kind: "reference", name:
|
|
1706
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
1156
1707
|
}
|
|
1157
1708
|
return objectNode;
|
|
1158
1709
|
}
|
|
1159
|
-
function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting) {
|
|
1710
|
+
function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1160
1711
|
const symbols = [type.getSymbol(), type.aliasSymbol].filter(
|
|
1161
1712
|
(s) => s?.declarations != null && s.declarations.length > 0
|
|
1162
1713
|
);
|
|
@@ -1168,7 +1719,14 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
1168
1719
|
const map = /* @__PURE__ */ new Map();
|
|
1169
1720
|
for (const member of classDecl.members) {
|
|
1170
1721
|
if (ts4.isPropertyDeclaration(member) && ts4.isIdentifier(member.name)) {
|
|
1171
|
-
const fieldNode = analyzeFieldToIR(
|
|
1722
|
+
const fieldNode = analyzeFieldToIR(
|
|
1723
|
+
member,
|
|
1724
|
+
checker,
|
|
1725
|
+
file,
|
|
1726
|
+
typeRegistry,
|
|
1727
|
+
visiting,
|
|
1728
|
+
extensionRegistry
|
|
1729
|
+
);
|
|
1172
1730
|
if (fieldNode) {
|
|
1173
1731
|
map.set(fieldNode.name, {
|
|
1174
1732
|
constraints: [...fieldNode.constraints],
|
|
@@ -1182,7 +1740,14 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
1182
1740
|
}
|
|
1183
1741
|
const interfaceDecl = declarations.find(ts4.isInterfaceDeclaration);
|
|
1184
1742
|
if (interfaceDecl) {
|
|
1185
|
-
return buildFieldNodeInfoMap(
|
|
1743
|
+
return buildFieldNodeInfoMap(
|
|
1744
|
+
interfaceDecl.members,
|
|
1745
|
+
checker,
|
|
1746
|
+
file,
|
|
1747
|
+
typeRegistry,
|
|
1748
|
+
visiting,
|
|
1749
|
+
extensionRegistry
|
|
1750
|
+
);
|
|
1186
1751
|
}
|
|
1187
1752
|
const typeAliasDecl = declarations.find(ts4.isTypeAliasDeclaration);
|
|
1188
1753
|
if (typeAliasDecl && ts4.isTypeLiteralNode(typeAliasDecl.type)) {
|
|
@@ -1191,17 +1756,68 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
1191
1756
|
checker,
|
|
1192
1757
|
file,
|
|
1193
1758
|
typeRegistry,
|
|
1194
|
-
visiting
|
|
1759
|
+
visiting,
|
|
1760
|
+
extensionRegistry
|
|
1195
1761
|
);
|
|
1196
1762
|
}
|
|
1197
1763
|
}
|
|
1198
1764
|
return null;
|
|
1199
1765
|
}
|
|
1200
|
-
function
|
|
1766
|
+
function extractArrayElementTypeNode(sourceNode, checker) {
|
|
1767
|
+
const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
|
|
1768
|
+
if (typeNode === void 0) {
|
|
1769
|
+
return void 0;
|
|
1770
|
+
}
|
|
1771
|
+
const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
|
|
1772
|
+
if (ts4.isArrayTypeNode(resolvedTypeNode)) {
|
|
1773
|
+
return resolvedTypeNode.elementType;
|
|
1774
|
+
}
|
|
1775
|
+
if (ts4.isTypeReferenceNode(resolvedTypeNode) && ts4.isIdentifier(resolvedTypeNode.typeName) && resolvedTypeNode.typeName.text === "Array" && resolvedTypeNode.typeArguments?.[0]) {
|
|
1776
|
+
return resolvedTypeNode.typeArguments[0];
|
|
1777
|
+
}
|
|
1778
|
+
return void 0;
|
|
1779
|
+
}
|
|
1780
|
+
function extractUnionMemberTypeNodes(sourceNode, checker) {
|
|
1781
|
+
const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
|
|
1782
|
+
if (!typeNode) {
|
|
1783
|
+
return [];
|
|
1784
|
+
}
|
|
1785
|
+
const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
|
|
1786
|
+
return ts4.isUnionTypeNode(resolvedTypeNode) ? [...resolvedTypeNode.types] : [];
|
|
1787
|
+
}
|
|
1788
|
+
function resolveAliasedTypeNode(typeNode, checker, visited = /* @__PURE__ */ new Set()) {
|
|
1789
|
+
if (ts4.isParenthesizedTypeNode(typeNode)) {
|
|
1790
|
+
return resolveAliasedTypeNode(typeNode.type, checker, visited);
|
|
1791
|
+
}
|
|
1792
|
+
if (!ts4.isTypeReferenceNode(typeNode) || !ts4.isIdentifier(typeNode.typeName)) {
|
|
1793
|
+
return typeNode;
|
|
1794
|
+
}
|
|
1795
|
+
const symbol = checker.getSymbolAtLocation(typeNode.typeName);
|
|
1796
|
+
const aliasDecl = symbol?.declarations?.find(ts4.isTypeAliasDeclaration);
|
|
1797
|
+
if (aliasDecl === void 0 || visited.has(aliasDecl)) {
|
|
1798
|
+
return typeNode;
|
|
1799
|
+
}
|
|
1800
|
+
visited.add(aliasDecl);
|
|
1801
|
+
return resolveAliasedTypeNode(aliasDecl.type, checker, visited);
|
|
1802
|
+
}
|
|
1803
|
+
function isNullishTypeNode(typeNode) {
|
|
1804
|
+
if (typeNode.kind === ts4.SyntaxKind.NullKeyword || typeNode.kind === ts4.SyntaxKind.UndefinedKeyword) {
|
|
1805
|
+
return true;
|
|
1806
|
+
}
|
|
1807
|
+
return ts4.isLiteralTypeNode(typeNode) && (typeNode.literal.kind === ts4.SyntaxKind.NullKeyword || typeNode.literal.kind === ts4.SyntaxKind.UndefinedKeyword);
|
|
1808
|
+
}
|
|
1809
|
+
function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1201
1810
|
const map = /* @__PURE__ */ new Map();
|
|
1202
1811
|
for (const member of members) {
|
|
1203
1812
|
if (ts4.isPropertySignature(member)) {
|
|
1204
|
-
const fieldNode = analyzeInterfacePropertyToIR(
|
|
1813
|
+
const fieldNode = analyzeInterfacePropertyToIR(
|
|
1814
|
+
member,
|
|
1815
|
+
checker,
|
|
1816
|
+
file,
|
|
1817
|
+
typeRegistry,
|
|
1818
|
+
visiting,
|
|
1819
|
+
extensionRegistry
|
|
1820
|
+
);
|
|
1205
1821
|
if (fieldNode) {
|
|
1206
1822
|
map.set(fieldNode.name, {
|
|
1207
1823
|
constraints: [...fieldNode.constraints],
|
|
@@ -1214,7 +1830,7 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting) {
|
|
|
1214
1830
|
return map;
|
|
1215
1831
|
}
|
|
1216
1832
|
var MAX_ALIAS_CHAIN_DEPTH = 8;
|
|
1217
|
-
function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
|
|
1833
|
+
function extractTypeAliasConstraintNodes(typeNode, checker, file, extensionRegistry, depth = 0) {
|
|
1218
1834
|
if (!ts4.isTypeReferenceNode(typeNode)) return [];
|
|
1219
1835
|
if (depth >= MAX_ALIAS_CHAIN_DEPTH) {
|
|
1220
1836
|
const aliasName = typeNode.typeName.getText();
|
|
@@ -1227,8 +1843,29 @@ function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
|
|
|
1227
1843
|
const aliasDecl = symbol.declarations.find(ts4.isTypeAliasDeclaration);
|
|
1228
1844
|
if (!aliasDecl) return [];
|
|
1229
1845
|
if (ts4.isTypeLiteralNode(aliasDecl.type)) return [];
|
|
1230
|
-
const
|
|
1231
|
-
|
|
1846
|
+
const aliasFieldType = resolveTypeNode(
|
|
1847
|
+
checker.getTypeAtLocation(aliasDecl.type),
|
|
1848
|
+
checker,
|
|
1849
|
+
file,
|
|
1850
|
+
{},
|
|
1851
|
+
/* @__PURE__ */ new Set(),
|
|
1852
|
+
aliasDecl.type,
|
|
1853
|
+
extensionRegistry
|
|
1854
|
+
);
|
|
1855
|
+
const constraints = extractJSDocConstraintNodes(
|
|
1856
|
+
aliasDecl,
|
|
1857
|
+
file,
|
|
1858
|
+
makeParseOptions(extensionRegistry, aliasFieldType)
|
|
1859
|
+
);
|
|
1860
|
+
constraints.push(
|
|
1861
|
+
...extractTypeAliasConstraintNodes(
|
|
1862
|
+
aliasDecl.type,
|
|
1863
|
+
checker,
|
|
1864
|
+
file,
|
|
1865
|
+
extensionRegistry,
|
|
1866
|
+
depth + 1
|
|
1867
|
+
)
|
|
1868
|
+
);
|
|
1232
1869
|
return constraints;
|
|
1233
1870
|
}
|
|
1234
1871
|
function provenanceForNode(node, file) {
|
|
@@ -1244,6 +1881,12 @@ function provenanceForNode(node, file) {
|
|
|
1244
1881
|
function provenanceForFile(file) {
|
|
1245
1882
|
return { surface: "tsdoc", file, line: 0, column: 0 };
|
|
1246
1883
|
}
|
|
1884
|
+
function provenanceForDeclaration(node, file) {
|
|
1885
|
+
if (!node) {
|
|
1886
|
+
return provenanceForFile(file);
|
|
1887
|
+
}
|
|
1888
|
+
return provenanceForNode(node, file);
|
|
1889
|
+
}
|
|
1247
1890
|
function getNamedTypeName(type) {
|
|
1248
1891
|
const symbol = type.getSymbol();
|
|
1249
1892
|
if (symbol?.declarations) {
|
|
@@ -1262,6 +1905,20 @@ function getNamedTypeName(type) {
|
|
|
1262
1905
|
}
|
|
1263
1906
|
return null;
|
|
1264
1907
|
}
|
|
1908
|
+
function getNamedTypeDeclaration(type) {
|
|
1909
|
+
const symbol = type.getSymbol();
|
|
1910
|
+
if (symbol?.declarations) {
|
|
1911
|
+
const decl = symbol.declarations[0];
|
|
1912
|
+
if (decl && (ts4.isClassDeclaration(decl) || ts4.isInterfaceDeclaration(decl) || ts4.isTypeAliasDeclaration(decl))) {
|
|
1913
|
+
return decl;
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
const aliasSymbol = type.aliasSymbol;
|
|
1917
|
+
if (aliasSymbol?.declarations) {
|
|
1918
|
+
return aliasSymbol.declarations.find(ts4.isTypeAliasDeclaration);
|
|
1919
|
+
}
|
|
1920
|
+
return void 0;
|
|
1921
|
+
}
|
|
1265
1922
|
function analyzeMethod(method, checker) {
|
|
1266
1923
|
if (!ts4.isIdentifier(method.name)) {
|
|
1267
1924
|
return null;
|
|
@@ -1321,6 +1978,9 @@ function generateJsonSchemaFromIR(ir, options) {
|
|
|
1321
1978
|
const ctx = makeContext(options);
|
|
1322
1979
|
for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
|
|
1323
1980
|
ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
|
|
1981
|
+
if (typeDef.annotations && typeDef.annotations.length > 0) {
|
|
1982
|
+
applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
|
|
1983
|
+
}
|
|
1324
1984
|
}
|
|
1325
1985
|
const properties = {};
|
|
1326
1986
|
const required = [];
|
|
@@ -1332,6 +1992,9 @@ function generateJsonSchemaFromIR(ir, options) {
|
|
|
1332
1992
|
properties,
|
|
1333
1993
|
...uniqueRequired.length > 0 && { required: uniqueRequired }
|
|
1334
1994
|
};
|
|
1995
|
+
if (ir.annotations && ir.annotations.length > 0) {
|
|
1996
|
+
applyAnnotations(result, ir.annotations, ctx);
|
|
1997
|
+
}
|
|
1335
1998
|
if (Object.keys(ctx.defs).length > 0) {
|
|
1336
1999
|
result.$defs = ctx.defs;
|
|
1337
2000
|
}
|
|
@@ -1361,22 +2024,51 @@ function collectFields(elements, properties, required, ctx) {
|
|
|
1361
2024
|
}
|
|
1362
2025
|
function generateFieldSchema(field, ctx) {
|
|
1363
2026
|
const schema = generateTypeNode(field.type, ctx);
|
|
2027
|
+
const itemStringSchema = schema.type === "array" && schema.items?.type === "string" ? schema.items : void 0;
|
|
1364
2028
|
const directConstraints = [];
|
|
2029
|
+
const itemConstraints = [];
|
|
1365
2030
|
const pathConstraints = [];
|
|
1366
2031
|
for (const c of field.constraints) {
|
|
1367
2032
|
if (c.path) {
|
|
1368
2033
|
pathConstraints.push(c);
|
|
2034
|
+
} else if (itemStringSchema !== void 0 && isStringItemConstraint(c)) {
|
|
2035
|
+
itemConstraints.push(c);
|
|
1369
2036
|
} else {
|
|
1370
2037
|
directConstraints.push(c);
|
|
1371
2038
|
}
|
|
1372
2039
|
}
|
|
1373
2040
|
applyConstraints(schema, directConstraints, ctx);
|
|
1374
|
-
|
|
2041
|
+
if (itemStringSchema !== void 0) {
|
|
2042
|
+
applyConstraints(itemStringSchema, itemConstraints, ctx);
|
|
2043
|
+
}
|
|
2044
|
+
const rootAnnotations = [];
|
|
2045
|
+
const itemAnnotations = [];
|
|
2046
|
+
for (const annotation of field.annotations) {
|
|
2047
|
+
if (itemStringSchema !== void 0 && annotation.annotationKind === "format") {
|
|
2048
|
+
itemAnnotations.push(annotation);
|
|
2049
|
+
} else {
|
|
2050
|
+
rootAnnotations.push(annotation);
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
applyAnnotations(schema, rootAnnotations, ctx);
|
|
2054
|
+
if (itemStringSchema !== void 0) {
|
|
2055
|
+
applyAnnotations(itemStringSchema, itemAnnotations, ctx);
|
|
2056
|
+
}
|
|
1375
2057
|
if (pathConstraints.length === 0) {
|
|
1376
2058
|
return schema;
|
|
1377
2059
|
}
|
|
1378
2060
|
return applyPathTargetedConstraints(schema, pathConstraints, ctx);
|
|
1379
2061
|
}
|
|
2062
|
+
function isStringItemConstraint(constraint) {
|
|
2063
|
+
switch (constraint.constraintKind) {
|
|
2064
|
+
case "minLength":
|
|
2065
|
+
case "maxLength":
|
|
2066
|
+
case "pattern":
|
|
2067
|
+
return true;
|
|
2068
|
+
default:
|
|
2069
|
+
return false;
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
1380
2072
|
function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
|
|
1381
2073
|
if (schema.type === "array" && schema.items) {
|
|
1382
2074
|
schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx);
|
|
@@ -1594,6 +2286,9 @@ function applyConstraints(schema, constraints, ctx) {
|
|
|
1594
2286
|
case "uniqueItems":
|
|
1595
2287
|
schema.uniqueItems = constraint.value;
|
|
1596
2288
|
break;
|
|
2289
|
+
case "const":
|
|
2290
|
+
schema.const = constraint.value;
|
|
2291
|
+
break;
|
|
1597
2292
|
case "allowedMembers":
|
|
1598
2293
|
break;
|
|
1599
2294
|
case "custom":
|
|
@@ -1618,8 +2313,14 @@ function applyAnnotations(schema, annotations, ctx) {
|
|
|
1618
2313
|
case "defaultValue":
|
|
1619
2314
|
schema.default = annotation.value;
|
|
1620
2315
|
break;
|
|
2316
|
+
case "format":
|
|
2317
|
+
schema.format = annotation.value;
|
|
2318
|
+
break;
|
|
1621
2319
|
case "deprecated":
|
|
1622
2320
|
schema.deprecated = true;
|
|
2321
|
+
if (annotation.message !== void 0 && annotation.message !== "") {
|
|
2322
|
+
schema["x-formspec-deprecation-description"] = annotation.message;
|
|
2323
|
+
}
|
|
1623
2324
|
break;
|
|
1624
2325
|
case "placeholder":
|
|
1625
2326
|
break;
|
|
@@ -1651,7 +2352,12 @@ function applyCustomConstraint(schema, constraint, ctx) {
|
|
|
1651
2352
|
`Cannot generate JSON Schema for custom constraint "${constraint.constraintId}" without a matching extension registration`
|
|
1652
2353
|
);
|
|
1653
2354
|
}
|
|
1654
|
-
|
|
2355
|
+
assignVendorPrefixedExtensionKeywords(
|
|
2356
|
+
schema,
|
|
2357
|
+
registration.toJsonSchema(constraint.payload, ctx.vendorPrefix),
|
|
2358
|
+
ctx.vendorPrefix,
|
|
2359
|
+
`custom constraint "${constraint.constraintId}"`
|
|
2360
|
+
);
|
|
1655
2361
|
}
|
|
1656
2362
|
function applyCustomAnnotation(schema, annotation, ctx) {
|
|
1657
2363
|
const registration = ctx.extensionRegistry?.findAnnotation(annotation.annotationId);
|
|
@@ -1663,7 +2369,22 @@ function applyCustomAnnotation(schema, annotation, ctx) {
|
|
|
1663
2369
|
if (registration.toJsonSchema === void 0) {
|
|
1664
2370
|
return;
|
|
1665
2371
|
}
|
|
1666
|
-
|
|
2372
|
+
assignVendorPrefixedExtensionKeywords(
|
|
2373
|
+
schema,
|
|
2374
|
+
registration.toJsonSchema(annotation.value, ctx.vendorPrefix),
|
|
2375
|
+
ctx.vendorPrefix,
|
|
2376
|
+
`custom annotation "${annotation.annotationId}"`
|
|
2377
|
+
);
|
|
2378
|
+
}
|
|
2379
|
+
function assignVendorPrefixedExtensionKeywords(schema, extensionSchema, vendorPrefix, source) {
|
|
2380
|
+
for (const [key, value] of Object.entries(extensionSchema)) {
|
|
2381
|
+
if (!key.startsWith(`${vendorPrefix}-`)) {
|
|
2382
|
+
throw new Error(
|
|
2383
|
+
`Cannot apply ${source}: extension hooks may only emit "${vendorPrefix}-*" JSON Schema keywords`
|
|
2384
|
+
);
|
|
2385
|
+
}
|
|
2386
|
+
schema[key] = value;
|
|
2387
|
+
}
|
|
1667
2388
|
}
|
|
1668
2389
|
|
|
1669
2390
|
// src/ui-schema/schema.ts
|
|
@@ -1801,25 +2522,31 @@ function createShowRule(fieldName, value) {
|
|
|
1801
2522
|
}
|
|
1802
2523
|
};
|
|
1803
2524
|
}
|
|
2525
|
+
function flattenConditionSchema(scope, schema) {
|
|
2526
|
+
if (schema.allOf === void 0) {
|
|
2527
|
+
if (scope === "#") {
|
|
2528
|
+
return [schema];
|
|
2529
|
+
}
|
|
2530
|
+
const fieldName = scope.replace("#/properties/", "");
|
|
2531
|
+
return [
|
|
2532
|
+
{
|
|
2533
|
+
properties: {
|
|
2534
|
+
[fieldName]: schema
|
|
2535
|
+
}
|
|
2536
|
+
}
|
|
2537
|
+
];
|
|
2538
|
+
}
|
|
2539
|
+
return schema.allOf.flatMap((member) => flattenConditionSchema(scope, member));
|
|
2540
|
+
}
|
|
1804
2541
|
function combineRules(parentRule, childRule) {
|
|
1805
|
-
const parentCondition = parentRule.condition;
|
|
1806
|
-
const childCondition = childRule.condition;
|
|
1807
2542
|
return {
|
|
1808
2543
|
effect: "SHOW",
|
|
1809
2544
|
condition: {
|
|
1810
2545
|
scope: "#",
|
|
1811
2546
|
schema: {
|
|
1812
2547
|
allOf: [
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
[parentCondition.scope.replace("#/properties/", "")]: parentCondition.schema
|
|
1816
|
-
}
|
|
1817
|
-
},
|
|
1818
|
-
{
|
|
1819
|
-
properties: {
|
|
1820
|
-
[childCondition.scope.replace("#/properties/", "")]: childCondition.schema
|
|
1821
|
-
}
|
|
1822
|
-
}
|
|
2548
|
+
...flattenConditionSchema(parentRule.condition.scope, parentRule.condition.schema),
|
|
2549
|
+
...flattenConditionSchema(childRule.condition.scope, childRule.condition.schema)
|
|
1823
2550
|
]
|
|
1824
2551
|
}
|
|
1825
2552
|
}
|
|
@@ -1827,10 +2554,14 @@ function combineRules(parentRule, childRule) {
|
|
|
1827
2554
|
}
|
|
1828
2555
|
function fieldNodeToControl(field, parentRule) {
|
|
1829
2556
|
const displayNameAnnotation = field.annotations.find((a) => a.annotationKind === "displayName");
|
|
2557
|
+
const placeholderAnnotation = field.annotations.find((a) => a.annotationKind === "placeholder");
|
|
1830
2558
|
const control = {
|
|
1831
2559
|
type: "Control",
|
|
1832
2560
|
scope: fieldToScope(field.name),
|
|
1833
2561
|
...displayNameAnnotation !== void 0 && { label: displayNameAnnotation.value },
|
|
2562
|
+
...placeholderAnnotation !== void 0 && {
|
|
2563
|
+
options: { placeholder: placeholderAnnotation.value }
|
|
2564
|
+
},
|
|
1834
2565
|
...parentRule !== void 0 && { rule: parentRule }
|
|
1835
2566
|
};
|
|
1836
2567
|
return control;
|
|
@@ -1880,15 +2611,16 @@ function generateUiSchemaFromIR(ir) {
|
|
|
1880
2611
|
}
|
|
1881
2612
|
|
|
1882
2613
|
// src/generators/class-schema.ts
|
|
1883
|
-
function generateClassSchemas(analysis, source) {
|
|
2614
|
+
function generateClassSchemas(analysis, source, options) {
|
|
1884
2615
|
const ir = canonicalizeTSDoc(analysis, source);
|
|
1885
2616
|
return {
|
|
1886
|
-
jsonSchema: generateJsonSchemaFromIR(ir),
|
|
2617
|
+
jsonSchema: generateJsonSchemaFromIR(ir, options),
|
|
1887
2618
|
uiSchema: generateUiSchemaFromIR(ir)
|
|
1888
2619
|
};
|
|
1889
2620
|
}
|
|
1890
2621
|
|
|
1891
2622
|
// src/validate/constraint-validator.ts
|
|
2623
|
+
import { normalizeConstraintTagName as normalizeConstraintTagName2 } from "@formspec/core";
|
|
1892
2624
|
function addContradiction(ctx, message, primary, related) {
|
|
1893
2625
|
ctx.diagnostics.push({
|
|
1894
2626
|
code: "CONTRADICTING_CONSTRAINTS",
|
|
@@ -1916,6 +2648,15 @@ function addUnknownExtension(ctx, message, primary) {
|
|
|
1916
2648
|
relatedLocations: []
|
|
1917
2649
|
});
|
|
1918
2650
|
}
|
|
2651
|
+
function addUnknownPathTarget(ctx, message, primary) {
|
|
2652
|
+
ctx.diagnostics.push({
|
|
2653
|
+
code: "UNKNOWN_PATH_TARGET",
|
|
2654
|
+
message,
|
|
2655
|
+
severity: "error",
|
|
2656
|
+
primaryLocation: primary,
|
|
2657
|
+
relatedLocations: []
|
|
2658
|
+
});
|
|
2659
|
+
}
|
|
1919
2660
|
function addConstraintBroadening(ctx, message, primary, related) {
|
|
1920
2661
|
ctx.diagnostics.push({
|
|
1921
2662
|
code: "CONSTRAINT_BROADENING",
|
|
@@ -1925,6 +2666,13 @@ function addConstraintBroadening(ctx, message, primary, related) {
|
|
|
1925
2666
|
relatedLocations: [related]
|
|
1926
2667
|
});
|
|
1927
2668
|
}
|
|
2669
|
+
function getExtensionIdFromConstraintId(constraintId) {
|
|
2670
|
+
const separator = constraintId.lastIndexOf("/");
|
|
2671
|
+
if (separator <= 0) {
|
|
2672
|
+
return null;
|
|
2673
|
+
}
|
|
2674
|
+
return constraintId.slice(0, separator);
|
|
2675
|
+
}
|
|
1928
2676
|
function findNumeric(constraints, constraintKind) {
|
|
1929
2677
|
return constraints.find((c) => c.constraintKind === constraintKind);
|
|
1930
2678
|
}
|
|
@@ -1936,6 +2684,45 @@ function findAllowedMembers(constraints) {
|
|
|
1936
2684
|
(c) => c.constraintKind === "allowedMembers"
|
|
1937
2685
|
);
|
|
1938
2686
|
}
|
|
2687
|
+
function findConstConstraints(constraints) {
|
|
2688
|
+
return constraints.filter(
|
|
2689
|
+
(c) => c.constraintKind === "const"
|
|
2690
|
+
);
|
|
2691
|
+
}
|
|
2692
|
+
function jsonValueEquals(left, right) {
|
|
2693
|
+
if (left === right) {
|
|
2694
|
+
return true;
|
|
2695
|
+
}
|
|
2696
|
+
if (Array.isArray(left) || Array.isArray(right)) {
|
|
2697
|
+
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
|
|
2698
|
+
return false;
|
|
2699
|
+
}
|
|
2700
|
+
return left.every((item, index) => jsonValueEquals(item, right[index]));
|
|
2701
|
+
}
|
|
2702
|
+
if (isJsonObject(left) || isJsonObject(right)) {
|
|
2703
|
+
if (!isJsonObject(left) || !isJsonObject(right)) {
|
|
2704
|
+
return false;
|
|
2705
|
+
}
|
|
2706
|
+
const leftKeys = Object.keys(left).sort();
|
|
2707
|
+
const rightKeys = Object.keys(right).sort();
|
|
2708
|
+
if (leftKeys.length !== rightKeys.length) {
|
|
2709
|
+
return false;
|
|
2710
|
+
}
|
|
2711
|
+
return leftKeys.every((key, index) => {
|
|
2712
|
+
const rightKey = rightKeys[index];
|
|
2713
|
+
if (rightKey !== key) {
|
|
2714
|
+
return false;
|
|
2715
|
+
}
|
|
2716
|
+
const leftValue = left[key];
|
|
2717
|
+
const rightValue = right[rightKey];
|
|
2718
|
+
return leftValue !== void 0 && rightValue !== void 0 && jsonValueEquals(leftValue, rightValue);
|
|
2719
|
+
});
|
|
2720
|
+
}
|
|
2721
|
+
return false;
|
|
2722
|
+
}
|
|
2723
|
+
function isJsonObject(value) {
|
|
2724
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2725
|
+
}
|
|
1939
2726
|
function isOrderedBoundConstraint(constraint) {
|
|
1940
2727
|
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";
|
|
1941
2728
|
}
|
|
@@ -2056,6 +2843,112 @@ function checkConstraintBroadening(ctx, fieldName, constraints) {
|
|
|
2056
2843
|
strongestByKey.set(key, constraint);
|
|
2057
2844
|
}
|
|
2058
2845
|
}
|
|
2846
|
+
function compareCustomConstraintStrength(current, previous) {
|
|
2847
|
+
const order = current.comparePayloads(current.constraint.payload, previous.constraint.payload);
|
|
2848
|
+
const equalPayloadTiebreaker = order === 0 ? compareSemanticInclusivity(current.role.inclusive, previous.role.inclusive) : order;
|
|
2849
|
+
switch (current.role.bound) {
|
|
2850
|
+
case "lower":
|
|
2851
|
+
return equalPayloadTiebreaker;
|
|
2852
|
+
case "upper":
|
|
2853
|
+
return equalPayloadTiebreaker === 0 ? 0 : -equalPayloadTiebreaker;
|
|
2854
|
+
case "exact":
|
|
2855
|
+
return order === 0 ? 0 : Number.NaN;
|
|
2856
|
+
default: {
|
|
2857
|
+
const _exhaustive = current.role.bound;
|
|
2858
|
+
return _exhaustive;
|
|
2859
|
+
}
|
|
2860
|
+
}
|
|
2861
|
+
}
|
|
2862
|
+
function compareSemanticInclusivity(currentInclusive, previousInclusive) {
|
|
2863
|
+
if (currentInclusive === previousInclusive) {
|
|
2864
|
+
return 0;
|
|
2865
|
+
}
|
|
2866
|
+
return currentInclusive ? -1 : 1;
|
|
2867
|
+
}
|
|
2868
|
+
function customConstraintsContradict(lower, upper) {
|
|
2869
|
+
const order = lower.comparePayloads(lower.constraint.payload, upper.constraint.payload);
|
|
2870
|
+
if (order > 0) {
|
|
2871
|
+
return true;
|
|
2872
|
+
}
|
|
2873
|
+
if (order < 0) {
|
|
2874
|
+
return false;
|
|
2875
|
+
}
|
|
2876
|
+
return !lower.role.inclusive || !upper.role.inclusive;
|
|
2877
|
+
}
|
|
2878
|
+
function describeCustomConstraintTag(constraint) {
|
|
2879
|
+
return constraint.provenance.tagName ?? constraint.constraintId;
|
|
2880
|
+
}
|
|
2881
|
+
function checkCustomConstraintSemantics(ctx, fieldName, constraints) {
|
|
2882
|
+
if (ctx.extensionRegistry === void 0) {
|
|
2883
|
+
return;
|
|
2884
|
+
}
|
|
2885
|
+
const strongestByKey = /* @__PURE__ */ new Map();
|
|
2886
|
+
const lowerByFamily = /* @__PURE__ */ new Map();
|
|
2887
|
+
const upperByFamily = /* @__PURE__ */ new Map();
|
|
2888
|
+
for (const constraint of constraints) {
|
|
2889
|
+
if (constraint.constraintKind !== "custom") {
|
|
2890
|
+
continue;
|
|
2891
|
+
}
|
|
2892
|
+
const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
|
|
2893
|
+
if (registration?.comparePayloads === void 0 || registration.semanticRole === void 0) {
|
|
2894
|
+
continue;
|
|
2895
|
+
}
|
|
2896
|
+
const entry = {
|
|
2897
|
+
constraint,
|
|
2898
|
+
comparePayloads: registration.comparePayloads,
|
|
2899
|
+
role: registration.semanticRole
|
|
2900
|
+
};
|
|
2901
|
+
const familyKey = `${registration.semanticRole.family}:${pathKey(constraint)}`;
|
|
2902
|
+
const boundKey = `${familyKey}:${registration.semanticRole.bound}`;
|
|
2903
|
+
const previous = strongestByKey.get(boundKey);
|
|
2904
|
+
if (previous !== void 0) {
|
|
2905
|
+
const strength = compareCustomConstraintStrength(entry, previous);
|
|
2906
|
+
if (Number.isNaN(strength)) {
|
|
2907
|
+
addContradiction(
|
|
2908
|
+
ctx,
|
|
2909
|
+
`Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} conflicts with ${describeCustomConstraintTag(previous.constraint)}`,
|
|
2910
|
+
constraint.provenance,
|
|
2911
|
+
previous.constraint.provenance
|
|
2912
|
+
);
|
|
2913
|
+
continue;
|
|
2914
|
+
}
|
|
2915
|
+
if (strength < 0) {
|
|
2916
|
+
addConstraintBroadening(
|
|
2917
|
+
ctx,
|
|
2918
|
+
`Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} is broader than earlier ${describeCustomConstraintTag(previous.constraint)}. Constraints can only narrow.`,
|
|
2919
|
+
constraint.provenance,
|
|
2920
|
+
previous.constraint.provenance
|
|
2921
|
+
);
|
|
2922
|
+
continue;
|
|
2923
|
+
}
|
|
2924
|
+
if (strength > 0) {
|
|
2925
|
+
strongestByKey.set(boundKey, entry);
|
|
2926
|
+
}
|
|
2927
|
+
} else {
|
|
2928
|
+
strongestByKey.set(boundKey, entry);
|
|
2929
|
+
}
|
|
2930
|
+
if (registration.semanticRole.bound === "lower") {
|
|
2931
|
+
lowerByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
|
|
2932
|
+
} else if (registration.semanticRole.bound === "upper") {
|
|
2933
|
+
upperByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
|
|
2934
|
+
}
|
|
2935
|
+
}
|
|
2936
|
+
for (const [familyKey, lower] of lowerByFamily) {
|
|
2937
|
+
const upper = upperByFamily.get(familyKey);
|
|
2938
|
+
if (upper === void 0) {
|
|
2939
|
+
continue;
|
|
2940
|
+
}
|
|
2941
|
+
if (!customConstraintsContradict(lower, upper)) {
|
|
2942
|
+
continue;
|
|
2943
|
+
}
|
|
2944
|
+
addContradiction(
|
|
2945
|
+
ctx,
|
|
2946
|
+
`Field "${formatPathTargetFieldName(fieldName, lower.constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(lower.constraint)} contradicts ${describeCustomConstraintTag(upper.constraint)}`,
|
|
2947
|
+
lower.constraint.provenance,
|
|
2948
|
+
upper.constraint.provenance
|
|
2949
|
+
);
|
|
2950
|
+
}
|
|
2951
|
+
}
|
|
2059
2952
|
function checkNumericContradictions(ctx, fieldName, constraints) {
|
|
2060
2953
|
const min = findNumeric(constraints, "minimum");
|
|
2061
2954
|
const max = findNumeric(constraints, "maximum");
|
|
@@ -2142,6 +3035,25 @@ function checkAllowedMembersContradiction(ctx, fieldName, constraints) {
|
|
|
2142
3035
|
}
|
|
2143
3036
|
}
|
|
2144
3037
|
}
|
|
3038
|
+
function checkConstContradictions(ctx, fieldName, constraints) {
|
|
3039
|
+
const constConstraints = findConstConstraints(constraints);
|
|
3040
|
+
if (constConstraints.length < 2) return;
|
|
3041
|
+
const first = constConstraints[0];
|
|
3042
|
+
if (first === void 0) return;
|
|
3043
|
+
for (let i = 1; i < constConstraints.length; i++) {
|
|
3044
|
+
const current = constConstraints[i];
|
|
3045
|
+
if (current === void 0) continue;
|
|
3046
|
+
if (jsonValueEquals(first.value, current.value)) {
|
|
3047
|
+
continue;
|
|
3048
|
+
}
|
|
3049
|
+
addContradiction(
|
|
3050
|
+
ctx,
|
|
3051
|
+
`Field "${fieldName}": conflicting @const constraints require both ${JSON.stringify(first.value)} and ${JSON.stringify(current.value)}`,
|
|
3052
|
+
first.provenance,
|
|
3053
|
+
current.provenance
|
|
3054
|
+
);
|
|
3055
|
+
}
|
|
3056
|
+
}
|
|
2145
3057
|
function typeLabel(type) {
|
|
2146
3058
|
switch (type.kind) {
|
|
2147
3059
|
case "primitive":
|
|
@@ -2214,6 +3126,8 @@ function checkConstraintOnType(ctx, fieldName, type, constraint) {
|
|
|
2214
3126
|
const isString = effectiveType.kind === "primitive" && effectiveType.primitiveKind === "string";
|
|
2215
3127
|
const isArray = effectiveType.kind === "array";
|
|
2216
3128
|
const isEnum = effectiveType.kind === "enum";
|
|
3129
|
+
const arrayItemType = effectiveType.kind === "array" ? dereferenceType(ctx, effectiveType.items) : void 0;
|
|
3130
|
+
const isStringArray = arrayItemType?.kind === "primitive" && arrayItemType.primitiveKind === "string";
|
|
2217
3131
|
const label = typeLabel(effectiveType);
|
|
2218
3132
|
const ck = constraint.constraintKind;
|
|
2219
3133
|
switch (ck) {
|
|
@@ -2234,10 +3148,10 @@ function checkConstraintOnType(ctx, fieldName, type, constraint) {
|
|
|
2234
3148
|
case "minLength":
|
|
2235
3149
|
case "maxLength":
|
|
2236
3150
|
case "pattern": {
|
|
2237
|
-
if (!isString) {
|
|
3151
|
+
if (!isString && !isStringArray) {
|
|
2238
3152
|
addTypeMismatch(
|
|
2239
3153
|
ctx,
|
|
2240
|
-
`Field "${fieldName}": constraint "${ck}" is only valid on string fields, but field type is "${label}"`,
|
|
3154
|
+
`Field "${fieldName}": constraint "${ck}" is only valid on string fields or string array items, but field type is "${label}"`,
|
|
2241
3155
|
constraint.provenance
|
|
2242
3156
|
);
|
|
2243
3157
|
}
|
|
@@ -2265,6 +3179,37 @@ function checkConstraintOnType(ctx, fieldName, type, constraint) {
|
|
|
2265
3179
|
}
|
|
2266
3180
|
break;
|
|
2267
3181
|
}
|
|
3182
|
+
case "const": {
|
|
3183
|
+
const isPrimitiveConstType = effectiveType.kind === "primitive" && ["string", "number", "boolean", "null"].includes(effectiveType.primitiveKind) || effectiveType.kind === "enum";
|
|
3184
|
+
if (!isPrimitiveConstType) {
|
|
3185
|
+
addTypeMismatch(
|
|
3186
|
+
ctx,
|
|
3187
|
+
`Field "${fieldName}": constraint "const" is only valid on primitive or enum fields, but field type is "${label}"`,
|
|
3188
|
+
constraint.provenance
|
|
3189
|
+
);
|
|
3190
|
+
break;
|
|
3191
|
+
}
|
|
3192
|
+
if (effectiveType.kind === "primitive") {
|
|
3193
|
+
const valueType = constraint.value === null ? "null" : Array.isArray(constraint.value) ? "array" : typeof constraint.value;
|
|
3194
|
+
if (valueType !== effectiveType.primitiveKind) {
|
|
3195
|
+
addTypeMismatch(
|
|
3196
|
+
ctx,
|
|
3197
|
+
`Field "${fieldName}": @const value type "${valueType}" is incompatible with field type "${effectiveType.primitiveKind}"`,
|
|
3198
|
+
constraint.provenance
|
|
3199
|
+
);
|
|
3200
|
+
}
|
|
3201
|
+
break;
|
|
3202
|
+
}
|
|
3203
|
+
const memberValues = effectiveType.members.map((member) => member.value);
|
|
3204
|
+
if (!memberValues.some((member) => jsonValueEquals(member, constraint.value))) {
|
|
3205
|
+
addTypeMismatch(
|
|
3206
|
+
ctx,
|
|
3207
|
+
`Field "${fieldName}": @const value ${JSON.stringify(constraint.value)} is not one of the enum members`,
|
|
3208
|
+
constraint.provenance
|
|
3209
|
+
);
|
|
3210
|
+
}
|
|
3211
|
+
break;
|
|
3212
|
+
}
|
|
2268
3213
|
case "custom": {
|
|
2269
3214
|
checkCustomConstraint(ctx, fieldName, effectiveType, constraint);
|
|
2270
3215
|
break;
|
|
@@ -2283,9 +3228,9 @@ function checkTypeApplicability(ctx, fieldName, type, constraints) {
|
|
|
2283
3228
|
const resolution = resolvePathTargetType(ctx, type, constraint.path.segments);
|
|
2284
3229
|
const targetFieldName = formatPathTargetFieldName(fieldName, constraint.path.segments);
|
|
2285
3230
|
if (resolution.kind === "missing-property") {
|
|
2286
|
-
|
|
3231
|
+
addUnknownPathTarget(
|
|
2287
3232
|
ctx,
|
|
2288
|
-
`Field "${
|
|
3233
|
+
`Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" references unknown path segment "${resolution.segment}"`,
|
|
2289
3234
|
constraint.provenance
|
|
2290
3235
|
);
|
|
2291
3236
|
continue;
|
|
@@ -2315,8 +3260,30 @@ function checkCustomConstraint(ctx, fieldName, type, constraint) {
|
|
|
2315
3260
|
);
|
|
2316
3261
|
return;
|
|
2317
3262
|
}
|
|
2318
|
-
|
|
2319
|
-
if (
|
|
3263
|
+
const normalizedTagName = constraint.provenance.tagName === void 0 ? void 0 : normalizeConstraintTagName2(constraint.provenance.tagName.replace(/^@/, ""));
|
|
3264
|
+
if (normalizedTagName !== void 0) {
|
|
3265
|
+
const tagRegistration = ctx.extensionRegistry.findConstraintTag(normalizedTagName);
|
|
3266
|
+
const extensionId = getExtensionIdFromConstraintId(constraint.constraintId);
|
|
3267
|
+
if (extensionId !== null && tagRegistration?.extensionId === extensionId && tagRegistration.registration.constraintName === registration.constraintName && tagRegistration.registration.isApplicableToType?.(type) === false) {
|
|
3268
|
+
addTypeMismatch(
|
|
3269
|
+
ctx,
|
|
3270
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
3271
|
+
constraint.provenance
|
|
3272
|
+
);
|
|
3273
|
+
return;
|
|
3274
|
+
}
|
|
3275
|
+
}
|
|
3276
|
+
if (registration.applicableTypes === null) {
|
|
3277
|
+
if (registration.isApplicableToType?.(type) === false) {
|
|
3278
|
+
addTypeMismatch(
|
|
3279
|
+
ctx,
|
|
3280
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
3281
|
+
constraint.provenance
|
|
3282
|
+
);
|
|
3283
|
+
}
|
|
3284
|
+
return;
|
|
3285
|
+
}
|
|
3286
|
+
if (!registration.applicableTypes.includes(type.kind) || registration.isApplicableToType?.(type) === false) {
|
|
2320
3287
|
addTypeMismatch(
|
|
2321
3288
|
ctx,
|
|
2322
3289
|
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
@@ -2345,7 +3312,9 @@ function validateConstraints(ctx, name, type, constraints) {
|
|
|
2345
3312
|
checkNumericContradictions(ctx, name, constraints);
|
|
2346
3313
|
checkLengthContradictions(ctx, name, constraints);
|
|
2347
3314
|
checkAllowedMembersContradiction(ctx, name, constraints);
|
|
3315
|
+
checkConstContradictions(ctx, name, constraints);
|
|
2348
3316
|
checkConstraintBroadening(ctx, name, constraints);
|
|
3317
|
+
checkCustomConstraintSemantics(ctx, name, constraints);
|
|
2349
3318
|
checkTypeApplicability(ctx, name, type, constraints);
|
|
2350
3319
|
}
|
|
2351
3320
|
function validateElement(ctx, element) {
|
|
@@ -2387,7 +3356,10 @@ function validateIR(ir, options) {
|
|
|
2387
3356
|
// src/extensions/registry.ts
|
|
2388
3357
|
function createExtensionRegistry(extensions) {
|
|
2389
3358
|
const typeMap = /* @__PURE__ */ new Map();
|
|
3359
|
+
const typeNameMap = /* @__PURE__ */ new Map();
|
|
2390
3360
|
const constraintMap = /* @__PURE__ */ new Map();
|
|
3361
|
+
const constraintTagMap = /* @__PURE__ */ new Map();
|
|
3362
|
+
const builtinBroadeningMap = /* @__PURE__ */ new Map();
|
|
2391
3363
|
const annotationMap = /* @__PURE__ */ new Map();
|
|
2392
3364
|
for (const ext of extensions) {
|
|
2393
3365
|
if (ext.types !== void 0) {
|
|
@@ -2397,6 +3369,27 @@ function createExtensionRegistry(extensions) {
|
|
|
2397
3369
|
throw new Error(`Duplicate custom type ID: "${qualifiedId}"`);
|
|
2398
3370
|
}
|
|
2399
3371
|
typeMap.set(qualifiedId, type);
|
|
3372
|
+
for (const sourceTypeName of type.tsTypeNames ?? [type.typeName]) {
|
|
3373
|
+
if (typeNameMap.has(sourceTypeName)) {
|
|
3374
|
+
throw new Error(`Duplicate custom type source name: "${sourceTypeName}"`);
|
|
3375
|
+
}
|
|
3376
|
+
typeNameMap.set(sourceTypeName, {
|
|
3377
|
+
extensionId: ext.extensionId,
|
|
3378
|
+
registration: type
|
|
3379
|
+
});
|
|
3380
|
+
}
|
|
3381
|
+
if (type.builtinConstraintBroadenings !== void 0) {
|
|
3382
|
+
for (const broadening of type.builtinConstraintBroadenings) {
|
|
3383
|
+
const key = `${qualifiedId}:${broadening.tagName}`;
|
|
3384
|
+
if (builtinBroadeningMap.has(key)) {
|
|
3385
|
+
throw new Error(`Duplicate built-in constraint broadening: "${key}"`);
|
|
3386
|
+
}
|
|
3387
|
+
builtinBroadeningMap.set(key, {
|
|
3388
|
+
extensionId: ext.extensionId,
|
|
3389
|
+
registration: broadening
|
|
3390
|
+
});
|
|
3391
|
+
}
|
|
3392
|
+
}
|
|
2400
3393
|
}
|
|
2401
3394
|
}
|
|
2402
3395
|
if (ext.constraints !== void 0) {
|
|
@@ -2408,6 +3401,17 @@ function createExtensionRegistry(extensions) {
|
|
|
2408
3401
|
constraintMap.set(qualifiedId, constraint);
|
|
2409
3402
|
}
|
|
2410
3403
|
}
|
|
3404
|
+
if (ext.constraintTags !== void 0) {
|
|
3405
|
+
for (const tag of ext.constraintTags) {
|
|
3406
|
+
if (constraintTagMap.has(tag.tagName)) {
|
|
3407
|
+
throw new Error(`Duplicate custom constraint tag: "@${tag.tagName}"`);
|
|
3408
|
+
}
|
|
3409
|
+
constraintTagMap.set(tag.tagName, {
|
|
3410
|
+
extensionId: ext.extensionId,
|
|
3411
|
+
registration: tag
|
|
3412
|
+
});
|
|
3413
|
+
}
|
|
3414
|
+
}
|
|
2411
3415
|
if (ext.annotations !== void 0) {
|
|
2412
3416
|
for (const annotation of ext.annotations) {
|
|
2413
3417
|
const qualifiedId = `${ext.extensionId}/${annotation.annotationName}`;
|
|
@@ -2421,7 +3425,10 @@ function createExtensionRegistry(extensions) {
|
|
|
2421
3425
|
return {
|
|
2422
3426
|
extensions,
|
|
2423
3427
|
findType: (typeId) => typeMap.get(typeId),
|
|
3428
|
+
findTypeByName: (typeName) => typeNameMap.get(typeName),
|
|
2424
3429
|
findConstraint: (constraintId) => constraintMap.get(constraintId),
|
|
3430
|
+
findConstraintTag: (tagName) => constraintTagMap.get(tagName),
|
|
3431
|
+
findBuiltinConstraintBroadening: (typeId, tagName) => builtinBroadeningMap.get(`${typeId}:${tagName}`),
|
|
2425
3432
|
findAnnotation: (annotationId) => annotationMap.get(annotationId)
|
|
2426
3433
|
};
|
|
2427
3434
|
}
|