@formspec/build 0.1.0-alpha.15 → 0.1.0-alpha.16
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/mixed-authoring-shipping-address.d.ts +30 -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__/parity/utils.d.ts +5 -3
- package/dist/__tests__/parity/utils.d.ts.map +1 -1
- package/dist/analyzer/class-analyzer.d.ts +4 -2
- package/dist/analyzer/class-analyzer.d.ts.map +1 -1
- package/dist/analyzer/tsdoc-parser.d.ts +20 -2
- package/dist/analyzer/tsdoc-parser.d.ts.map +1 -1
- package/dist/browser.cjs +172 -17
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +172 -17
- package/dist/browser.js.map +1 -1
- package/dist/build.d.ts +39 -1
- package/dist/canonicalize/tsdoc-canonicalizer.d.ts.map +1 -1
- package/dist/cli.cjs +634 -88
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +634 -88
- package/dist/cli.js.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 +622 -87
- 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 +621 -87
- package/dist/index.js.map +1 -1
- package/dist/internals.cjs +526 -91
- package/dist/internals.cjs.map +1 -1
- package/dist/internals.js +526 -91
- 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,7 +541,7 @@ 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
|
+
var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions", "defaultValue"]);
|
|
544
545
|
function createFormSpecTSDocConfig() {
|
|
545
546
|
const config = new import_tsdoc.TSDocConfiguration();
|
|
546
547
|
for (const tagName of Object.keys(import_core3.BUILTIN_CONSTRAINT_DEFINITIONS)) {
|
|
@@ -552,7 +553,7 @@ function createFormSpecTSDocConfig() {
|
|
|
552
553
|
})
|
|
553
554
|
);
|
|
554
555
|
}
|
|
555
|
-
for (const tagName of ["displayName", "description"]) {
|
|
556
|
+
for (const tagName of ["displayName", "description", "format", "placeholder"]) {
|
|
556
557
|
config.addTagDefinition(
|
|
557
558
|
new import_tsdoc.TSDocTagDefinition({
|
|
558
559
|
tagName: "@" + tagName,
|
|
@@ -571,6 +572,12 @@ function getParser() {
|
|
|
571
572
|
function parseTSDocTags(node, file = "") {
|
|
572
573
|
const constraints = [];
|
|
573
574
|
const annotations = [];
|
|
575
|
+
let displayName;
|
|
576
|
+
let description;
|
|
577
|
+
let placeholder;
|
|
578
|
+
let displayNameProvenance;
|
|
579
|
+
let descriptionProvenance;
|
|
580
|
+
let placeholderProvenance;
|
|
574
581
|
const sourceFile = node.getSourceFile();
|
|
575
582
|
const sourceText = sourceFile.getFullText();
|
|
576
583
|
const commentRanges = ts2.getLeadingCommentRanges(sourceText, node.getFullStart());
|
|
@@ -590,30 +597,37 @@ function parseTSDocTags(node, file = "") {
|
|
|
590
597
|
const docComment = parserContext.docComment;
|
|
591
598
|
for (const block of docComment.customBlocks) {
|
|
592
599
|
const tagName = (0, import_core3.normalizeConstraintTagName)(block.blockTag.tagName.substring(1));
|
|
593
|
-
if (tagName === "displayName" || tagName === "description") {
|
|
600
|
+
if (tagName === "displayName" || tagName === "description" || tagName === "format" || tagName === "placeholder") {
|
|
594
601
|
const text2 = extractBlockText(block).trim();
|
|
595
602
|
if (text2 === "") continue;
|
|
596
603
|
const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
|
|
597
604
|
if (tagName === "displayName") {
|
|
605
|
+
if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
|
|
606
|
+
displayName = text2;
|
|
607
|
+
displayNameProvenance = provenance2;
|
|
608
|
+
}
|
|
609
|
+
} else if (tagName === "format") {
|
|
598
610
|
annotations.push({
|
|
599
611
|
kind: "annotation",
|
|
600
|
-
annotationKind: "
|
|
612
|
+
annotationKind: "format",
|
|
601
613
|
value: text2,
|
|
602
614
|
provenance: provenance2
|
|
603
615
|
});
|
|
604
616
|
} else {
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
617
|
+
if (tagName === "description" && description === void 0) {
|
|
618
|
+
description = text2;
|
|
619
|
+
descriptionProvenance = provenance2;
|
|
620
|
+
} else if (tagName === "placeholder" && placeholder === void 0) {
|
|
621
|
+
placeholder = text2;
|
|
622
|
+
placeholderProvenance = provenance2;
|
|
623
|
+
}
|
|
611
624
|
}
|
|
612
625
|
continue;
|
|
613
626
|
}
|
|
614
627
|
if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
615
628
|
const text = extractBlockText(block).trim();
|
|
616
|
-
|
|
629
|
+
const expectedType = (0, import_core3.isBuiltinConstraintName)(tagName) ? import_core3.BUILTIN_CONSTRAINT_DEFINITIONS[tagName] : void 0;
|
|
630
|
+
if (text === "" && expectedType !== "boolean") continue;
|
|
617
631
|
const provenance = provenanceForComment(range, sourceFile, file, tagName);
|
|
618
632
|
const constraintNode = parseConstraintValue(tagName, text, provenance);
|
|
619
633
|
if (constraintNode) {
|
|
@@ -621,14 +635,47 @@ function parseTSDocTags(node, file = "") {
|
|
|
621
635
|
}
|
|
622
636
|
}
|
|
623
637
|
if (docComment.deprecatedBlock !== void 0) {
|
|
638
|
+
const message = extractBlockText(docComment.deprecatedBlock).trim();
|
|
624
639
|
annotations.push({
|
|
625
640
|
kind: "annotation",
|
|
626
641
|
annotationKind: "deprecated",
|
|
642
|
+
...message !== "" && { message },
|
|
627
643
|
provenance: provenanceForComment(range, sourceFile, file, "deprecated")
|
|
628
644
|
});
|
|
629
645
|
}
|
|
646
|
+
if (description === void 0 && docComment.remarksBlock !== void 0) {
|
|
647
|
+
const remarks = extractBlockText(docComment.remarksBlock).trim();
|
|
648
|
+
if (remarks !== "") {
|
|
649
|
+
description = remarks;
|
|
650
|
+
descriptionProvenance = provenanceForComment(range, sourceFile, file, "remarks");
|
|
651
|
+
}
|
|
652
|
+
}
|
|
630
653
|
}
|
|
631
654
|
}
|
|
655
|
+
if (displayName !== void 0 && displayNameProvenance !== void 0) {
|
|
656
|
+
annotations.push({
|
|
657
|
+
kind: "annotation",
|
|
658
|
+
annotationKind: "displayName",
|
|
659
|
+
value: displayName,
|
|
660
|
+
provenance: displayNameProvenance
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
if (description !== void 0 && descriptionProvenance !== void 0) {
|
|
664
|
+
annotations.push({
|
|
665
|
+
kind: "annotation",
|
|
666
|
+
annotationKind: "description",
|
|
667
|
+
value: description,
|
|
668
|
+
provenance: descriptionProvenance
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
if (placeholder !== void 0 && placeholderProvenance !== void 0) {
|
|
672
|
+
annotations.push({
|
|
673
|
+
kind: "annotation",
|
|
674
|
+
annotationKind: "placeholder",
|
|
675
|
+
value: placeholder,
|
|
676
|
+
provenance: placeholderProvenance
|
|
677
|
+
});
|
|
678
|
+
}
|
|
632
679
|
const jsDocTagsAll = ts2.getJSDocTags(node);
|
|
633
680
|
for (const tag of jsDocTagsAll) {
|
|
634
681
|
const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
|
|
@@ -637,6 +684,11 @@ function parseTSDocTags(node, file = "") {
|
|
|
637
684
|
if (commentText === void 0 || commentText.trim() === "") continue;
|
|
638
685
|
const text = commentText.trim();
|
|
639
686
|
const provenance = provenanceForJSDocTag(tag, file);
|
|
687
|
+
if (tagName === "defaultValue") {
|
|
688
|
+
const defaultValueNode = parseDefaultValueValue(text, provenance);
|
|
689
|
+
annotations.push(defaultValueNode);
|
|
690
|
+
continue;
|
|
691
|
+
}
|
|
640
692
|
const constraintNode = parseConstraintValue(tagName, text, provenance);
|
|
641
693
|
if (constraintNode) {
|
|
642
694
|
constraints.push(constraintNode);
|
|
@@ -644,6 +696,28 @@ function parseTSDocTags(node, file = "") {
|
|
|
644
696
|
}
|
|
645
697
|
return { constraints, annotations };
|
|
646
698
|
}
|
|
699
|
+
function extractDisplayNameMetadata(node) {
|
|
700
|
+
let displayName;
|
|
701
|
+
const memberDisplayNames = /* @__PURE__ */ new Map();
|
|
702
|
+
for (const tag of ts2.getJSDocTags(node)) {
|
|
703
|
+
const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
|
|
704
|
+
if (tagName !== "displayName") continue;
|
|
705
|
+
const commentText = getTagCommentText(tag);
|
|
706
|
+
if (commentText === void 0) continue;
|
|
707
|
+
const text = commentText.trim();
|
|
708
|
+
if (text === "") continue;
|
|
709
|
+
const memberTarget = parseMemberTargetDisplayName(text);
|
|
710
|
+
if (memberTarget) {
|
|
711
|
+
memberDisplayNames.set(memberTarget.target, memberTarget.label);
|
|
712
|
+
continue;
|
|
713
|
+
}
|
|
714
|
+
displayName ??= text;
|
|
715
|
+
}
|
|
716
|
+
return {
|
|
717
|
+
...displayName !== void 0 && { displayName },
|
|
718
|
+
memberDisplayNames
|
|
719
|
+
};
|
|
720
|
+
}
|
|
647
721
|
function extractPathTarget(text) {
|
|
648
722
|
const trimmed = text.trimStart();
|
|
649
723
|
const match = /^:([a-zA-Z_]\w*)\s+([\s\S]*)$/.exec(trimmed);
|
|
@@ -706,7 +780,45 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
706
780
|
}
|
|
707
781
|
return null;
|
|
708
782
|
}
|
|
783
|
+
if (expectedType === "boolean") {
|
|
784
|
+
const trimmed = effectiveText.trim();
|
|
785
|
+
if (trimmed !== "" && trimmed !== "true") {
|
|
786
|
+
return null;
|
|
787
|
+
}
|
|
788
|
+
if (tagName === "uniqueItems") {
|
|
789
|
+
return {
|
|
790
|
+
kind: "constraint",
|
|
791
|
+
constraintKind: "uniqueItems",
|
|
792
|
+
value: true,
|
|
793
|
+
...path2 && { path: path2 },
|
|
794
|
+
provenance
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
return null;
|
|
798
|
+
}
|
|
709
799
|
if (expectedType === "json") {
|
|
800
|
+
if (tagName === "const") {
|
|
801
|
+
const trimmedText = effectiveText.trim();
|
|
802
|
+
if (trimmedText === "") return null;
|
|
803
|
+
try {
|
|
804
|
+
const parsed2 = JSON.parse(trimmedText);
|
|
805
|
+
return {
|
|
806
|
+
kind: "constraint",
|
|
807
|
+
constraintKind: "const",
|
|
808
|
+
value: parsed2,
|
|
809
|
+
...path2 && { path: path2 },
|
|
810
|
+
provenance
|
|
811
|
+
};
|
|
812
|
+
} catch {
|
|
813
|
+
return {
|
|
814
|
+
kind: "constraint",
|
|
815
|
+
constraintKind: "const",
|
|
816
|
+
value: trimmedText,
|
|
817
|
+
...path2 && { path: path2 },
|
|
818
|
+
provenance
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
}
|
|
710
822
|
const parsed = tryParseJson(effectiveText);
|
|
711
823
|
if (!Array.isArray(parsed)) {
|
|
712
824
|
return null;
|
|
@@ -738,6 +850,34 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
738
850
|
provenance
|
|
739
851
|
};
|
|
740
852
|
}
|
|
853
|
+
function parseDefaultValueValue(text, provenance) {
|
|
854
|
+
const trimmed = text.trim();
|
|
855
|
+
let value;
|
|
856
|
+
if (trimmed === "null") {
|
|
857
|
+
value = null;
|
|
858
|
+
} else if (trimmed === "true") {
|
|
859
|
+
value = true;
|
|
860
|
+
} else if (trimmed === "false") {
|
|
861
|
+
value = false;
|
|
862
|
+
} else {
|
|
863
|
+
const parsed = tryParseJson(trimmed);
|
|
864
|
+
value = parsed !== null ? parsed : trimmed;
|
|
865
|
+
}
|
|
866
|
+
return {
|
|
867
|
+
kind: "annotation",
|
|
868
|
+
annotationKind: "defaultValue",
|
|
869
|
+
value,
|
|
870
|
+
provenance
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
function isMemberTargetDisplayName(text) {
|
|
874
|
+
return parseMemberTargetDisplayName(text) !== null;
|
|
875
|
+
}
|
|
876
|
+
function parseMemberTargetDisplayName(text) {
|
|
877
|
+
const match = /^:([^\s]+)\s+([\s\S]+)$/.exec(text);
|
|
878
|
+
if (!match?.[1] || !match[2]) return null;
|
|
879
|
+
return { target: match[1], label: match[2].trim() };
|
|
880
|
+
}
|
|
741
881
|
function provenanceForComment(range, sourceFile, file, tagName) {
|
|
742
882
|
const { line, character } = sourceFile.getLineAndCharacterOfPosition(range.pos);
|
|
743
883
|
return {
|
|
@@ -819,11 +959,17 @@ function isObjectType(type) {
|
|
|
819
959
|
function isTypeReference(type) {
|
|
820
960
|
return !!(type.flags & ts4.TypeFlags.Object) && !!(type.objectFlags & ts4.ObjectFlags.Reference);
|
|
821
961
|
}
|
|
962
|
+
var RESOLVING_TYPE_PLACEHOLDER = {
|
|
963
|
+
kind: "object",
|
|
964
|
+
properties: [],
|
|
965
|
+
additionalProperties: true
|
|
966
|
+
};
|
|
822
967
|
function analyzeClassToIR(classDecl, checker, file = "") {
|
|
823
968
|
const name = classDecl.name?.text ?? "AnonymousClass";
|
|
824
969
|
const fields = [];
|
|
825
970
|
const fieldLayouts = [];
|
|
826
971
|
const typeRegistry = {};
|
|
972
|
+
const annotations = extractJSDocAnnotationNodes(classDecl, file);
|
|
827
973
|
const visiting = /* @__PURE__ */ new Set();
|
|
828
974
|
const instanceMethods = [];
|
|
829
975
|
const staticMethods = [];
|
|
@@ -846,12 +992,21 @@ function analyzeClassToIR(classDecl, checker, file = "") {
|
|
|
846
992
|
}
|
|
847
993
|
}
|
|
848
994
|
}
|
|
849
|
-
return {
|
|
995
|
+
return {
|
|
996
|
+
name,
|
|
997
|
+
fields,
|
|
998
|
+
fieldLayouts,
|
|
999
|
+
typeRegistry,
|
|
1000
|
+
...annotations.length > 0 && { annotations },
|
|
1001
|
+
instanceMethods,
|
|
1002
|
+
staticMethods
|
|
1003
|
+
};
|
|
850
1004
|
}
|
|
851
1005
|
function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
|
|
852
1006
|
const name = interfaceDecl.name.text;
|
|
853
1007
|
const fields = [];
|
|
854
1008
|
const typeRegistry = {};
|
|
1009
|
+
const annotations = extractJSDocAnnotationNodes(interfaceDecl, file);
|
|
855
1010
|
const visiting = /* @__PURE__ */ new Set();
|
|
856
1011
|
for (const member of interfaceDecl.members) {
|
|
857
1012
|
if (ts4.isPropertySignature(member)) {
|
|
@@ -862,7 +1017,15 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
|
|
|
862
1017
|
}
|
|
863
1018
|
}
|
|
864
1019
|
const fieldLayouts = fields.map(() => ({}));
|
|
865
|
-
return {
|
|
1020
|
+
return {
|
|
1021
|
+
name,
|
|
1022
|
+
fields,
|
|
1023
|
+
fieldLayouts,
|
|
1024
|
+
typeRegistry,
|
|
1025
|
+
...annotations.length > 0 && { annotations },
|
|
1026
|
+
instanceMethods: [],
|
|
1027
|
+
staticMethods: []
|
|
1028
|
+
};
|
|
866
1029
|
}
|
|
867
1030
|
function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
868
1031
|
if (!ts4.isTypeLiteralNode(typeAlias.type)) {
|
|
@@ -877,6 +1040,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
|
877
1040
|
const name = typeAlias.name.text;
|
|
878
1041
|
const fields = [];
|
|
879
1042
|
const typeRegistry = {};
|
|
1043
|
+
const annotations = extractJSDocAnnotationNodes(typeAlias, file);
|
|
880
1044
|
const visiting = /* @__PURE__ */ new Set();
|
|
881
1045
|
for (const member of typeAlias.type.members) {
|
|
882
1046
|
if (ts4.isPropertySignature(member)) {
|
|
@@ -893,6 +1057,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
|
893
1057
|
fields,
|
|
894
1058
|
fieldLayouts: fields.map(() => ({})),
|
|
895
1059
|
typeRegistry,
|
|
1060
|
+
...annotations.length > 0 && { annotations },
|
|
896
1061
|
instanceMethods: [],
|
|
897
1062
|
staticMethods: []
|
|
898
1063
|
}
|
|
@@ -906,7 +1071,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
|
|
|
906
1071
|
const tsType = checker.getTypeAtLocation(prop);
|
|
907
1072
|
const optional = prop.questionToken !== void 0;
|
|
908
1073
|
const provenance = provenanceForNode(prop, file);
|
|
909
|
-
let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
|
|
1074
|
+
let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting, prop);
|
|
910
1075
|
const constraints = [];
|
|
911
1076
|
if (prop.type) {
|
|
912
1077
|
constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
|
|
@@ -915,7 +1080,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
|
|
|
915
1080
|
let annotations = [];
|
|
916
1081
|
annotations.push(...extractJSDocAnnotationNodes(prop, file));
|
|
917
1082
|
const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
|
|
918
|
-
if (defaultAnnotation) {
|
|
1083
|
+
if (defaultAnnotation && !annotations.some((a) => a.annotationKind === "defaultValue")) {
|
|
919
1084
|
annotations.push(defaultAnnotation);
|
|
920
1085
|
}
|
|
921
1086
|
({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
|
|
@@ -937,7 +1102,7 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
|
|
|
937
1102
|
const tsType = checker.getTypeAtLocation(prop);
|
|
938
1103
|
const optional = prop.questionToken !== void 0;
|
|
939
1104
|
const provenance = provenanceForNode(prop, file);
|
|
940
|
-
let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
|
|
1105
|
+
let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting, prop);
|
|
941
1106
|
const constraints = [];
|
|
942
1107
|
if (prop.type) {
|
|
943
1108
|
constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
|
|
@@ -1018,7 +1183,7 @@ function parseEnumMemberDisplayName(value) {
|
|
|
1018
1183
|
if (label === "") return null;
|
|
1019
1184
|
return { value: match[1], label };
|
|
1020
1185
|
}
|
|
1021
|
-
function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
|
|
1186
|
+
function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode) {
|
|
1022
1187
|
if (type.flags & ts4.TypeFlags.String) {
|
|
1023
1188
|
return { kind: "primitive", primitiveKind: "string" };
|
|
1024
1189
|
}
|
|
@@ -1047,7 +1212,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
|
|
|
1047
1212
|
};
|
|
1048
1213
|
}
|
|
1049
1214
|
if (type.isUnion()) {
|
|
1050
|
-
return resolveUnionType(type, checker, file, typeRegistry, visiting);
|
|
1215
|
+
return resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode);
|
|
1051
1216
|
}
|
|
1052
1217
|
if (checker.isArrayType(type)) {
|
|
1053
1218
|
return resolveArrayType(type, checker, file, typeRegistry, visiting);
|
|
@@ -1057,70 +1222,102 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
|
|
|
1057
1222
|
}
|
|
1058
1223
|
return { kind: "primitive", primitiveKind: "string" };
|
|
1059
1224
|
}
|
|
1060
|
-
function resolveUnionType(type, checker, file, typeRegistry, visiting) {
|
|
1225
|
+
function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode) {
|
|
1226
|
+
const typeName = getNamedTypeName(type);
|
|
1227
|
+
const namedDecl = getNamedTypeDeclaration(type);
|
|
1228
|
+
if (typeName && typeName in typeRegistry) {
|
|
1229
|
+
return { kind: "reference", name: typeName, typeArguments: [] };
|
|
1230
|
+
}
|
|
1061
1231
|
const allTypes = type.types;
|
|
1062
1232
|
const nonNullTypes = allTypes.filter(
|
|
1063
1233
|
(t) => !(t.flags & (ts4.TypeFlags.Null | ts4.TypeFlags.Undefined))
|
|
1064
1234
|
);
|
|
1065
1235
|
const hasNull = allTypes.some((t) => t.flags & ts4.TypeFlags.Null);
|
|
1236
|
+
const memberDisplayNames = /* @__PURE__ */ new Map();
|
|
1237
|
+
if (namedDecl) {
|
|
1238
|
+
for (const [value, label] of extractDisplayNameMetadata(namedDecl).memberDisplayNames) {
|
|
1239
|
+
memberDisplayNames.set(value, label);
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
if (sourceNode) {
|
|
1243
|
+
for (const [value, label] of extractDisplayNameMetadata(sourceNode).memberDisplayNames) {
|
|
1244
|
+
memberDisplayNames.set(value, label);
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
const registerNamed = (result) => {
|
|
1248
|
+
if (!typeName) {
|
|
1249
|
+
return result;
|
|
1250
|
+
}
|
|
1251
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
|
|
1252
|
+
typeRegistry[typeName] = {
|
|
1253
|
+
name: typeName,
|
|
1254
|
+
type: result,
|
|
1255
|
+
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
1256
|
+
provenance: provenanceForDeclaration(namedDecl ?? sourceNode, file)
|
|
1257
|
+
};
|
|
1258
|
+
return { kind: "reference", name: typeName, typeArguments: [] };
|
|
1259
|
+
};
|
|
1260
|
+
const applyMemberLabels = (members2) => members2.map((value) => {
|
|
1261
|
+
const displayName = memberDisplayNames.get(String(value));
|
|
1262
|
+
return displayName !== void 0 ? { value, displayName } : { value };
|
|
1263
|
+
});
|
|
1066
1264
|
const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts4.TypeFlags.BooleanLiteral);
|
|
1067
1265
|
if (isBooleanUnion2) {
|
|
1068
1266
|
const boolNode = { kind: "primitive", primitiveKind: "boolean" };
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
}
|
|
1075
|
-
return boolNode;
|
|
1267
|
+
const result = hasNull ? {
|
|
1268
|
+
kind: "union",
|
|
1269
|
+
members: [boolNode, { kind: "primitive", primitiveKind: "null" }]
|
|
1270
|
+
} : boolNode;
|
|
1271
|
+
return registerNamed(result);
|
|
1076
1272
|
}
|
|
1077
1273
|
const allStringLiterals = nonNullTypes.every((t) => t.isStringLiteral());
|
|
1078
1274
|
if (allStringLiterals && nonNullTypes.length > 0) {
|
|
1079
1275
|
const stringTypes = nonNullTypes.filter((t) => t.isStringLiteral());
|
|
1080
1276
|
const enumNode = {
|
|
1081
1277
|
kind: "enum",
|
|
1082
|
-
members: stringTypes.map((t) =>
|
|
1278
|
+
members: applyMemberLabels(stringTypes.map((t) => t.value))
|
|
1083
1279
|
};
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
}
|
|
1090
|
-
return enumNode;
|
|
1280
|
+
const result = hasNull ? {
|
|
1281
|
+
kind: "union",
|
|
1282
|
+
members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
|
|
1283
|
+
} : enumNode;
|
|
1284
|
+
return registerNamed(result);
|
|
1091
1285
|
}
|
|
1092
1286
|
const allNumberLiterals = nonNullTypes.every((t) => t.isNumberLiteral());
|
|
1093
1287
|
if (allNumberLiterals && nonNullTypes.length > 0) {
|
|
1094
1288
|
const numberTypes = nonNullTypes.filter((t) => t.isNumberLiteral());
|
|
1095
1289
|
const enumNode = {
|
|
1096
1290
|
kind: "enum",
|
|
1097
|
-
members: numberTypes.map((t) =>
|
|
1291
|
+
members: applyMemberLabels(numberTypes.map((t) => t.value))
|
|
1098
1292
|
};
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
}
|
|
1105
|
-
return enumNode;
|
|
1293
|
+
const result = hasNull ? {
|
|
1294
|
+
kind: "union",
|
|
1295
|
+
members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
|
|
1296
|
+
} : enumNode;
|
|
1297
|
+
return registerNamed(result);
|
|
1106
1298
|
}
|
|
1107
1299
|
if (nonNullTypes.length === 1 && nonNullTypes[0]) {
|
|
1108
|
-
const inner = resolveTypeNode(
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1300
|
+
const inner = resolveTypeNode(
|
|
1301
|
+
nonNullTypes[0],
|
|
1302
|
+
checker,
|
|
1303
|
+
file,
|
|
1304
|
+
typeRegistry,
|
|
1305
|
+
visiting,
|
|
1306
|
+
sourceNode
|
|
1307
|
+
);
|
|
1308
|
+
const result = hasNull ? {
|
|
1309
|
+
kind: "union",
|
|
1310
|
+
members: [inner, { kind: "primitive", primitiveKind: "null" }]
|
|
1311
|
+
} : inner;
|
|
1312
|
+
return registerNamed(result);
|
|
1116
1313
|
}
|
|
1117
1314
|
const members = nonNullTypes.map(
|
|
1118
|
-
(t) => resolveTypeNode(t, checker, file, typeRegistry, visiting)
|
|
1315
|
+
(t) => resolveTypeNode(t, checker, file, typeRegistry, visiting, sourceNode)
|
|
1119
1316
|
);
|
|
1120
1317
|
if (hasNull) {
|
|
1121
1318
|
members.push({ kind: "primitive", primitiveKind: "null" });
|
|
1122
1319
|
}
|
|
1123
|
-
return { kind: "union", members };
|
|
1320
|
+
return registerNamed({ kind: "union", members });
|
|
1124
1321
|
}
|
|
1125
1322
|
function resolveArrayType(type, checker, file, typeRegistry, visiting) {
|
|
1126
1323
|
const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
|
|
@@ -1136,30 +1333,84 @@ function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
|
|
|
1136
1333
|
if (!indexInfo) {
|
|
1137
1334
|
return null;
|
|
1138
1335
|
}
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1336
|
+
const valueType = resolveTypeNode(indexInfo.type, checker, file, typeRegistry, visiting);
|
|
1337
|
+
return { kind: "record", valueType };
|
|
1338
|
+
}
|
|
1339
|
+
function typeNodeContainsReference(type, targetName) {
|
|
1340
|
+
switch (type.kind) {
|
|
1341
|
+
case "reference":
|
|
1342
|
+
return type.name === targetName;
|
|
1343
|
+
case "array":
|
|
1344
|
+
return typeNodeContainsReference(type.items, targetName);
|
|
1345
|
+
case "record":
|
|
1346
|
+
return typeNodeContainsReference(type.valueType, targetName);
|
|
1347
|
+
case "union":
|
|
1348
|
+
return type.members.some((member) => typeNodeContainsReference(member, targetName));
|
|
1349
|
+
case "object":
|
|
1350
|
+
return type.properties.some(
|
|
1351
|
+
(property) => typeNodeContainsReference(property.type, targetName)
|
|
1352
|
+
);
|
|
1353
|
+
case "primitive":
|
|
1354
|
+
case "enum":
|
|
1355
|
+
case "dynamic":
|
|
1356
|
+
case "custom":
|
|
1357
|
+
return false;
|
|
1358
|
+
default: {
|
|
1359
|
+
const _exhaustive = type;
|
|
1360
|
+
return _exhaustive;
|
|
1361
|
+
}
|
|
1148
1362
|
}
|
|
1149
1363
|
}
|
|
1150
1364
|
function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
1151
|
-
const
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1365
|
+
const typeName = getNamedTypeName(type);
|
|
1366
|
+
const namedTypeName = typeName ?? void 0;
|
|
1367
|
+
const namedDecl = getNamedTypeDeclaration(type);
|
|
1368
|
+
const shouldRegisterNamedType = namedTypeName !== void 0 && !(namedTypeName === "Record" && namedDecl?.getSourceFile().fileName !== file);
|
|
1369
|
+
const clearNamedTypeRegistration = () => {
|
|
1370
|
+
if (namedTypeName === void 0 || !shouldRegisterNamedType) {
|
|
1371
|
+
return;
|
|
1372
|
+
}
|
|
1373
|
+
Reflect.deleteProperty(typeRegistry, namedTypeName);
|
|
1374
|
+
};
|
|
1155
1375
|
if (visiting.has(type)) {
|
|
1376
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
1377
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
1378
|
+
}
|
|
1156
1379
|
return { kind: "object", properties: [], additionalProperties: false };
|
|
1157
1380
|
}
|
|
1381
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[namedTypeName]) {
|
|
1382
|
+
typeRegistry[namedTypeName] = {
|
|
1383
|
+
name: namedTypeName,
|
|
1384
|
+
type: RESOLVING_TYPE_PLACEHOLDER,
|
|
1385
|
+
provenance: provenanceForDeclaration(namedDecl, file)
|
|
1386
|
+
};
|
|
1387
|
+
}
|
|
1158
1388
|
visiting.add(type);
|
|
1159
|
-
|
|
1160
|
-
|
|
1389
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType && typeRegistry[namedTypeName]?.type !== void 0) {
|
|
1390
|
+
if (typeRegistry[namedTypeName].type !== RESOLVING_TYPE_PLACEHOLDER) {
|
|
1391
|
+
visiting.delete(type);
|
|
1392
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
const recordNode = tryResolveRecordType(type, checker, file, typeRegistry, visiting);
|
|
1396
|
+
if (recordNode) {
|
|
1161
1397
|
visiting.delete(type);
|
|
1162
|
-
|
|
1398
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
1399
|
+
const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, namedTypeName);
|
|
1400
|
+
if (!isRecursiveRecord) {
|
|
1401
|
+
clearNamedTypeRegistration();
|
|
1402
|
+
return recordNode;
|
|
1403
|
+
}
|
|
1404
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
|
|
1405
|
+
typeRegistry[namedTypeName] = {
|
|
1406
|
+
name: namedTypeName,
|
|
1407
|
+
type: recordNode,
|
|
1408
|
+
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
1409
|
+
provenance: provenanceForDeclaration(namedDecl, file)
|
|
1410
|
+
};
|
|
1411
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
1412
|
+
}
|
|
1413
|
+
return recordNode;
|
|
1163
1414
|
}
|
|
1164
1415
|
const properties = [];
|
|
1165
1416
|
const fieldInfoMap = getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting);
|
|
@@ -1168,7 +1419,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
1168
1419
|
if (!declaration) continue;
|
|
1169
1420
|
const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
|
|
1170
1421
|
const optional = !!(prop.flags & ts4.SymbolFlags.Optional);
|
|
1171
|
-
const propTypeNode = resolveTypeNode(
|
|
1422
|
+
const propTypeNode = resolveTypeNode(
|
|
1423
|
+
propType,
|
|
1424
|
+
checker,
|
|
1425
|
+
file,
|
|
1426
|
+
typeRegistry,
|
|
1427
|
+
visiting,
|
|
1428
|
+
declaration
|
|
1429
|
+
);
|
|
1172
1430
|
const fieldNodeInfo = fieldInfoMap?.get(prop.name);
|
|
1173
1431
|
properties.push({
|
|
1174
1432
|
name: prop.name,
|
|
@@ -1185,13 +1443,15 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
1185
1443
|
properties,
|
|
1186
1444
|
additionalProperties: true
|
|
1187
1445
|
};
|
|
1188
|
-
if (
|
|
1189
|
-
|
|
1190
|
-
|
|
1446
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
1447
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
|
|
1448
|
+
typeRegistry[namedTypeName] = {
|
|
1449
|
+
name: namedTypeName,
|
|
1191
1450
|
type: objectNode,
|
|
1192
|
-
|
|
1451
|
+
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
1452
|
+
provenance: provenanceForDeclaration(namedDecl, file)
|
|
1193
1453
|
};
|
|
1194
|
-
return { kind: "reference", name:
|
|
1454
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
1195
1455
|
}
|
|
1196
1456
|
return objectNode;
|
|
1197
1457
|
}
|
|
@@ -1283,6 +1543,12 @@ function provenanceForNode(node, file) {
|
|
|
1283
1543
|
function provenanceForFile(file) {
|
|
1284
1544
|
return { surface: "tsdoc", file, line: 0, column: 0 };
|
|
1285
1545
|
}
|
|
1546
|
+
function provenanceForDeclaration(node, file) {
|
|
1547
|
+
if (!node) {
|
|
1548
|
+
return provenanceForFile(file);
|
|
1549
|
+
}
|
|
1550
|
+
return provenanceForNode(node, file);
|
|
1551
|
+
}
|
|
1286
1552
|
function getNamedTypeName(type) {
|
|
1287
1553
|
const symbol = type.getSymbol();
|
|
1288
1554
|
if (symbol?.declarations) {
|
|
@@ -1301,6 +1567,20 @@ function getNamedTypeName(type) {
|
|
|
1301
1567
|
}
|
|
1302
1568
|
return null;
|
|
1303
1569
|
}
|
|
1570
|
+
function getNamedTypeDeclaration(type) {
|
|
1571
|
+
const symbol = type.getSymbol();
|
|
1572
|
+
if (symbol?.declarations) {
|
|
1573
|
+
const decl = symbol.declarations[0];
|
|
1574
|
+
if (decl && (ts4.isClassDeclaration(decl) || ts4.isInterfaceDeclaration(decl) || ts4.isTypeAliasDeclaration(decl))) {
|
|
1575
|
+
return decl;
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
const aliasSymbol = type.aliasSymbol;
|
|
1579
|
+
if (aliasSymbol?.declarations) {
|
|
1580
|
+
return aliasSymbol.declarations.find(ts4.isTypeAliasDeclaration);
|
|
1581
|
+
}
|
|
1582
|
+
return void 0;
|
|
1583
|
+
}
|
|
1304
1584
|
function analyzeMethod(method, checker) {
|
|
1305
1585
|
if (!ts4.isIdentifier(method.name)) {
|
|
1306
1586
|
return null;
|
|
@@ -1360,6 +1640,9 @@ function generateJsonSchemaFromIR(ir, options) {
|
|
|
1360
1640
|
const ctx = makeContext(options);
|
|
1361
1641
|
for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
|
|
1362
1642
|
ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
|
|
1643
|
+
if (typeDef.annotations && typeDef.annotations.length > 0) {
|
|
1644
|
+
applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
|
|
1645
|
+
}
|
|
1363
1646
|
}
|
|
1364
1647
|
const properties = {};
|
|
1365
1648
|
const required = [];
|
|
@@ -1371,6 +1654,9 @@ function generateJsonSchemaFromIR(ir, options) {
|
|
|
1371
1654
|
properties,
|
|
1372
1655
|
...uniqueRequired.length > 0 && { required: uniqueRequired }
|
|
1373
1656
|
};
|
|
1657
|
+
if (ir.annotations && ir.annotations.length > 0) {
|
|
1658
|
+
applyAnnotations(result, ir.annotations, ctx);
|
|
1659
|
+
}
|
|
1374
1660
|
if (Object.keys(ctx.defs).length > 0) {
|
|
1375
1661
|
result.$defs = ctx.defs;
|
|
1376
1662
|
}
|
|
@@ -1400,22 +1686,51 @@ function collectFields(elements, properties, required, ctx) {
|
|
|
1400
1686
|
}
|
|
1401
1687
|
function generateFieldSchema(field, ctx) {
|
|
1402
1688
|
const schema = generateTypeNode(field.type, ctx);
|
|
1689
|
+
const itemStringSchema = schema.type === "array" && schema.items?.type === "string" ? schema.items : void 0;
|
|
1403
1690
|
const directConstraints = [];
|
|
1691
|
+
const itemConstraints = [];
|
|
1404
1692
|
const pathConstraints = [];
|
|
1405
1693
|
for (const c of field.constraints) {
|
|
1406
1694
|
if (c.path) {
|
|
1407
1695
|
pathConstraints.push(c);
|
|
1696
|
+
} else if (itemStringSchema !== void 0 && isStringItemConstraint(c)) {
|
|
1697
|
+
itemConstraints.push(c);
|
|
1408
1698
|
} else {
|
|
1409
1699
|
directConstraints.push(c);
|
|
1410
1700
|
}
|
|
1411
1701
|
}
|
|
1412
1702
|
applyConstraints(schema, directConstraints, ctx);
|
|
1413
|
-
|
|
1703
|
+
if (itemStringSchema !== void 0) {
|
|
1704
|
+
applyConstraints(itemStringSchema, itemConstraints, ctx);
|
|
1705
|
+
}
|
|
1706
|
+
const rootAnnotations = [];
|
|
1707
|
+
const itemAnnotations = [];
|
|
1708
|
+
for (const annotation of field.annotations) {
|
|
1709
|
+
if (itemStringSchema !== void 0 && annotation.annotationKind === "format") {
|
|
1710
|
+
itemAnnotations.push(annotation);
|
|
1711
|
+
} else {
|
|
1712
|
+
rootAnnotations.push(annotation);
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
applyAnnotations(schema, rootAnnotations, ctx);
|
|
1716
|
+
if (itemStringSchema !== void 0) {
|
|
1717
|
+
applyAnnotations(itemStringSchema, itemAnnotations, ctx);
|
|
1718
|
+
}
|
|
1414
1719
|
if (pathConstraints.length === 0) {
|
|
1415
1720
|
return schema;
|
|
1416
1721
|
}
|
|
1417
1722
|
return applyPathTargetedConstraints(schema, pathConstraints, ctx);
|
|
1418
1723
|
}
|
|
1724
|
+
function isStringItemConstraint(constraint) {
|
|
1725
|
+
switch (constraint.constraintKind) {
|
|
1726
|
+
case "minLength":
|
|
1727
|
+
case "maxLength":
|
|
1728
|
+
case "pattern":
|
|
1729
|
+
return true;
|
|
1730
|
+
default:
|
|
1731
|
+
return false;
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1419
1734
|
function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
|
|
1420
1735
|
if (schema.type === "array" && schema.items) {
|
|
1421
1736
|
schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx);
|
|
@@ -1633,6 +1948,9 @@ function applyConstraints(schema, constraints, ctx) {
|
|
|
1633
1948
|
case "uniqueItems":
|
|
1634
1949
|
schema.uniqueItems = constraint.value;
|
|
1635
1950
|
break;
|
|
1951
|
+
case "const":
|
|
1952
|
+
schema.const = constraint.value;
|
|
1953
|
+
break;
|
|
1636
1954
|
case "allowedMembers":
|
|
1637
1955
|
break;
|
|
1638
1956
|
case "custom":
|
|
@@ -1657,8 +1975,14 @@ function applyAnnotations(schema, annotations, ctx) {
|
|
|
1657
1975
|
case "defaultValue":
|
|
1658
1976
|
schema.default = annotation.value;
|
|
1659
1977
|
break;
|
|
1978
|
+
case "format":
|
|
1979
|
+
schema.format = annotation.value;
|
|
1980
|
+
break;
|
|
1660
1981
|
case "deprecated":
|
|
1661
1982
|
schema.deprecated = true;
|
|
1983
|
+
if (annotation.message !== void 0 && annotation.message !== "") {
|
|
1984
|
+
schema["x-formspec-deprecation-description"] = annotation.message;
|
|
1985
|
+
}
|
|
1662
1986
|
break;
|
|
1663
1987
|
case "placeholder":
|
|
1664
1988
|
break;
|
|
@@ -1840,25 +2164,31 @@ function createShowRule(fieldName, value) {
|
|
|
1840
2164
|
}
|
|
1841
2165
|
};
|
|
1842
2166
|
}
|
|
2167
|
+
function flattenConditionSchema(scope, schema) {
|
|
2168
|
+
if (schema.allOf === void 0) {
|
|
2169
|
+
if (scope === "#") {
|
|
2170
|
+
return [schema];
|
|
2171
|
+
}
|
|
2172
|
+
const fieldName = scope.replace("#/properties/", "");
|
|
2173
|
+
return [
|
|
2174
|
+
{
|
|
2175
|
+
properties: {
|
|
2176
|
+
[fieldName]: schema
|
|
2177
|
+
}
|
|
2178
|
+
}
|
|
2179
|
+
];
|
|
2180
|
+
}
|
|
2181
|
+
return schema.allOf.flatMap((member) => flattenConditionSchema(scope, member));
|
|
2182
|
+
}
|
|
1843
2183
|
function combineRules(parentRule, childRule) {
|
|
1844
|
-
const parentCondition = parentRule.condition;
|
|
1845
|
-
const childCondition = childRule.condition;
|
|
1846
2184
|
return {
|
|
1847
2185
|
effect: "SHOW",
|
|
1848
2186
|
condition: {
|
|
1849
2187
|
scope: "#",
|
|
1850
2188
|
schema: {
|
|
1851
2189
|
allOf: [
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
[parentCondition.scope.replace("#/properties/", "")]: parentCondition.schema
|
|
1855
|
-
}
|
|
1856
|
-
},
|
|
1857
|
-
{
|
|
1858
|
-
properties: {
|
|
1859
|
-
[childCondition.scope.replace("#/properties/", "")]: childCondition.schema
|
|
1860
|
-
}
|
|
1861
|
-
}
|
|
2190
|
+
...flattenConditionSchema(parentRule.condition.scope, parentRule.condition.schema),
|
|
2191
|
+
...flattenConditionSchema(childRule.condition.scope, childRule.condition.schema)
|
|
1862
2192
|
]
|
|
1863
2193
|
}
|
|
1864
2194
|
}
|
|
@@ -1866,10 +2196,14 @@ function combineRules(parentRule, childRule) {
|
|
|
1866
2196
|
}
|
|
1867
2197
|
function fieldNodeToControl(field, parentRule) {
|
|
1868
2198
|
const displayNameAnnotation = field.annotations.find((a) => a.annotationKind === "displayName");
|
|
2199
|
+
const placeholderAnnotation = field.annotations.find((a) => a.annotationKind === "placeholder");
|
|
1869
2200
|
const control = {
|
|
1870
2201
|
type: "Control",
|
|
1871
2202
|
scope: fieldToScope(field.name),
|
|
1872
2203
|
...displayNameAnnotation !== void 0 && { label: displayNameAnnotation.value },
|
|
2204
|
+
...placeholderAnnotation !== void 0 && {
|
|
2205
|
+
options: { placeholder: placeholderAnnotation.value }
|
|
2206
|
+
},
|
|
1873
2207
|
...parentRule !== void 0 && { rule: parentRule }
|
|
1874
2208
|
};
|
|
1875
2209
|
return control;
|
|
@@ -1955,6 +2289,15 @@ function addUnknownExtension(ctx, message, primary) {
|
|
|
1955
2289
|
relatedLocations: []
|
|
1956
2290
|
});
|
|
1957
2291
|
}
|
|
2292
|
+
function addUnknownPathTarget(ctx, message, primary) {
|
|
2293
|
+
ctx.diagnostics.push({
|
|
2294
|
+
code: "UNKNOWN_PATH_TARGET",
|
|
2295
|
+
message,
|
|
2296
|
+
severity: "error",
|
|
2297
|
+
primaryLocation: primary,
|
|
2298
|
+
relatedLocations: []
|
|
2299
|
+
});
|
|
2300
|
+
}
|
|
1958
2301
|
function addConstraintBroadening(ctx, message, primary, related) {
|
|
1959
2302
|
ctx.diagnostics.push({
|
|
1960
2303
|
code: "CONSTRAINT_BROADENING",
|
|
@@ -1975,6 +2318,45 @@ function findAllowedMembers(constraints) {
|
|
|
1975
2318
|
(c) => c.constraintKind === "allowedMembers"
|
|
1976
2319
|
);
|
|
1977
2320
|
}
|
|
2321
|
+
function findConstConstraints(constraints) {
|
|
2322
|
+
return constraints.filter(
|
|
2323
|
+
(c) => c.constraintKind === "const"
|
|
2324
|
+
);
|
|
2325
|
+
}
|
|
2326
|
+
function jsonValueEquals(left, right) {
|
|
2327
|
+
if (left === right) {
|
|
2328
|
+
return true;
|
|
2329
|
+
}
|
|
2330
|
+
if (Array.isArray(left) || Array.isArray(right)) {
|
|
2331
|
+
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
|
|
2332
|
+
return false;
|
|
2333
|
+
}
|
|
2334
|
+
return left.every((item, index) => jsonValueEquals(item, right[index]));
|
|
2335
|
+
}
|
|
2336
|
+
if (isJsonObject(left) || isJsonObject(right)) {
|
|
2337
|
+
if (!isJsonObject(left) || !isJsonObject(right)) {
|
|
2338
|
+
return false;
|
|
2339
|
+
}
|
|
2340
|
+
const leftKeys = Object.keys(left).sort();
|
|
2341
|
+
const rightKeys = Object.keys(right).sort();
|
|
2342
|
+
if (leftKeys.length !== rightKeys.length) {
|
|
2343
|
+
return false;
|
|
2344
|
+
}
|
|
2345
|
+
return leftKeys.every((key, index) => {
|
|
2346
|
+
const rightKey = rightKeys[index];
|
|
2347
|
+
if (rightKey !== key) {
|
|
2348
|
+
return false;
|
|
2349
|
+
}
|
|
2350
|
+
const leftValue = left[key];
|
|
2351
|
+
const rightValue = right[rightKey];
|
|
2352
|
+
return leftValue !== void 0 && rightValue !== void 0 && jsonValueEquals(leftValue, rightValue);
|
|
2353
|
+
});
|
|
2354
|
+
}
|
|
2355
|
+
return false;
|
|
2356
|
+
}
|
|
2357
|
+
function isJsonObject(value) {
|
|
2358
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2359
|
+
}
|
|
1978
2360
|
function isOrderedBoundConstraint(constraint) {
|
|
1979
2361
|
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
2362
|
}
|
|
@@ -2181,6 +2563,25 @@ function checkAllowedMembersContradiction(ctx, fieldName, constraints) {
|
|
|
2181
2563
|
}
|
|
2182
2564
|
}
|
|
2183
2565
|
}
|
|
2566
|
+
function checkConstContradictions(ctx, fieldName, constraints) {
|
|
2567
|
+
const constConstraints = findConstConstraints(constraints);
|
|
2568
|
+
if (constConstraints.length < 2) return;
|
|
2569
|
+
const first = constConstraints[0];
|
|
2570
|
+
if (first === void 0) return;
|
|
2571
|
+
for (let i = 1; i < constConstraints.length; i++) {
|
|
2572
|
+
const current = constConstraints[i];
|
|
2573
|
+
if (current === void 0) continue;
|
|
2574
|
+
if (jsonValueEquals(first.value, current.value)) {
|
|
2575
|
+
continue;
|
|
2576
|
+
}
|
|
2577
|
+
addContradiction(
|
|
2578
|
+
ctx,
|
|
2579
|
+
`Field "${fieldName}": conflicting @const constraints require both ${JSON.stringify(first.value)} and ${JSON.stringify(current.value)}`,
|
|
2580
|
+
first.provenance,
|
|
2581
|
+
current.provenance
|
|
2582
|
+
);
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2184
2585
|
function typeLabel(type) {
|
|
2185
2586
|
switch (type.kind) {
|
|
2186
2587
|
case "primitive":
|
|
@@ -2253,6 +2654,8 @@ function checkConstraintOnType(ctx, fieldName, type, constraint) {
|
|
|
2253
2654
|
const isString = effectiveType.kind === "primitive" && effectiveType.primitiveKind === "string";
|
|
2254
2655
|
const isArray = effectiveType.kind === "array";
|
|
2255
2656
|
const isEnum = effectiveType.kind === "enum";
|
|
2657
|
+
const arrayItemType = effectiveType.kind === "array" ? dereferenceType(ctx, effectiveType.items) : void 0;
|
|
2658
|
+
const isStringArray = arrayItemType?.kind === "primitive" && arrayItemType.primitiveKind === "string";
|
|
2256
2659
|
const label = typeLabel(effectiveType);
|
|
2257
2660
|
const ck = constraint.constraintKind;
|
|
2258
2661
|
switch (ck) {
|
|
@@ -2273,10 +2676,10 @@ function checkConstraintOnType(ctx, fieldName, type, constraint) {
|
|
|
2273
2676
|
case "minLength":
|
|
2274
2677
|
case "maxLength":
|
|
2275
2678
|
case "pattern": {
|
|
2276
|
-
if (!isString) {
|
|
2679
|
+
if (!isString && !isStringArray) {
|
|
2277
2680
|
addTypeMismatch(
|
|
2278
2681
|
ctx,
|
|
2279
|
-
`Field "${fieldName}": constraint "${ck}" is only valid on string fields, but field type is "${label}"`,
|
|
2682
|
+
`Field "${fieldName}": constraint "${ck}" is only valid on string fields or string array items, but field type is "${label}"`,
|
|
2280
2683
|
constraint.provenance
|
|
2281
2684
|
);
|
|
2282
2685
|
}
|
|
@@ -2304,6 +2707,37 @@ function checkConstraintOnType(ctx, fieldName, type, constraint) {
|
|
|
2304
2707
|
}
|
|
2305
2708
|
break;
|
|
2306
2709
|
}
|
|
2710
|
+
case "const": {
|
|
2711
|
+
const isPrimitiveConstType = effectiveType.kind === "primitive" && ["string", "number", "boolean", "null"].includes(effectiveType.primitiveKind) || effectiveType.kind === "enum";
|
|
2712
|
+
if (!isPrimitiveConstType) {
|
|
2713
|
+
addTypeMismatch(
|
|
2714
|
+
ctx,
|
|
2715
|
+
`Field "${fieldName}": constraint "const" is only valid on primitive or enum fields, but field type is "${label}"`,
|
|
2716
|
+
constraint.provenance
|
|
2717
|
+
);
|
|
2718
|
+
break;
|
|
2719
|
+
}
|
|
2720
|
+
if (effectiveType.kind === "primitive") {
|
|
2721
|
+
const valueType = constraint.value === null ? "null" : Array.isArray(constraint.value) ? "array" : typeof constraint.value;
|
|
2722
|
+
if (valueType !== effectiveType.primitiveKind) {
|
|
2723
|
+
addTypeMismatch(
|
|
2724
|
+
ctx,
|
|
2725
|
+
`Field "${fieldName}": @const value type "${valueType}" is incompatible with field type "${effectiveType.primitiveKind}"`,
|
|
2726
|
+
constraint.provenance
|
|
2727
|
+
);
|
|
2728
|
+
}
|
|
2729
|
+
break;
|
|
2730
|
+
}
|
|
2731
|
+
const memberValues = effectiveType.members.map((member) => member.value);
|
|
2732
|
+
if (!memberValues.some((member) => jsonValueEquals(member, constraint.value))) {
|
|
2733
|
+
addTypeMismatch(
|
|
2734
|
+
ctx,
|
|
2735
|
+
`Field "${fieldName}": @const value ${JSON.stringify(constraint.value)} is not one of the enum members`,
|
|
2736
|
+
constraint.provenance
|
|
2737
|
+
);
|
|
2738
|
+
}
|
|
2739
|
+
break;
|
|
2740
|
+
}
|
|
2307
2741
|
case "custom": {
|
|
2308
2742
|
checkCustomConstraint(ctx, fieldName, effectiveType, constraint);
|
|
2309
2743
|
break;
|
|
@@ -2322,9 +2756,9 @@ function checkTypeApplicability(ctx, fieldName, type, constraints) {
|
|
|
2322
2756
|
const resolution = resolvePathTargetType(ctx, type, constraint.path.segments);
|
|
2323
2757
|
const targetFieldName = formatPathTargetFieldName(fieldName, constraint.path.segments);
|
|
2324
2758
|
if (resolution.kind === "missing-property") {
|
|
2325
|
-
|
|
2759
|
+
addUnknownPathTarget(
|
|
2326
2760
|
ctx,
|
|
2327
|
-
`Field "${
|
|
2761
|
+
`Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" references unknown path segment "${resolution.segment}"`,
|
|
2328
2762
|
constraint.provenance
|
|
2329
2763
|
);
|
|
2330
2764
|
continue;
|
|
@@ -2384,6 +2818,7 @@ function validateConstraints(ctx, name, type, constraints) {
|
|
|
2384
2818
|
checkNumericContradictions(ctx, name, constraints);
|
|
2385
2819
|
checkLengthContradictions(ctx, name, constraints);
|
|
2386
2820
|
checkAllowedMembersContradiction(ctx, name, constraints);
|
|
2821
|
+
checkConstContradictions(ctx, name, constraints);
|
|
2387
2822
|
checkConstraintBroadening(ctx, name, constraints);
|
|
2388
2823
|
checkTypeApplicability(ctx, name, type, constraints);
|
|
2389
2824
|
}
|