@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.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,7 +502,7 @@ 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
|
+
var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions", "defaultValue"]);
|
|
505
506
|
function createFormSpecTSDocConfig() {
|
|
506
507
|
const config = new TSDocConfiguration();
|
|
507
508
|
for (const tagName of Object.keys(BUILTIN_CONSTRAINT_DEFINITIONS)) {
|
|
@@ -513,7 +514,7 @@ function createFormSpecTSDocConfig() {
|
|
|
513
514
|
})
|
|
514
515
|
);
|
|
515
516
|
}
|
|
516
|
-
for (const tagName of ["displayName", "description"]) {
|
|
517
|
+
for (const tagName of ["displayName", "description", "format", "placeholder"]) {
|
|
517
518
|
config.addTagDefinition(
|
|
518
519
|
new TSDocTagDefinition({
|
|
519
520
|
tagName: "@" + tagName,
|
|
@@ -532,6 +533,12 @@ function getParser() {
|
|
|
532
533
|
function parseTSDocTags(node, file = "") {
|
|
533
534
|
const constraints = [];
|
|
534
535
|
const annotations = [];
|
|
536
|
+
let displayName;
|
|
537
|
+
let description;
|
|
538
|
+
let placeholder;
|
|
539
|
+
let displayNameProvenance;
|
|
540
|
+
let descriptionProvenance;
|
|
541
|
+
let placeholderProvenance;
|
|
535
542
|
const sourceFile = node.getSourceFile();
|
|
536
543
|
const sourceText = sourceFile.getFullText();
|
|
537
544
|
const commentRanges = ts2.getLeadingCommentRanges(sourceText, node.getFullStart());
|
|
@@ -551,30 +558,37 @@ function parseTSDocTags(node, file = "") {
|
|
|
551
558
|
const docComment = parserContext.docComment;
|
|
552
559
|
for (const block of docComment.customBlocks) {
|
|
553
560
|
const tagName = normalizeConstraintTagName(block.blockTag.tagName.substring(1));
|
|
554
|
-
if (tagName === "displayName" || tagName === "description") {
|
|
561
|
+
if (tagName === "displayName" || tagName === "description" || tagName === "format" || tagName === "placeholder") {
|
|
555
562
|
const text2 = extractBlockText(block).trim();
|
|
556
563
|
if (text2 === "") continue;
|
|
557
564
|
const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
|
|
558
565
|
if (tagName === "displayName") {
|
|
566
|
+
if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
|
|
567
|
+
displayName = text2;
|
|
568
|
+
displayNameProvenance = provenance2;
|
|
569
|
+
}
|
|
570
|
+
} else if (tagName === "format") {
|
|
559
571
|
annotations.push({
|
|
560
572
|
kind: "annotation",
|
|
561
|
-
annotationKind: "
|
|
573
|
+
annotationKind: "format",
|
|
562
574
|
value: text2,
|
|
563
575
|
provenance: provenance2
|
|
564
576
|
});
|
|
565
577
|
} else {
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
578
|
+
if (tagName === "description" && description === void 0) {
|
|
579
|
+
description = text2;
|
|
580
|
+
descriptionProvenance = provenance2;
|
|
581
|
+
} else if (tagName === "placeholder" && placeholder === void 0) {
|
|
582
|
+
placeholder = text2;
|
|
583
|
+
placeholderProvenance = provenance2;
|
|
584
|
+
}
|
|
572
585
|
}
|
|
573
586
|
continue;
|
|
574
587
|
}
|
|
575
588
|
if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
576
589
|
const text = extractBlockText(block).trim();
|
|
577
|
-
|
|
590
|
+
const expectedType = isBuiltinConstraintName(tagName) ? BUILTIN_CONSTRAINT_DEFINITIONS[tagName] : void 0;
|
|
591
|
+
if (text === "" && expectedType !== "boolean") continue;
|
|
578
592
|
const provenance = provenanceForComment(range, sourceFile, file, tagName);
|
|
579
593
|
const constraintNode = parseConstraintValue(tagName, text, provenance);
|
|
580
594
|
if (constraintNode) {
|
|
@@ -582,14 +596,47 @@ function parseTSDocTags(node, file = "") {
|
|
|
582
596
|
}
|
|
583
597
|
}
|
|
584
598
|
if (docComment.deprecatedBlock !== void 0) {
|
|
599
|
+
const message = extractBlockText(docComment.deprecatedBlock).trim();
|
|
585
600
|
annotations.push({
|
|
586
601
|
kind: "annotation",
|
|
587
602
|
annotationKind: "deprecated",
|
|
603
|
+
...message !== "" && { message },
|
|
588
604
|
provenance: provenanceForComment(range, sourceFile, file, "deprecated")
|
|
589
605
|
});
|
|
590
606
|
}
|
|
607
|
+
if (description === void 0 && docComment.remarksBlock !== void 0) {
|
|
608
|
+
const remarks = extractBlockText(docComment.remarksBlock).trim();
|
|
609
|
+
if (remarks !== "") {
|
|
610
|
+
description = remarks;
|
|
611
|
+
descriptionProvenance = provenanceForComment(range, sourceFile, file, "remarks");
|
|
612
|
+
}
|
|
613
|
+
}
|
|
591
614
|
}
|
|
592
615
|
}
|
|
616
|
+
if (displayName !== void 0 && displayNameProvenance !== void 0) {
|
|
617
|
+
annotations.push({
|
|
618
|
+
kind: "annotation",
|
|
619
|
+
annotationKind: "displayName",
|
|
620
|
+
value: displayName,
|
|
621
|
+
provenance: displayNameProvenance
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
if (description !== void 0 && descriptionProvenance !== void 0) {
|
|
625
|
+
annotations.push({
|
|
626
|
+
kind: "annotation",
|
|
627
|
+
annotationKind: "description",
|
|
628
|
+
value: description,
|
|
629
|
+
provenance: descriptionProvenance
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
if (placeholder !== void 0 && placeholderProvenance !== void 0) {
|
|
633
|
+
annotations.push({
|
|
634
|
+
kind: "annotation",
|
|
635
|
+
annotationKind: "placeholder",
|
|
636
|
+
value: placeholder,
|
|
637
|
+
provenance: placeholderProvenance
|
|
638
|
+
});
|
|
639
|
+
}
|
|
593
640
|
const jsDocTagsAll = ts2.getJSDocTags(node);
|
|
594
641
|
for (const tag of jsDocTagsAll) {
|
|
595
642
|
const tagName = normalizeConstraintTagName(tag.tagName.text);
|
|
@@ -598,6 +645,11 @@ function parseTSDocTags(node, file = "") {
|
|
|
598
645
|
if (commentText === void 0 || commentText.trim() === "") continue;
|
|
599
646
|
const text = commentText.trim();
|
|
600
647
|
const provenance = provenanceForJSDocTag(tag, file);
|
|
648
|
+
if (tagName === "defaultValue") {
|
|
649
|
+
const defaultValueNode = parseDefaultValueValue(text, provenance);
|
|
650
|
+
annotations.push(defaultValueNode);
|
|
651
|
+
continue;
|
|
652
|
+
}
|
|
601
653
|
const constraintNode = parseConstraintValue(tagName, text, provenance);
|
|
602
654
|
if (constraintNode) {
|
|
603
655
|
constraints.push(constraintNode);
|
|
@@ -605,6 +657,28 @@ function parseTSDocTags(node, file = "") {
|
|
|
605
657
|
}
|
|
606
658
|
return { constraints, annotations };
|
|
607
659
|
}
|
|
660
|
+
function extractDisplayNameMetadata(node) {
|
|
661
|
+
let displayName;
|
|
662
|
+
const memberDisplayNames = /* @__PURE__ */ new Map();
|
|
663
|
+
for (const tag of ts2.getJSDocTags(node)) {
|
|
664
|
+
const tagName = normalizeConstraintTagName(tag.tagName.text);
|
|
665
|
+
if (tagName !== "displayName") continue;
|
|
666
|
+
const commentText = getTagCommentText(tag);
|
|
667
|
+
if (commentText === void 0) continue;
|
|
668
|
+
const text = commentText.trim();
|
|
669
|
+
if (text === "") continue;
|
|
670
|
+
const memberTarget = parseMemberTargetDisplayName(text);
|
|
671
|
+
if (memberTarget) {
|
|
672
|
+
memberDisplayNames.set(memberTarget.target, memberTarget.label);
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
displayName ??= text;
|
|
676
|
+
}
|
|
677
|
+
return {
|
|
678
|
+
...displayName !== void 0 && { displayName },
|
|
679
|
+
memberDisplayNames
|
|
680
|
+
};
|
|
681
|
+
}
|
|
608
682
|
function extractPathTarget(text) {
|
|
609
683
|
const trimmed = text.trimStart();
|
|
610
684
|
const match = /^:([a-zA-Z_]\w*)\s+([\s\S]*)$/.exec(trimmed);
|
|
@@ -667,7 +741,45 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
667
741
|
}
|
|
668
742
|
return null;
|
|
669
743
|
}
|
|
744
|
+
if (expectedType === "boolean") {
|
|
745
|
+
const trimmed = effectiveText.trim();
|
|
746
|
+
if (trimmed !== "" && trimmed !== "true") {
|
|
747
|
+
return null;
|
|
748
|
+
}
|
|
749
|
+
if (tagName === "uniqueItems") {
|
|
750
|
+
return {
|
|
751
|
+
kind: "constraint",
|
|
752
|
+
constraintKind: "uniqueItems",
|
|
753
|
+
value: true,
|
|
754
|
+
...path2 && { path: path2 },
|
|
755
|
+
provenance
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
return null;
|
|
759
|
+
}
|
|
670
760
|
if (expectedType === "json") {
|
|
761
|
+
if (tagName === "const") {
|
|
762
|
+
const trimmedText = effectiveText.trim();
|
|
763
|
+
if (trimmedText === "") return null;
|
|
764
|
+
try {
|
|
765
|
+
const parsed2 = JSON.parse(trimmedText);
|
|
766
|
+
return {
|
|
767
|
+
kind: "constraint",
|
|
768
|
+
constraintKind: "const",
|
|
769
|
+
value: parsed2,
|
|
770
|
+
...path2 && { path: path2 },
|
|
771
|
+
provenance
|
|
772
|
+
};
|
|
773
|
+
} catch {
|
|
774
|
+
return {
|
|
775
|
+
kind: "constraint",
|
|
776
|
+
constraintKind: "const",
|
|
777
|
+
value: trimmedText,
|
|
778
|
+
...path2 && { path: path2 },
|
|
779
|
+
provenance
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
}
|
|
671
783
|
const parsed = tryParseJson(effectiveText);
|
|
672
784
|
if (!Array.isArray(parsed)) {
|
|
673
785
|
return null;
|
|
@@ -699,6 +811,34 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
699
811
|
provenance
|
|
700
812
|
};
|
|
701
813
|
}
|
|
814
|
+
function parseDefaultValueValue(text, provenance) {
|
|
815
|
+
const trimmed = text.trim();
|
|
816
|
+
let value;
|
|
817
|
+
if (trimmed === "null") {
|
|
818
|
+
value = null;
|
|
819
|
+
} else if (trimmed === "true") {
|
|
820
|
+
value = true;
|
|
821
|
+
} else if (trimmed === "false") {
|
|
822
|
+
value = false;
|
|
823
|
+
} else {
|
|
824
|
+
const parsed = tryParseJson(trimmed);
|
|
825
|
+
value = parsed !== null ? parsed : trimmed;
|
|
826
|
+
}
|
|
827
|
+
return {
|
|
828
|
+
kind: "annotation",
|
|
829
|
+
annotationKind: "defaultValue",
|
|
830
|
+
value,
|
|
831
|
+
provenance
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
function isMemberTargetDisplayName(text) {
|
|
835
|
+
return parseMemberTargetDisplayName(text) !== null;
|
|
836
|
+
}
|
|
837
|
+
function parseMemberTargetDisplayName(text) {
|
|
838
|
+
const match = /^:([^\s]+)\s+([\s\S]+)$/.exec(text);
|
|
839
|
+
if (!match?.[1] || !match[2]) return null;
|
|
840
|
+
return { target: match[1], label: match[2].trim() };
|
|
841
|
+
}
|
|
702
842
|
function provenanceForComment(range, sourceFile, file, tagName) {
|
|
703
843
|
const { line, character } = sourceFile.getLineAndCharacterOfPosition(range.pos);
|
|
704
844
|
return {
|
|
@@ -780,11 +920,17 @@ function isObjectType(type) {
|
|
|
780
920
|
function isTypeReference(type) {
|
|
781
921
|
return !!(type.flags & ts4.TypeFlags.Object) && !!(type.objectFlags & ts4.ObjectFlags.Reference);
|
|
782
922
|
}
|
|
923
|
+
var RESOLVING_TYPE_PLACEHOLDER = {
|
|
924
|
+
kind: "object",
|
|
925
|
+
properties: [],
|
|
926
|
+
additionalProperties: true
|
|
927
|
+
};
|
|
783
928
|
function analyzeClassToIR(classDecl, checker, file = "") {
|
|
784
929
|
const name = classDecl.name?.text ?? "AnonymousClass";
|
|
785
930
|
const fields = [];
|
|
786
931
|
const fieldLayouts = [];
|
|
787
932
|
const typeRegistry = {};
|
|
933
|
+
const annotations = extractJSDocAnnotationNodes(classDecl, file);
|
|
788
934
|
const visiting = /* @__PURE__ */ new Set();
|
|
789
935
|
const instanceMethods = [];
|
|
790
936
|
const staticMethods = [];
|
|
@@ -807,12 +953,21 @@ function analyzeClassToIR(classDecl, checker, file = "") {
|
|
|
807
953
|
}
|
|
808
954
|
}
|
|
809
955
|
}
|
|
810
|
-
return {
|
|
956
|
+
return {
|
|
957
|
+
name,
|
|
958
|
+
fields,
|
|
959
|
+
fieldLayouts,
|
|
960
|
+
typeRegistry,
|
|
961
|
+
...annotations.length > 0 && { annotations },
|
|
962
|
+
instanceMethods,
|
|
963
|
+
staticMethods
|
|
964
|
+
};
|
|
811
965
|
}
|
|
812
966
|
function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
|
|
813
967
|
const name = interfaceDecl.name.text;
|
|
814
968
|
const fields = [];
|
|
815
969
|
const typeRegistry = {};
|
|
970
|
+
const annotations = extractJSDocAnnotationNodes(interfaceDecl, file);
|
|
816
971
|
const visiting = /* @__PURE__ */ new Set();
|
|
817
972
|
for (const member of interfaceDecl.members) {
|
|
818
973
|
if (ts4.isPropertySignature(member)) {
|
|
@@ -823,7 +978,15 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
|
|
|
823
978
|
}
|
|
824
979
|
}
|
|
825
980
|
const fieldLayouts = fields.map(() => ({}));
|
|
826
|
-
return {
|
|
981
|
+
return {
|
|
982
|
+
name,
|
|
983
|
+
fields,
|
|
984
|
+
fieldLayouts,
|
|
985
|
+
typeRegistry,
|
|
986
|
+
...annotations.length > 0 && { annotations },
|
|
987
|
+
instanceMethods: [],
|
|
988
|
+
staticMethods: []
|
|
989
|
+
};
|
|
827
990
|
}
|
|
828
991
|
function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
829
992
|
if (!ts4.isTypeLiteralNode(typeAlias.type)) {
|
|
@@ -838,6 +1001,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
|
838
1001
|
const name = typeAlias.name.text;
|
|
839
1002
|
const fields = [];
|
|
840
1003
|
const typeRegistry = {};
|
|
1004
|
+
const annotations = extractJSDocAnnotationNodes(typeAlias, file);
|
|
841
1005
|
const visiting = /* @__PURE__ */ new Set();
|
|
842
1006
|
for (const member of typeAlias.type.members) {
|
|
843
1007
|
if (ts4.isPropertySignature(member)) {
|
|
@@ -854,6 +1018,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
|
854
1018
|
fields,
|
|
855
1019
|
fieldLayouts: fields.map(() => ({})),
|
|
856
1020
|
typeRegistry,
|
|
1021
|
+
...annotations.length > 0 && { annotations },
|
|
857
1022
|
instanceMethods: [],
|
|
858
1023
|
staticMethods: []
|
|
859
1024
|
}
|
|
@@ -867,7 +1032,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
|
|
|
867
1032
|
const tsType = checker.getTypeAtLocation(prop);
|
|
868
1033
|
const optional = prop.questionToken !== void 0;
|
|
869
1034
|
const provenance = provenanceForNode(prop, file);
|
|
870
|
-
let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
|
|
1035
|
+
let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting, prop);
|
|
871
1036
|
const constraints = [];
|
|
872
1037
|
if (prop.type) {
|
|
873
1038
|
constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
|
|
@@ -876,7 +1041,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
|
|
|
876
1041
|
let annotations = [];
|
|
877
1042
|
annotations.push(...extractJSDocAnnotationNodes(prop, file));
|
|
878
1043
|
const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
|
|
879
|
-
if (defaultAnnotation) {
|
|
1044
|
+
if (defaultAnnotation && !annotations.some((a) => a.annotationKind === "defaultValue")) {
|
|
880
1045
|
annotations.push(defaultAnnotation);
|
|
881
1046
|
}
|
|
882
1047
|
({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
|
|
@@ -898,7 +1063,7 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
|
|
|
898
1063
|
const tsType = checker.getTypeAtLocation(prop);
|
|
899
1064
|
const optional = prop.questionToken !== void 0;
|
|
900
1065
|
const provenance = provenanceForNode(prop, file);
|
|
901
|
-
let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
|
|
1066
|
+
let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting, prop);
|
|
902
1067
|
const constraints = [];
|
|
903
1068
|
if (prop.type) {
|
|
904
1069
|
constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
|
|
@@ -979,7 +1144,7 @@ function parseEnumMemberDisplayName(value) {
|
|
|
979
1144
|
if (label === "") return null;
|
|
980
1145
|
return { value: match[1], label };
|
|
981
1146
|
}
|
|
982
|
-
function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
|
|
1147
|
+
function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode) {
|
|
983
1148
|
if (type.flags & ts4.TypeFlags.String) {
|
|
984
1149
|
return { kind: "primitive", primitiveKind: "string" };
|
|
985
1150
|
}
|
|
@@ -1008,7 +1173,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
|
|
|
1008
1173
|
};
|
|
1009
1174
|
}
|
|
1010
1175
|
if (type.isUnion()) {
|
|
1011
|
-
return resolveUnionType(type, checker, file, typeRegistry, visiting);
|
|
1176
|
+
return resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode);
|
|
1012
1177
|
}
|
|
1013
1178
|
if (checker.isArrayType(type)) {
|
|
1014
1179
|
return resolveArrayType(type, checker, file, typeRegistry, visiting);
|
|
@@ -1018,70 +1183,102 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
|
|
|
1018
1183
|
}
|
|
1019
1184
|
return { kind: "primitive", primitiveKind: "string" };
|
|
1020
1185
|
}
|
|
1021
|
-
function resolveUnionType(type, checker, file, typeRegistry, visiting) {
|
|
1186
|
+
function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode) {
|
|
1187
|
+
const typeName = getNamedTypeName(type);
|
|
1188
|
+
const namedDecl = getNamedTypeDeclaration(type);
|
|
1189
|
+
if (typeName && typeName in typeRegistry) {
|
|
1190
|
+
return { kind: "reference", name: typeName, typeArguments: [] };
|
|
1191
|
+
}
|
|
1022
1192
|
const allTypes = type.types;
|
|
1023
1193
|
const nonNullTypes = allTypes.filter(
|
|
1024
1194
|
(t) => !(t.flags & (ts4.TypeFlags.Null | ts4.TypeFlags.Undefined))
|
|
1025
1195
|
);
|
|
1026
1196
|
const hasNull = allTypes.some((t) => t.flags & ts4.TypeFlags.Null);
|
|
1197
|
+
const memberDisplayNames = /* @__PURE__ */ new Map();
|
|
1198
|
+
if (namedDecl) {
|
|
1199
|
+
for (const [value, label] of extractDisplayNameMetadata(namedDecl).memberDisplayNames) {
|
|
1200
|
+
memberDisplayNames.set(value, label);
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
if (sourceNode) {
|
|
1204
|
+
for (const [value, label] of extractDisplayNameMetadata(sourceNode).memberDisplayNames) {
|
|
1205
|
+
memberDisplayNames.set(value, label);
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
const registerNamed = (result) => {
|
|
1209
|
+
if (!typeName) {
|
|
1210
|
+
return result;
|
|
1211
|
+
}
|
|
1212
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
|
|
1213
|
+
typeRegistry[typeName] = {
|
|
1214
|
+
name: typeName,
|
|
1215
|
+
type: result,
|
|
1216
|
+
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
1217
|
+
provenance: provenanceForDeclaration(namedDecl ?? sourceNode, file)
|
|
1218
|
+
};
|
|
1219
|
+
return { kind: "reference", name: typeName, typeArguments: [] };
|
|
1220
|
+
};
|
|
1221
|
+
const applyMemberLabels = (members2) => members2.map((value) => {
|
|
1222
|
+
const displayName = memberDisplayNames.get(String(value));
|
|
1223
|
+
return displayName !== void 0 ? { value, displayName } : { value };
|
|
1224
|
+
});
|
|
1027
1225
|
const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts4.TypeFlags.BooleanLiteral);
|
|
1028
1226
|
if (isBooleanUnion2) {
|
|
1029
1227
|
const boolNode = { kind: "primitive", primitiveKind: "boolean" };
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
}
|
|
1036
|
-
return boolNode;
|
|
1228
|
+
const result = hasNull ? {
|
|
1229
|
+
kind: "union",
|
|
1230
|
+
members: [boolNode, { kind: "primitive", primitiveKind: "null" }]
|
|
1231
|
+
} : boolNode;
|
|
1232
|
+
return registerNamed(result);
|
|
1037
1233
|
}
|
|
1038
1234
|
const allStringLiterals = nonNullTypes.every((t) => t.isStringLiteral());
|
|
1039
1235
|
if (allStringLiterals && nonNullTypes.length > 0) {
|
|
1040
1236
|
const stringTypes = nonNullTypes.filter((t) => t.isStringLiteral());
|
|
1041
1237
|
const enumNode = {
|
|
1042
1238
|
kind: "enum",
|
|
1043
|
-
members: stringTypes.map((t) =>
|
|
1239
|
+
members: applyMemberLabels(stringTypes.map((t) => t.value))
|
|
1044
1240
|
};
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
}
|
|
1051
|
-
return enumNode;
|
|
1241
|
+
const result = hasNull ? {
|
|
1242
|
+
kind: "union",
|
|
1243
|
+
members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
|
|
1244
|
+
} : enumNode;
|
|
1245
|
+
return registerNamed(result);
|
|
1052
1246
|
}
|
|
1053
1247
|
const allNumberLiterals = nonNullTypes.every((t) => t.isNumberLiteral());
|
|
1054
1248
|
if (allNumberLiterals && nonNullTypes.length > 0) {
|
|
1055
1249
|
const numberTypes = nonNullTypes.filter((t) => t.isNumberLiteral());
|
|
1056
1250
|
const enumNode = {
|
|
1057
1251
|
kind: "enum",
|
|
1058
|
-
members: numberTypes.map((t) =>
|
|
1252
|
+
members: applyMemberLabels(numberTypes.map((t) => t.value))
|
|
1059
1253
|
};
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
}
|
|
1066
|
-
return enumNode;
|
|
1254
|
+
const result = hasNull ? {
|
|
1255
|
+
kind: "union",
|
|
1256
|
+
members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
|
|
1257
|
+
} : enumNode;
|
|
1258
|
+
return registerNamed(result);
|
|
1067
1259
|
}
|
|
1068
1260
|
if (nonNullTypes.length === 1 && nonNullTypes[0]) {
|
|
1069
|
-
const inner = resolveTypeNode(
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1261
|
+
const inner = resolveTypeNode(
|
|
1262
|
+
nonNullTypes[0],
|
|
1263
|
+
checker,
|
|
1264
|
+
file,
|
|
1265
|
+
typeRegistry,
|
|
1266
|
+
visiting,
|
|
1267
|
+
sourceNode
|
|
1268
|
+
);
|
|
1269
|
+
const result = hasNull ? {
|
|
1270
|
+
kind: "union",
|
|
1271
|
+
members: [inner, { kind: "primitive", primitiveKind: "null" }]
|
|
1272
|
+
} : inner;
|
|
1273
|
+
return registerNamed(result);
|
|
1077
1274
|
}
|
|
1078
1275
|
const members = nonNullTypes.map(
|
|
1079
|
-
(t) => resolveTypeNode(t, checker, file, typeRegistry, visiting)
|
|
1276
|
+
(t) => resolveTypeNode(t, checker, file, typeRegistry, visiting, sourceNode)
|
|
1080
1277
|
);
|
|
1081
1278
|
if (hasNull) {
|
|
1082
1279
|
members.push({ kind: "primitive", primitiveKind: "null" });
|
|
1083
1280
|
}
|
|
1084
|
-
return { kind: "union", members };
|
|
1281
|
+
return registerNamed({ kind: "union", members });
|
|
1085
1282
|
}
|
|
1086
1283
|
function resolveArrayType(type, checker, file, typeRegistry, visiting) {
|
|
1087
1284
|
const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
|
|
@@ -1097,30 +1294,84 @@ function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
|
|
|
1097
1294
|
if (!indexInfo) {
|
|
1098
1295
|
return null;
|
|
1099
1296
|
}
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1297
|
+
const valueType = resolveTypeNode(indexInfo.type, checker, file, typeRegistry, visiting);
|
|
1298
|
+
return { kind: "record", valueType };
|
|
1299
|
+
}
|
|
1300
|
+
function typeNodeContainsReference(type, targetName) {
|
|
1301
|
+
switch (type.kind) {
|
|
1302
|
+
case "reference":
|
|
1303
|
+
return type.name === targetName;
|
|
1304
|
+
case "array":
|
|
1305
|
+
return typeNodeContainsReference(type.items, targetName);
|
|
1306
|
+
case "record":
|
|
1307
|
+
return typeNodeContainsReference(type.valueType, targetName);
|
|
1308
|
+
case "union":
|
|
1309
|
+
return type.members.some((member) => typeNodeContainsReference(member, targetName));
|
|
1310
|
+
case "object":
|
|
1311
|
+
return type.properties.some(
|
|
1312
|
+
(property) => typeNodeContainsReference(property.type, targetName)
|
|
1313
|
+
);
|
|
1314
|
+
case "primitive":
|
|
1315
|
+
case "enum":
|
|
1316
|
+
case "dynamic":
|
|
1317
|
+
case "custom":
|
|
1318
|
+
return false;
|
|
1319
|
+
default: {
|
|
1320
|
+
const _exhaustive = type;
|
|
1321
|
+
return _exhaustive;
|
|
1322
|
+
}
|
|
1109
1323
|
}
|
|
1110
1324
|
}
|
|
1111
1325
|
function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
1112
|
-
const
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1326
|
+
const typeName = getNamedTypeName(type);
|
|
1327
|
+
const namedTypeName = typeName ?? void 0;
|
|
1328
|
+
const namedDecl = getNamedTypeDeclaration(type);
|
|
1329
|
+
const shouldRegisterNamedType = namedTypeName !== void 0 && !(namedTypeName === "Record" && namedDecl?.getSourceFile().fileName !== file);
|
|
1330
|
+
const clearNamedTypeRegistration = () => {
|
|
1331
|
+
if (namedTypeName === void 0 || !shouldRegisterNamedType) {
|
|
1332
|
+
return;
|
|
1333
|
+
}
|
|
1334
|
+
Reflect.deleteProperty(typeRegistry, namedTypeName);
|
|
1335
|
+
};
|
|
1116
1336
|
if (visiting.has(type)) {
|
|
1337
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
1338
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
1339
|
+
}
|
|
1117
1340
|
return { kind: "object", properties: [], additionalProperties: false };
|
|
1118
1341
|
}
|
|
1342
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[namedTypeName]) {
|
|
1343
|
+
typeRegistry[namedTypeName] = {
|
|
1344
|
+
name: namedTypeName,
|
|
1345
|
+
type: RESOLVING_TYPE_PLACEHOLDER,
|
|
1346
|
+
provenance: provenanceForDeclaration(namedDecl, file)
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1119
1349
|
visiting.add(type);
|
|
1120
|
-
|
|
1121
|
-
|
|
1350
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType && typeRegistry[namedTypeName]?.type !== void 0) {
|
|
1351
|
+
if (typeRegistry[namedTypeName].type !== RESOLVING_TYPE_PLACEHOLDER) {
|
|
1352
|
+
visiting.delete(type);
|
|
1353
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
const recordNode = tryResolveRecordType(type, checker, file, typeRegistry, visiting);
|
|
1357
|
+
if (recordNode) {
|
|
1122
1358
|
visiting.delete(type);
|
|
1123
|
-
|
|
1359
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
1360
|
+
const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, namedTypeName);
|
|
1361
|
+
if (!isRecursiveRecord) {
|
|
1362
|
+
clearNamedTypeRegistration();
|
|
1363
|
+
return recordNode;
|
|
1364
|
+
}
|
|
1365
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
|
|
1366
|
+
typeRegistry[namedTypeName] = {
|
|
1367
|
+
name: namedTypeName,
|
|
1368
|
+
type: recordNode,
|
|
1369
|
+
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
1370
|
+
provenance: provenanceForDeclaration(namedDecl, file)
|
|
1371
|
+
};
|
|
1372
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
1373
|
+
}
|
|
1374
|
+
return recordNode;
|
|
1124
1375
|
}
|
|
1125
1376
|
const properties = [];
|
|
1126
1377
|
const fieldInfoMap = getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting);
|
|
@@ -1129,7 +1380,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
1129
1380
|
if (!declaration) continue;
|
|
1130
1381
|
const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
|
|
1131
1382
|
const optional = !!(prop.flags & ts4.SymbolFlags.Optional);
|
|
1132
|
-
const propTypeNode = resolveTypeNode(
|
|
1383
|
+
const propTypeNode = resolveTypeNode(
|
|
1384
|
+
propType,
|
|
1385
|
+
checker,
|
|
1386
|
+
file,
|
|
1387
|
+
typeRegistry,
|
|
1388
|
+
visiting,
|
|
1389
|
+
declaration
|
|
1390
|
+
);
|
|
1133
1391
|
const fieldNodeInfo = fieldInfoMap?.get(prop.name);
|
|
1134
1392
|
properties.push({
|
|
1135
1393
|
name: prop.name,
|
|
@@ -1146,13 +1404,15 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
1146
1404
|
properties,
|
|
1147
1405
|
additionalProperties: true
|
|
1148
1406
|
};
|
|
1149
|
-
if (
|
|
1150
|
-
|
|
1151
|
-
|
|
1407
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
1408
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
|
|
1409
|
+
typeRegistry[namedTypeName] = {
|
|
1410
|
+
name: namedTypeName,
|
|
1152
1411
|
type: objectNode,
|
|
1153
|
-
|
|
1412
|
+
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
1413
|
+
provenance: provenanceForDeclaration(namedDecl, file)
|
|
1154
1414
|
};
|
|
1155
|
-
return { kind: "reference", name:
|
|
1415
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
1156
1416
|
}
|
|
1157
1417
|
return objectNode;
|
|
1158
1418
|
}
|
|
@@ -1244,6 +1504,12 @@ function provenanceForNode(node, file) {
|
|
|
1244
1504
|
function provenanceForFile(file) {
|
|
1245
1505
|
return { surface: "tsdoc", file, line: 0, column: 0 };
|
|
1246
1506
|
}
|
|
1507
|
+
function provenanceForDeclaration(node, file) {
|
|
1508
|
+
if (!node) {
|
|
1509
|
+
return provenanceForFile(file);
|
|
1510
|
+
}
|
|
1511
|
+
return provenanceForNode(node, file);
|
|
1512
|
+
}
|
|
1247
1513
|
function getNamedTypeName(type) {
|
|
1248
1514
|
const symbol = type.getSymbol();
|
|
1249
1515
|
if (symbol?.declarations) {
|
|
@@ -1262,6 +1528,20 @@ function getNamedTypeName(type) {
|
|
|
1262
1528
|
}
|
|
1263
1529
|
return null;
|
|
1264
1530
|
}
|
|
1531
|
+
function getNamedTypeDeclaration(type) {
|
|
1532
|
+
const symbol = type.getSymbol();
|
|
1533
|
+
if (symbol?.declarations) {
|
|
1534
|
+
const decl = symbol.declarations[0];
|
|
1535
|
+
if (decl && (ts4.isClassDeclaration(decl) || ts4.isInterfaceDeclaration(decl) || ts4.isTypeAliasDeclaration(decl))) {
|
|
1536
|
+
return decl;
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
const aliasSymbol = type.aliasSymbol;
|
|
1540
|
+
if (aliasSymbol?.declarations) {
|
|
1541
|
+
return aliasSymbol.declarations.find(ts4.isTypeAliasDeclaration);
|
|
1542
|
+
}
|
|
1543
|
+
return void 0;
|
|
1544
|
+
}
|
|
1265
1545
|
function analyzeMethod(method, checker) {
|
|
1266
1546
|
if (!ts4.isIdentifier(method.name)) {
|
|
1267
1547
|
return null;
|
|
@@ -1321,6 +1601,9 @@ function generateJsonSchemaFromIR(ir, options) {
|
|
|
1321
1601
|
const ctx = makeContext(options);
|
|
1322
1602
|
for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
|
|
1323
1603
|
ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
|
|
1604
|
+
if (typeDef.annotations && typeDef.annotations.length > 0) {
|
|
1605
|
+
applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
|
|
1606
|
+
}
|
|
1324
1607
|
}
|
|
1325
1608
|
const properties = {};
|
|
1326
1609
|
const required = [];
|
|
@@ -1332,6 +1615,9 @@ function generateJsonSchemaFromIR(ir, options) {
|
|
|
1332
1615
|
properties,
|
|
1333
1616
|
...uniqueRequired.length > 0 && { required: uniqueRequired }
|
|
1334
1617
|
};
|
|
1618
|
+
if (ir.annotations && ir.annotations.length > 0) {
|
|
1619
|
+
applyAnnotations(result, ir.annotations, ctx);
|
|
1620
|
+
}
|
|
1335
1621
|
if (Object.keys(ctx.defs).length > 0) {
|
|
1336
1622
|
result.$defs = ctx.defs;
|
|
1337
1623
|
}
|
|
@@ -1361,22 +1647,51 @@ function collectFields(elements, properties, required, ctx) {
|
|
|
1361
1647
|
}
|
|
1362
1648
|
function generateFieldSchema(field, ctx) {
|
|
1363
1649
|
const schema = generateTypeNode(field.type, ctx);
|
|
1650
|
+
const itemStringSchema = schema.type === "array" && schema.items?.type === "string" ? schema.items : void 0;
|
|
1364
1651
|
const directConstraints = [];
|
|
1652
|
+
const itemConstraints = [];
|
|
1365
1653
|
const pathConstraints = [];
|
|
1366
1654
|
for (const c of field.constraints) {
|
|
1367
1655
|
if (c.path) {
|
|
1368
1656
|
pathConstraints.push(c);
|
|
1657
|
+
} else if (itemStringSchema !== void 0 && isStringItemConstraint(c)) {
|
|
1658
|
+
itemConstraints.push(c);
|
|
1369
1659
|
} else {
|
|
1370
1660
|
directConstraints.push(c);
|
|
1371
1661
|
}
|
|
1372
1662
|
}
|
|
1373
1663
|
applyConstraints(schema, directConstraints, ctx);
|
|
1374
|
-
|
|
1664
|
+
if (itemStringSchema !== void 0) {
|
|
1665
|
+
applyConstraints(itemStringSchema, itemConstraints, ctx);
|
|
1666
|
+
}
|
|
1667
|
+
const rootAnnotations = [];
|
|
1668
|
+
const itemAnnotations = [];
|
|
1669
|
+
for (const annotation of field.annotations) {
|
|
1670
|
+
if (itemStringSchema !== void 0 && annotation.annotationKind === "format") {
|
|
1671
|
+
itemAnnotations.push(annotation);
|
|
1672
|
+
} else {
|
|
1673
|
+
rootAnnotations.push(annotation);
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
applyAnnotations(schema, rootAnnotations, ctx);
|
|
1677
|
+
if (itemStringSchema !== void 0) {
|
|
1678
|
+
applyAnnotations(itemStringSchema, itemAnnotations, ctx);
|
|
1679
|
+
}
|
|
1375
1680
|
if (pathConstraints.length === 0) {
|
|
1376
1681
|
return schema;
|
|
1377
1682
|
}
|
|
1378
1683
|
return applyPathTargetedConstraints(schema, pathConstraints, ctx);
|
|
1379
1684
|
}
|
|
1685
|
+
function isStringItemConstraint(constraint) {
|
|
1686
|
+
switch (constraint.constraintKind) {
|
|
1687
|
+
case "minLength":
|
|
1688
|
+
case "maxLength":
|
|
1689
|
+
case "pattern":
|
|
1690
|
+
return true;
|
|
1691
|
+
default:
|
|
1692
|
+
return false;
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1380
1695
|
function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
|
|
1381
1696
|
if (schema.type === "array" && schema.items) {
|
|
1382
1697
|
schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx);
|
|
@@ -1594,6 +1909,9 @@ function applyConstraints(schema, constraints, ctx) {
|
|
|
1594
1909
|
case "uniqueItems":
|
|
1595
1910
|
schema.uniqueItems = constraint.value;
|
|
1596
1911
|
break;
|
|
1912
|
+
case "const":
|
|
1913
|
+
schema.const = constraint.value;
|
|
1914
|
+
break;
|
|
1597
1915
|
case "allowedMembers":
|
|
1598
1916
|
break;
|
|
1599
1917
|
case "custom":
|
|
@@ -1618,8 +1936,14 @@ function applyAnnotations(schema, annotations, ctx) {
|
|
|
1618
1936
|
case "defaultValue":
|
|
1619
1937
|
schema.default = annotation.value;
|
|
1620
1938
|
break;
|
|
1939
|
+
case "format":
|
|
1940
|
+
schema.format = annotation.value;
|
|
1941
|
+
break;
|
|
1621
1942
|
case "deprecated":
|
|
1622
1943
|
schema.deprecated = true;
|
|
1944
|
+
if (annotation.message !== void 0 && annotation.message !== "") {
|
|
1945
|
+
schema["x-formspec-deprecation-description"] = annotation.message;
|
|
1946
|
+
}
|
|
1623
1947
|
break;
|
|
1624
1948
|
case "placeholder":
|
|
1625
1949
|
break;
|
|
@@ -1801,25 +2125,31 @@ function createShowRule(fieldName, value) {
|
|
|
1801
2125
|
}
|
|
1802
2126
|
};
|
|
1803
2127
|
}
|
|
2128
|
+
function flattenConditionSchema(scope, schema) {
|
|
2129
|
+
if (schema.allOf === void 0) {
|
|
2130
|
+
if (scope === "#") {
|
|
2131
|
+
return [schema];
|
|
2132
|
+
}
|
|
2133
|
+
const fieldName = scope.replace("#/properties/", "");
|
|
2134
|
+
return [
|
|
2135
|
+
{
|
|
2136
|
+
properties: {
|
|
2137
|
+
[fieldName]: schema
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
];
|
|
2141
|
+
}
|
|
2142
|
+
return schema.allOf.flatMap((member) => flattenConditionSchema(scope, member));
|
|
2143
|
+
}
|
|
1804
2144
|
function combineRules(parentRule, childRule) {
|
|
1805
|
-
const parentCondition = parentRule.condition;
|
|
1806
|
-
const childCondition = childRule.condition;
|
|
1807
2145
|
return {
|
|
1808
2146
|
effect: "SHOW",
|
|
1809
2147
|
condition: {
|
|
1810
2148
|
scope: "#",
|
|
1811
2149
|
schema: {
|
|
1812
2150
|
allOf: [
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
[parentCondition.scope.replace("#/properties/", "")]: parentCondition.schema
|
|
1816
|
-
}
|
|
1817
|
-
},
|
|
1818
|
-
{
|
|
1819
|
-
properties: {
|
|
1820
|
-
[childCondition.scope.replace("#/properties/", "")]: childCondition.schema
|
|
1821
|
-
}
|
|
1822
|
-
}
|
|
2151
|
+
...flattenConditionSchema(parentRule.condition.scope, parentRule.condition.schema),
|
|
2152
|
+
...flattenConditionSchema(childRule.condition.scope, childRule.condition.schema)
|
|
1823
2153
|
]
|
|
1824
2154
|
}
|
|
1825
2155
|
}
|
|
@@ -1827,10 +2157,14 @@ function combineRules(parentRule, childRule) {
|
|
|
1827
2157
|
}
|
|
1828
2158
|
function fieldNodeToControl(field, parentRule) {
|
|
1829
2159
|
const displayNameAnnotation = field.annotations.find((a) => a.annotationKind === "displayName");
|
|
2160
|
+
const placeholderAnnotation = field.annotations.find((a) => a.annotationKind === "placeholder");
|
|
1830
2161
|
const control = {
|
|
1831
2162
|
type: "Control",
|
|
1832
2163
|
scope: fieldToScope(field.name),
|
|
1833
2164
|
...displayNameAnnotation !== void 0 && { label: displayNameAnnotation.value },
|
|
2165
|
+
...placeholderAnnotation !== void 0 && {
|
|
2166
|
+
options: { placeholder: placeholderAnnotation.value }
|
|
2167
|
+
},
|
|
1834
2168
|
...parentRule !== void 0 && { rule: parentRule }
|
|
1835
2169
|
};
|
|
1836
2170
|
return control;
|
|
@@ -1916,6 +2250,15 @@ function addUnknownExtension(ctx, message, primary) {
|
|
|
1916
2250
|
relatedLocations: []
|
|
1917
2251
|
});
|
|
1918
2252
|
}
|
|
2253
|
+
function addUnknownPathTarget(ctx, message, primary) {
|
|
2254
|
+
ctx.diagnostics.push({
|
|
2255
|
+
code: "UNKNOWN_PATH_TARGET",
|
|
2256
|
+
message,
|
|
2257
|
+
severity: "error",
|
|
2258
|
+
primaryLocation: primary,
|
|
2259
|
+
relatedLocations: []
|
|
2260
|
+
});
|
|
2261
|
+
}
|
|
1919
2262
|
function addConstraintBroadening(ctx, message, primary, related) {
|
|
1920
2263
|
ctx.diagnostics.push({
|
|
1921
2264
|
code: "CONSTRAINT_BROADENING",
|
|
@@ -1936,6 +2279,45 @@ function findAllowedMembers(constraints) {
|
|
|
1936
2279
|
(c) => c.constraintKind === "allowedMembers"
|
|
1937
2280
|
);
|
|
1938
2281
|
}
|
|
2282
|
+
function findConstConstraints(constraints) {
|
|
2283
|
+
return constraints.filter(
|
|
2284
|
+
(c) => c.constraintKind === "const"
|
|
2285
|
+
);
|
|
2286
|
+
}
|
|
2287
|
+
function jsonValueEquals(left, right) {
|
|
2288
|
+
if (left === right) {
|
|
2289
|
+
return true;
|
|
2290
|
+
}
|
|
2291
|
+
if (Array.isArray(left) || Array.isArray(right)) {
|
|
2292
|
+
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
|
|
2293
|
+
return false;
|
|
2294
|
+
}
|
|
2295
|
+
return left.every((item, index) => jsonValueEquals(item, right[index]));
|
|
2296
|
+
}
|
|
2297
|
+
if (isJsonObject(left) || isJsonObject(right)) {
|
|
2298
|
+
if (!isJsonObject(left) || !isJsonObject(right)) {
|
|
2299
|
+
return false;
|
|
2300
|
+
}
|
|
2301
|
+
const leftKeys = Object.keys(left).sort();
|
|
2302
|
+
const rightKeys = Object.keys(right).sort();
|
|
2303
|
+
if (leftKeys.length !== rightKeys.length) {
|
|
2304
|
+
return false;
|
|
2305
|
+
}
|
|
2306
|
+
return leftKeys.every((key, index) => {
|
|
2307
|
+
const rightKey = rightKeys[index];
|
|
2308
|
+
if (rightKey !== key) {
|
|
2309
|
+
return false;
|
|
2310
|
+
}
|
|
2311
|
+
const leftValue = left[key];
|
|
2312
|
+
const rightValue = right[rightKey];
|
|
2313
|
+
return leftValue !== void 0 && rightValue !== void 0 && jsonValueEquals(leftValue, rightValue);
|
|
2314
|
+
});
|
|
2315
|
+
}
|
|
2316
|
+
return false;
|
|
2317
|
+
}
|
|
2318
|
+
function isJsonObject(value) {
|
|
2319
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2320
|
+
}
|
|
1939
2321
|
function isOrderedBoundConstraint(constraint) {
|
|
1940
2322
|
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
2323
|
}
|
|
@@ -2142,6 +2524,25 @@ function checkAllowedMembersContradiction(ctx, fieldName, constraints) {
|
|
|
2142
2524
|
}
|
|
2143
2525
|
}
|
|
2144
2526
|
}
|
|
2527
|
+
function checkConstContradictions(ctx, fieldName, constraints) {
|
|
2528
|
+
const constConstraints = findConstConstraints(constraints);
|
|
2529
|
+
if (constConstraints.length < 2) return;
|
|
2530
|
+
const first = constConstraints[0];
|
|
2531
|
+
if (first === void 0) return;
|
|
2532
|
+
for (let i = 1; i < constConstraints.length; i++) {
|
|
2533
|
+
const current = constConstraints[i];
|
|
2534
|
+
if (current === void 0) continue;
|
|
2535
|
+
if (jsonValueEquals(first.value, current.value)) {
|
|
2536
|
+
continue;
|
|
2537
|
+
}
|
|
2538
|
+
addContradiction(
|
|
2539
|
+
ctx,
|
|
2540
|
+
`Field "${fieldName}": conflicting @const constraints require both ${JSON.stringify(first.value)} and ${JSON.stringify(current.value)}`,
|
|
2541
|
+
first.provenance,
|
|
2542
|
+
current.provenance
|
|
2543
|
+
);
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2145
2546
|
function typeLabel(type) {
|
|
2146
2547
|
switch (type.kind) {
|
|
2147
2548
|
case "primitive":
|
|
@@ -2214,6 +2615,8 @@ function checkConstraintOnType(ctx, fieldName, type, constraint) {
|
|
|
2214
2615
|
const isString = effectiveType.kind === "primitive" && effectiveType.primitiveKind === "string";
|
|
2215
2616
|
const isArray = effectiveType.kind === "array";
|
|
2216
2617
|
const isEnum = effectiveType.kind === "enum";
|
|
2618
|
+
const arrayItemType = effectiveType.kind === "array" ? dereferenceType(ctx, effectiveType.items) : void 0;
|
|
2619
|
+
const isStringArray = arrayItemType?.kind === "primitive" && arrayItemType.primitiveKind === "string";
|
|
2217
2620
|
const label = typeLabel(effectiveType);
|
|
2218
2621
|
const ck = constraint.constraintKind;
|
|
2219
2622
|
switch (ck) {
|
|
@@ -2234,10 +2637,10 @@ function checkConstraintOnType(ctx, fieldName, type, constraint) {
|
|
|
2234
2637
|
case "minLength":
|
|
2235
2638
|
case "maxLength":
|
|
2236
2639
|
case "pattern": {
|
|
2237
|
-
if (!isString) {
|
|
2640
|
+
if (!isString && !isStringArray) {
|
|
2238
2641
|
addTypeMismatch(
|
|
2239
2642
|
ctx,
|
|
2240
|
-
`Field "${fieldName}": constraint "${ck}" is only valid on string fields, but field type is "${label}"`,
|
|
2643
|
+
`Field "${fieldName}": constraint "${ck}" is only valid on string fields or string array items, but field type is "${label}"`,
|
|
2241
2644
|
constraint.provenance
|
|
2242
2645
|
);
|
|
2243
2646
|
}
|
|
@@ -2265,6 +2668,37 @@ function checkConstraintOnType(ctx, fieldName, type, constraint) {
|
|
|
2265
2668
|
}
|
|
2266
2669
|
break;
|
|
2267
2670
|
}
|
|
2671
|
+
case "const": {
|
|
2672
|
+
const isPrimitiveConstType = effectiveType.kind === "primitive" && ["string", "number", "boolean", "null"].includes(effectiveType.primitiveKind) || effectiveType.kind === "enum";
|
|
2673
|
+
if (!isPrimitiveConstType) {
|
|
2674
|
+
addTypeMismatch(
|
|
2675
|
+
ctx,
|
|
2676
|
+
`Field "${fieldName}": constraint "const" is only valid on primitive or enum fields, but field type is "${label}"`,
|
|
2677
|
+
constraint.provenance
|
|
2678
|
+
);
|
|
2679
|
+
break;
|
|
2680
|
+
}
|
|
2681
|
+
if (effectiveType.kind === "primitive") {
|
|
2682
|
+
const valueType = constraint.value === null ? "null" : Array.isArray(constraint.value) ? "array" : typeof constraint.value;
|
|
2683
|
+
if (valueType !== effectiveType.primitiveKind) {
|
|
2684
|
+
addTypeMismatch(
|
|
2685
|
+
ctx,
|
|
2686
|
+
`Field "${fieldName}": @const value type "${valueType}" is incompatible with field type "${effectiveType.primitiveKind}"`,
|
|
2687
|
+
constraint.provenance
|
|
2688
|
+
);
|
|
2689
|
+
}
|
|
2690
|
+
break;
|
|
2691
|
+
}
|
|
2692
|
+
const memberValues = effectiveType.members.map((member) => member.value);
|
|
2693
|
+
if (!memberValues.some((member) => jsonValueEquals(member, constraint.value))) {
|
|
2694
|
+
addTypeMismatch(
|
|
2695
|
+
ctx,
|
|
2696
|
+
`Field "${fieldName}": @const value ${JSON.stringify(constraint.value)} is not one of the enum members`,
|
|
2697
|
+
constraint.provenance
|
|
2698
|
+
);
|
|
2699
|
+
}
|
|
2700
|
+
break;
|
|
2701
|
+
}
|
|
2268
2702
|
case "custom": {
|
|
2269
2703
|
checkCustomConstraint(ctx, fieldName, effectiveType, constraint);
|
|
2270
2704
|
break;
|
|
@@ -2283,9 +2717,9 @@ function checkTypeApplicability(ctx, fieldName, type, constraints) {
|
|
|
2283
2717
|
const resolution = resolvePathTargetType(ctx, type, constraint.path.segments);
|
|
2284
2718
|
const targetFieldName = formatPathTargetFieldName(fieldName, constraint.path.segments);
|
|
2285
2719
|
if (resolution.kind === "missing-property") {
|
|
2286
|
-
|
|
2720
|
+
addUnknownPathTarget(
|
|
2287
2721
|
ctx,
|
|
2288
|
-
`Field "${
|
|
2722
|
+
`Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" references unknown path segment "${resolution.segment}"`,
|
|
2289
2723
|
constraint.provenance
|
|
2290
2724
|
);
|
|
2291
2725
|
continue;
|
|
@@ -2345,6 +2779,7 @@ function validateConstraints(ctx, name, type, constraints) {
|
|
|
2345
2779
|
checkNumericContradictions(ctx, name, constraints);
|
|
2346
2780
|
checkLengthContradictions(ctx, name, constraints);
|
|
2347
2781
|
checkAllowedMembersContradiction(ctx, name, constraints);
|
|
2782
|
+
checkConstContradictions(ctx, name, constraints);
|
|
2348
2783
|
checkConstraintBroadening(ctx, name, constraints);
|
|
2349
2784
|
checkTypeApplicability(ctx, name, type, constraints);
|
|
2350
2785
|
}
|