@formspec/build 0.1.0-alpha.14 → 0.1.0-alpha.15
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__/extension-runtime.integration.test.d.ts +2 -0
- package/dist/__tests__/extension-runtime.integration.test.d.ts.map +1 -0
- package/dist/__tests__/fixtures/edge-cases.d.ts +11 -0
- package/dist/__tests__/fixtures/edge-cases.d.ts.map +1 -1
- package/dist/__tests__/fixtures/example-a-builtins.d.ts +6 -6
- package/dist/__tests__/fixtures/example-interface-types.d.ts +26 -26
- package/dist/__tests__/fixtures/example-interface-types.d.ts.map +1 -1
- package/dist/__tests__/jsdoc-constraints.test.d.ts +4 -5
- package/dist/__tests__/jsdoc-constraints.test.d.ts.map +1 -1
- package/dist/__tests__/parity/fixtures/plan-status/chain-dsl.d.ts +19 -0
- package/dist/__tests__/parity/fixtures/plan-status/chain-dsl.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/plan-status/expected-ir.d.ts +6 -0
- package/dist/__tests__/parity/fixtures/plan-status/expected-ir.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/plan-status/tsdoc.d.ts +17 -0
- package/dist/__tests__/parity/fixtures/plan-status/tsdoc.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/usd-cents/chain-dsl.d.ts +9 -0
- package/dist/__tests__/parity/fixtures/usd-cents/chain-dsl.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/usd-cents/expected-ir.d.ts +6 -0
- package/dist/__tests__/parity/fixtures/usd-cents/expected-ir.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/usd-cents/tsdoc.d.ts +19 -0
- package/dist/__tests__/parity/fixtures/usd-cents/tsdoc.d.ts.map +1 -0
- package/dist/__tests__/parity/utils.d.ts +6 -1
- package/dist/__tests__/parity/utils.d.ts.map +1 -1
- package/dist/analyzer/class-analyzer.d.ts +1 -1
- package/dist/analyzer/class-analyzer.d.ts.map +1 -1
- package/dist/analyzer/jsdoc-constraints.d.ts +7 -51
- package/dist/analyzer/jsdoc-constraints.d.ts.map +1 -1
- package/dist/analyzer/tsdoc-parser.d.ts +6 -8
- package/dist/analyzer/tsdoc-parser.d.ts.map +1 -1
- package/dist/browser.cjs +387 -98
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.d.ts +15 -2
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +385 -98
- package/dist/browser.js.map +1 -1
- package/dist/build.d.ts +131 -5
- package/dist/canonicalize/tsdoc-canonicalizer.d.ts +3 -3
- package/dist/cli.cjs +272 -69
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +271 -72
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +257 -67
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +20 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +255 -71
- package/dist/index.js.map +1 -1
- package/dist/internals.cjs +461 -137
- package/dist/internals.cjs.map +1 -1
- package/dist/internals.js +459 -139
- package/dist/internals.js.map +1 -1
- package/dist/json-schema/generator.d.ts +8 -2
- package/dist/json-schema/generator.d.ts.map +1 -1
- package/dist/json-schema/ir-generator.d.ts +24 -2
- package/dist/json-schema/ir-generator.d.ts.map +1 -1
- package/dist/json-schema/types.d.ts +1 -1
- package/dist/json-schema/types.d.ts.map +1 -1
- package/dist/validate/constraint-validator.d.ts +3 -7
- package/dist/validate/constraint-validator.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/internals.cjs
CHANGED
|
@@ -228,7 +228,7 @@ function canonicalizeArrayField(field) {
|
|
|
228
228
|
const itemsType = {
|
|
229
229
|
kind: "object",
|
|
230
230
|
properties: itemProperties,
|
|
231
|
-
additionalProperties:
|
|
231
|
+
additionalProperties: true
|
|
232
232
|
};
|
|
233
233
|
const type = { kind: "array", items: itemsType };
|
|
234
234
|
const constraints = [];
|
|
@@ -263,7 +263,7 @@ function canonicalizeObjectField(field) {
|
|
|
263
263
|
const type = {
|
|
264
264
|
kind: "object",
|
|
265
265
|
properties,
|
|
266
|
-
additionalProperties:
|
|
266
|
+
additionalProperties: true
|
|
267
267
|
};
|
|
268
268
|
return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
|
|
269
269
|
}
|
|
@@ -511,7 +511,6 @@ var ts4 = __toESM(require("typescript"), 1);
|
|
|
511
511
|
|
|
512
512
|
// src/analyzer/jsdoc-constraints.ts
|
|
513
513
|
var ts3 = __toESM(require("typescript"), 1);
|
|
514
|
-
var import_core4 = require("@formspec/core");
|
|
515
514
|
|
|
516
515
|
// src/analyzer/tsdoc-parser.ts
|
|
517
516
|
var ts2 = __toESM(require("typescript"), 1);
|
|
@@ -553,6 +552,15 @@ function createFormSpecTSDocConfig() {
|
|
|
553
552
|
})
|
|
554
553
|
);
|
|
555
554
|
}
|
|
555
|
+
for (const tagName of ["displayName", "description"]) {
|
|
556
|
+
config.addTagDefinition(
|
|
557
|
+
new import_tsdoc.TSDocTagDefinition({
|
|
558
|
+
tagName: "@" + tagName,
|
|
559
|
+
syntaxKind: import_tsdoc.TSDocTagSyntaxKind.BlockTag,
|
|
560
|
+
allowMultiple: true
|
|
561
|
+
})
|
|
562
|
+
);
|
|
563
|
+
}
|
|
556
564
|
return config;
|
|
557
565
|
}
|
|
558
566
|
var sharedParser;
|
|
@@ -582,6 +590,27 @@ function parseTSDocTags(node, file = "") {
|
|
|
582
590
|
const docComment = parserContext.docComment;
|
|
583
591
|
for (const block of docComment.customBlocks) {
|
|
584
592
|
const tagName = (0, import_core3.normalizeConstraintTagName)(block.blockTag.tagName.substring(1));
|
|
593
|
+
if (tagName === "displayName" || tagName === "description") {
|
|
594
|
+
const text2 = extractBlockText(block).trim();
|
|
595
|
+
if (text2 === "") continue;
|
|
596
|
+
const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
|
|
597
|
+
if (tagName === "displayName") {
|
|
598
|
+
annotations.push({
|
|
599
|
+
kind: "annotation",
|
|
600
|
+
annotationKind: "displayName",
|
|
601
|
+
value: text2,
|
|
602
|
+
provenance: provenance2
|
|
603
|
+
});
|
|
604
|
+
} else {
|
|
605
|
+
annotations.push({
|
|
606
|
+
kind: "annotation",
|
|
607
|
+
annotationKind: "description",
|
|
608
|
+
value: text2,
|
|
609
|
+
provenance: provenance2
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
continue;
|
|
613
|
+
}
|
|
585
614
|
if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
586
615
|
const text = extractBlockText(block).trim();
|
|
587
616
|
if (text === "") continue;
|
|
@@ -613,41 +642,6 @@ function parseTSDocTags(node, file = "") {
|
|
|
613
642
|
constraints.push(constraintNode);
|
|
614
643
|
}
|
|
615
644
|
}
|
|
616
|
-
let displayName;
|
|
617
|
-
let description;
|
|
618
|
-
let displayNameTag;
|
|
619
|
-
let descriptionTag;
|
|
620
|
-
for (const tag of jsDocTagsAll) {
|
|
621
|
-
const tagName = tag.tagName.text;
|
|
622
|
-
const commentText = getTagCommentText(tag);
|
|
623
|
-
if (commentText === void 0 || commentText.trim() === "") {
|
|
624
|
-
continue;
|
|
625
|
-
}
|
|
626
|
-
const trimmed = commentText.trim();
|
|
627
|
-
if (tagName === "Field_displayName") {
|
|
628
|
-
displayName = trimmed;
|
|
629
|
-
displayNameTag = tag;
|
|
630
|
-
} else if (tagName === "Field_description") {
|
|
631
|
-
description = trimmed;
|
|
632
|
-
descriptionTag = tag;
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
if (displayName !== void 0 && displayNameTag) {
|
|
636
|
-
annotations.push({
|
|
637
|
-
kind: "annotation",
|
|
638
|
-
annotationKind: "displayName",
|
|
639
|
-
value: displayName,
|
|
640
|
-
provenance: provenanceForJSDocTag(displayNameTag, file)
|
|
641
|
-
});
|
|
642
|
-
}
|
|
643
|
-
if (description !== void 0 && descriptionTag) {
|
|
644
|
-
annotations.push({
|
|
645
|
-
kind: "annotation",
|
|
646
|
-
annotationKind: "description",
|
|
647
|
-
value: description,
|
|
648
|
-
provenance: provenanceForJSDocTag(descriptionTag, file)
|
|
649
|
-
});
|
|
650
|
-
}
|
|
651
645
|
return { constraints, annotations };
|
|
652
646
|
}
|
|
653
647
|
function extractPathTarget(text) {
|
|
@@ -912,18 +906,19 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
|
|
|
912
906
|
const tsType = checker.getTypeAtLocation(prop);
|
|
913
907
|
const optional = prop.questionToken !== void 0;
|
|
914
908
|
const provenance = provenanceForNode(prop, file);
|
|
915
|
-
|
|
909
|
+
let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
|
|
916
910
|
const constraints = [];
|
|
917
911
|
if (prop.type) {
|
|
918
912
|
constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
|
|
919
913
|
}
|
|
920
914
|
constraints.push(...extractJSDocConstraintNodes(prop, file));
|
|
921
|
-
|
|
915
|
+
let annotations = [];
|
|
922
916
|
annotations.push(...extractJSDocAnnotationNodes(prop, file));
|
|
923
917
|
const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
|
|
924
918
|
if (defaultAnnotation) {
|
|
925
919
|
annotations.push(defaultAnnotation);
|
|
926
920
|
}
|
|
921
|
+
({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
|
|
927
922
|
return {
|
|
928
923
|
kind: "field",
|
|
929
924
|
name,
|
|
@@ -942,14 +937,15 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
|
|
|
942
937
|
const tsType = checker.getTypeAtLocation(prop);
|
|
943
938
|
const optional = prop.questionToken !== void 0;
|
|
944
939
|
const provenance = provenanceForNode(prop, file);
|
|
945
|
-
|
|
940
|
+
let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
|
|
946
941
|
const constraints = [];
|
|
947
942
|
if (prop.type) {
|
|
948
943
|
constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
|
|
949
944
|
}
|
|
950
945
|
constraints.push(...extractJSDocConstraintNodes(prop, file));
|
|
951
|
-
|
|
946
|
+
let annotations = [];
|
|
952
947
|
annotations.push(...extractJSDocAnnotationNodes(prop, file));
|
|
948
|
+
({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
|
|
953
949
|
return {
|
|
954
950
|
kind: "field",
|
|
955
951
|
name,
|
|
@@ -960,6 +956,68 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
|
|
|
960
956
|
provenance
|
|
961
957
|
};
|
|
962
958
|
}
|
|
959
|
+
function applyEnumMemberDisplayNames(type, annotations) {
|
|
960
|
+
if (!annotations.some(
|
|
961
|
+
(annotation) => annotation.annotationKind === "displayName" && annotation.value.trim().startsWith(":")
|
|
962
|
+
)) {
|
|
963
|
+
return { type, annotations: [...annotations] };
|
|
964
|
+
}
|
|
965
|
+
const consumed = /* @__PURE__ */ new Set();
|
|
966
|
+
const nextType = rewriteEnumDisplayNames(type, annotations, consumed);
|
|
967
|
+
if (consumed.size === 0) {
|
|
968
|
+
return { type, annotations: [...annotations] };
|
|
969
|
+
}
|
|
970
|
+
return {
|
|
971
|
+
type: nextType,
|
|
972
|
+
annotations: annotations.filter((annotation) => !consumed.has(annotation))
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
function rewriteEnumDisplayNames(type, annotations, consumed) {
|
|
976
|
+
switch (type.kind) {
|
|
977
|
+
case "enum":
|
|
978
|
+
return applyEnumMemberDisplayNamesToEnum(type, annotations, consumed);
|
|
979
|
+
case "union": {
|
|
980
|
+
return {
|
|
981
|
+
...type,
|
|
982
|
+
members: type.members.map(
|
|
983
|
+
(member) => rewriteEnumDisplayNames(member, annotations, consumed)
|
|
984
|
+
)
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
default:
|
|
988
|
+
return type;
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
function applyEnumMemberDisplayNamesToEnum(type, annotations, consumed) {
|
|
992
|
+
const displayNames = /* @__PURE__ */ new Map();
|
|
993
|
+
for (const annotation of annotations) {
|
|
994
|
+
if (annotation.annotationKind !== "displayName") continue;
|
|
995
|
+
const parsed = parseEnumMemberDisplayName(annotation.value);
|
|
996
|
+
if (!parsed) continue;
|
|
997
|
+
consumed.add(annotation);
|
|
998
|
+
const member = type.members.find((m) => String(m.value) === parsed.value);
|
|
999
|
+
if (!member) continue;
|
|
1000
|
+
displayNames.set(String(member.value), parsed.label);
|
|
1001
|
+
}
|
|
1002
|
+
if (displayNames.size === 0) {
|
|
1003
|
+
return type;
|
|
1004
|
+
}
|
|
1005
|
+
return {
|
|
1006
|
+
...type,
|
|
1007
|
+
members: type.members.map((member) => {
|
|
1008
|
+
const displayName = displayNames.get(String(member.value));
|
|
1009
|
+
return displayName !== void 0 ? { ...member, displayName } : member;
|
|
1010
|
+
})
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
1013
|
+
function parseEnumMemberDisplayName(value) {
|
|
1014
|
+
const trimmed = value.trim();
|
|
1015
|
+
const match = /^:([^\s]+)\s+([\s\S]+)$/.exec(trimmed);
|
|
1016
|
+
if (!match?.[1] || !match[2]) return null;
|
|
1017
|
+
const label = match[2].trim();
|
|
1018
|
+
if (label === "") return null;
|
|
1019
|
+
return { value: match[1], label };
|
|
1020
|
+
}
|
|
963
1021
|
function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
|
|
964
1022
|
if (type.flags & ts4.TypeFlags.String) {
|
|
965
1023
|
return { kind: "primitive", primitiveKind: "string" };
|
|
@@ -1070,7 +1128,30 @@ function resolveArrayType(type, checker, file, typeRegistry, visiting) {
|
|
|
1070
1128
|
const items = elementType ? resolveTypeNode(elementType, checker, file, typeRegistry, visiting) : { kind: "primitive", primitiveKind: "string" };
|
|
1071
1129
|
return { kind: "array", items };
|
|
1072
1130
|
}
|
|
1131
|
+
function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
|
|
1132
|
+
if (type.getProperties().length > 0) {
|
|
1133
|
+
return null;
|
|
1134
|
+
}
|
|
1135
|
+
const indexInfo = checker.getIndexInfoOfType(type, ts4.IndexKind.String);
|
|
1136
|
+
if (!indexInfo) {
|
|
1137
|
+
return null;
|
|
1138
|
+
}
|
|
1139
|
+
if (visiting.has(type)) {
|
|
1140
|
+
return null;
|
|
1141
|
+
}
|
|
1142
|
+
visiting.add(type);
|
|
1143
|
+
try {
|
|
1144
|
+
const valueType = resolveTypeNode(indexInfo.type, checker, file, typeRegistry, visiting);
|
|
1145
|
+
return { kind: "record", valueType };
|
|
1146
|
+
} finally {
|
|
1147
|
+
visiting.delete(type);
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1073
1150
|
function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
1151
|
+
const recordNode = tryResolveRecordType(type, checker, file, typeRegistry, visiting);
|
|
1152
|
+
if (recordNode) {
|
|
1153
|
+
return recordNode;
|
|
1154
|
+
}
|
|
1074
1155
|
if (visiting.has(type)) {
|
|
1075
1156
|
return { kind: "object", properties: [], additionalProperties: false };
|
|
1076
1157
|
}
|
|
@@ -1102,7 +1183,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
1102
1183
|
const objectNode = {
|
|
1103
1184
|
kind: "object",
|
|
1104
1185
|
properties,
|
|
1105
|
-
additionalProperties:
|
|
1186
|
+
additionalProperties: true
|
|
1106
1187
|
};
|
|
1107
1188
|
if (typeName) {
|
|
1108
1189
|
typeRegistry[typeName] = {
|
|
@@ -1262,11 +1343,21 @@ function detectFormSpecReference(typeNode) {
|
|
|
1262
1343
|
}
|
|
1263
1344
|
|
|
1264
1345
|
// src/json-schema/ir-generator.ts
|
|
1265
|
-
function makeContext() {
|
|
1266
|
-
|
|
1346
|
+
function makeContext(options) {
|
|
1347
|
+
const vendorPrefix = options?.vendorPrefix ?? "x-formspec";
|
|
1348
|
+
if (!vendorPrefix.startsWith("x-")) {
|
|
1349
|
+
throw new Error(
|
|
1350
|
+
`Invalid vendorPrefix "${vendorPrefix}". Extension JSON Schema keywords must start with "x-".`
|
|
1351
|
+
);
|
|
1352
|
+
}
|
|
1353
|
+
return {
|
|
1354
|
+
defs: {},
|
|
1355
|
+
extensionRegistry: options?.extensionRegistry,
|
|
1356
|
+
vendorPrefix
|
|
1357
|
+
};
|
|
1267
1358
|
}
|
|
1268
|
-
function generateJsonSchemaFromIR(ir) {
|
|
1269
|
-
const ctx = makeContext();
|
|
1359
|
+
function generateJsonSchemaFromIR(ir, options) {
|
|
1360
|
+
const ctx = makeContext(options);
|
|
1270
1361
|
for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
|
|
1271
1362
|
ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
|
|
1272
1363
|
}
|
|
@@ -1318,16 +1409,16 @@ function generateFieldSchema(field, ctx) {
|
|
|
1318
1409
|
directConstraints.push(c);
|
|
1319
1410
|
}
|
|
1320
1411
|
}
|
|
1321
|
-
applyConstraints(schema, directConstraints);
|
|
1322
|
-
applyAnnotations(schema, field.annotations);
|
|
1412
|
+
applyConstraints(schema, directConstraints, ctx);
|
|
1413
|
+
applyAnnotations(schema, field.annotations, ctx);
|
|
1323
1414
|
if (pathConstraints.length === 0) {
|
|
1324
1415
|
return schema;
|
|
1325
1416
|
}
|
|
1326
|
-
return applyPathTargetedConstraints(schema, pathConstraints);
|
|
1417
|
+
return applyPathTargetedConstraints(schema, pathConstraints, ctx);
|
|
1327
1418
|
}
|
|
1328
|
-
function applyPathTargetedConstraints(schema, pathConstraints) {
|
|
1419
|
+
function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
|
|
1329
1420
|
if (schema.type === "array" && schema.items) {
|
|
1330
|
-
schema.items = applyPathTargetedConstraints(schema.items, pathConstraints);
|
|
1421
|
+
schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx);
|
|
1331
1422
|
return schema;
|
|
1332
1423
|
}
|
|
1333
1424
|
const byTarget = /* @__PURE__ */ new Map();
|
|
@@ -1341,7 +1432,7 @@ function applyPathTargetedConstraints(schema, pathConstraints) {
|
|
|
1341
1432
|
const propertyOverrides = {};
|
|
1342
1433
|
for (const [target, constraints] of byTarget) {
|
|
1343
1434
|
const subSchema = {};
|
|
1344
|
-
applyConstraints(subSchema, constraints);
|
|
1435
|
+
applyConstraints(subSchema, constraints, ctx);
|
|
1345
1436
|
propertyOverrides[target] = subSchema;
|
|
1346
1437
|
}
|
|
1347
1438
|
if (schema.$ref) {
|
|
@@ -1385,6 +1476,8 @@ function generateTypeNode(type, ctx) {
|
|
|
1385
1476
|
return generateArrayType(type, ctx);
|
|
1386
1477
|
case "object":
|
|
1387
1478
|
return generateObjectType(type, ctx);
|
|
1479
|
+
case "record":
|
|
1480
|
+
return generateRecordType(type, ctx);
|
|
1388
1481
|
case "union":
|
|
1389
1482
|
return generateUnionType(type, ctx);
|
|
1390
1483
|
case "reference":
|
|
@@ -1392,7 +1485,7 @@ function generateTypeNode(type, ctx) {
|
|
|
1392
1485
|
case "dynamic":
|
|
1393
1486
|
return generateDynamicType(type);
|
|
1394
1487
|
case "custom":
|
|
1395
|
-
return generateCustomType(type);
|
|
1488
|
+
return generateCustomType(type, ctx);
|
|
1396
1489
|
default: {
|
|
1397
1490
|
const _exhaustive = type;
|
|
1398
1491
|
return _exhaustive;
|
|
@@ -1441,16 +1534,27 @@ function generateObjectType(type, ctx) {
|
|
|
1441
1534
|
}
|
|
1442
1535
|
return schema;
|
|
1443
1536
|
}
|
|
1537
|
+
function generateRecordType(type, ctx) {
|
|
1538
|
+
return {
|
|
1539
|
+
type: "object",
|
|
1540
|
+
additionalProperties: generateTypeNode(type.valueType, ctx)
|
|
1541
|
+
};
|
|
1542
|
+
}
|
|
1444
1543
|
function generatePropertySchema(prop, ctx) {
|
|
1445
1544
|
const schema = generateTypeNode(prop.type, ctx);
|
|
1446
|
-
applyConstraints(schema, prop.constraints);
|
|
1447
|
-
applyAnnotations(schema, prop.annotations);
|
|
1545
|
+
applyConstraints(schema, prop.constraints, ctx);
|
|
1546
|
+
applyAnnotations(schema, prop.annotations, ctx);
|
|
1448
1547
|
return schema;
|
|
1449
1548
|
}
|
|
1450
1549
|
function generateUnionType(type, ctx) {
|
|
1451
1550
|
if (isBooleanUnion(type)) {
|
|
1452
1551
|
return { type: "boolean" };
|
|
1453
1552
|
}
|
|
1553
|
+
if (isNullableUnion(type)) {
|
|
1554
|
+
return {
|
|
1555
|
+
oneOf: type.members.map((m) => generateTypeNode(m, ctx))
|
|
1556
|
+
};
|
|
1557
|
+
}
|
|
1454
1558
|
return {
|
|
1455
1559
|
anyOf: type.members.map((m) => generateTypeNode(m, ctx))
|
|
1456
1560
|
};
|
|
@@ -1460,6 +1564,13 @@ function isBooleanUnion(type) {
|
|
|
1460
1564
|
const kinds = type.members.map((m) => m.kind);
|
|
1461
1565
|
return kinds.every((k) => k === "primitive") && type.members.every((m) => m.kind === "primitive" && m.primitiveKind === "boolean");
|
|
1462
1566
|
}
|
|
1567
|
+
function isNullableUnion(type) {
|
|
1568
|
+
if (type.members.length !== 2) return false;
|
|
1569
|
+
const nullCount = type.members.filter(
|
|
1570
|
+
(m) => m.kind === "primitive" && m.primitiveKind === "null"
|
|
1571
|
+
).length;
|
|
1572
|
+
return nullCount === 1;
|
|
1573
|
+
}
|
|
1463
1574
|
function generateReferenceType(type) {
|
|
1464
1575
|
return { $ref: `#/$defs/${type.name}` };
|
|
1465
1576
|
}
|
|
@@ -1480,10 +1591,7 @@ function generateDynamicType(type) {
|
|
|
1480
1591
|
"x-formspec-schemaSource": type.sourceKey
|
|
1481
1592
|
};
|
|
1482
1593
|
}
|
|
1483
|
-
function
|
|
1484
|
-
return { type: "object" };
|
|
1485
|
-
}
|
|
1486
|
-
function applyConstraints(schema, constraints) {
|
|
1594
|
+
function applyConstraints(schema, constraints, ctx) {
|
|
1487
1595
|
for (const constraint of constraints) {
|
|
1488
1596
|
switch (constraint.constraintKind) {
|
|
1489
1597
|
case "minimum":
|
|
@@ -1528,6 +1636,7 @@ function applyConstraints(schema, constraints) {
|
|
|
1528
1636
|
case "allowedMembers":
|
|
1529
1637
|
break;
|
|
1530
1638
|
case "custom":
|
|
1639
|
+
applyCustomConstraint(schema, constraint, ctx);
|
|
1531
1640
|
break;
|
|
1532
1641
|
default: {
|
|
1533
1642
|
const _exhaustive = constraint;
|
|
@@ -1536,7 +1645,7 @@ function applyConstraints(schema, constraints) {
|
|
|
1536
1645
|
}
|
|
1537
1646
|
}
|
|
1538
1647
|
}
|
|
1539
|
-
function applyAnnotations(schema, annotations) {
|
|
1648
|
+
function applyAnnotations(schema, annotations, ctx) {
|
|
1540
1649
|
for (const annotation of annotations) {
|
|
1541
1650
|
switch (annotation.annotationKind) {
|
|
1542
1651
|
case "displayName":
|
|
@@ -1556,6 +1665,7 @@ function applyAnnotations(schema, annotations) {
|
|
|
1556
1665
|
case "formatHint":
|
|
1557
1666
|
break;
|
|
1558
1667
|
case "custom":
|
|
1668
|
+
applyCustomAnnotation(schema, annotation, ctx);
|
|
1559
1669
|
break;
|
|
1560
1670
|
default: {
|
|
1561
1671
|
const _exhaustive = annotation;
|
|
@@ -1564,6 +1674,36 @@ function applyAnnotations(schema, annotations) {
|
|
|
1564
1674
|
}
|
|
1565
1675
|
}
|
|
1566
1676
|
}
|
|
1677
|
+
function generateCustomType(type, ctx) {
|
|
1678
|
+
const registration = ctx.extensionRegistry?.findType(type.typeId);
|
|
1679
|
+
if (registration === void 0) {
|
|
1680
|
+
throw new Error(
|
|
1681
|
+
`Cannot generate JSON Schema for custom type "${type.typeId}" without a matching extension registration`
|
|
1682
|
+
);
|
|
1683
|
+
}
|
|
1684
|
+
return registration.toJsonSchema(type.payload, ctx.vendorPrefix);
|
|
1685
|
+
}
|
|
1686
|
+
function applyCustomConstraint(schema, constraint, ctx) {
|
|
1687
|
+
const registration = ctx.extensionRegistry?.findConstraint(constraint.constraintId);
|
|
1688
|
+
if (registration === void 0) {
|
|
1689
|
+
throw new Error(
|
|
1690
|
+
`Cannot generate JSON Schema for custom constraint "${constraint.constraintId}" without a matching extension registration`
|
|
1691
|
+
);
|
|
1692
|
+
}
|
|
1693
|
+
Object.assign(schema, registration.toJsonSchema(constraint.payload, ctx.vendorPrefix));
|
|
1694
|
+
}
|
|
1695
|
+
function applyCustomAnnotation(schema, annotation, ctx) {
|
|
1696
|
+
const registration = ctx.extensionRegistry?.findAnnotation(annotation.annotationId);
|
|
1697
|
+
if (registration === void 0) {
|
|
1698
|
+
throw new Error(
|
|
1699
|
+
`Cannot generate JSON Schema for custom annotation "${annotation.annotationId}" without a matching extension registration`
|
|
1700
|
+
);
|
|
1701
|
+
}
|
|
1702
|
+
if (registration.toJsonSchema === void 0) {
|
|
1703
|
+
return;
|
|
1704
|
+
}
|
|
1705
|
+
Object.assign(schema, registration.toJsonSchema(annotation.value, ctx.vendorPrefix));
|
|
1706
|
+
}
|
|
1567
1707
|
|
|
1568
1708
|
// src/ui-schema/schema.ts
|
|
1569
1709
|
var import_zod = require("zod");
|
|
@@ -1788,12 +1928,9 @@ function generateClassSchemas(analysis, source) {
|
|
|
1788
1928
|
}
|
|
1789
1929
|
|
|
1790
1930
|
// src/validate/constraint-validator.ts
|
|
1791
|
-
function makeCode(ctx, category, number) {
|
|
1792
|
-
return `${ctx.vendorPrefix}-${category}-${String(number).padStart(3, "0")}`;
|
|
1793
|
-
}
|
|
1794
1931
|
function addContradiction(ctx, message, primary, related) {
|
|
1795
1932
|
ctx.diagnostics.push({
|
|
1796
|
-
code:
|
|
1933
|
+
code: "CONTRADICTING_CONSTRAINTS",
|
|
1797
1934
|
message,
|
|
1798
1935
|
severity: "error",
|
|
1799
1936
|
primaryLocation: primary,
|
|
@@ -1802,7 +1939,7 @@ function addContradiction(ctx, message, primary, related) {
|
|
|
1802
1939
|
}
|
|
1803
1940
|
function addTypeMismatch(ctx, message, primary) {
|
|
1804
1941
|
ctx.diagnostics.push({
|
|
1805
|
-
code:
|
|
1942
|
+
code: "TYPE_MISMATCH",
|
|
1806
1943
|
message,
|
|
1807
1944
|
severity: "error",
|
|
1808
1945
|
primaryLocation: primary,
|
|
@@ -1811,13 +1948,22 @@ function addTypeMismatch(ctx, message, primary) {
|
|
|
1811
1948
|
}
|
|
1812
1949
|
function addUnknownExtension(ctx, message, primary) {
|
|
1813
1950
|
ctx.diagnostics.push({
|
|
1814
|
-
code:
|
|
1951
|
+
code: "UNKNOWN_EXTENSION",
|
|
1815
1952
|
message,
|
|
1816
1953
|
severity: "warning",
|
|
1817
1954
|
primaryLocation: primary,
|
|
1818
1955
|
relatedLocations: []
|
|
1819
1956
|
});
|
|
1820
1957
|
}
|
|
1958
|
+
function addConstraintBroadening(ctx, message, primary, related) {
|
|
1959
|
+
ctx.diagnostics.push({
|
|
1960
|
+
code: "CONSTRAINT_BROADENING",
|
|
1961
|
+
message,
|
|
1962
|
+
severity: "error",
|
|
1963
|
+
primaryLocation: primary,
|
|
1964
|
+
relatedLocations: [related]
|
|
1965
|
+
});
|
|
1966
|
+
}
|
|
1821
1967
|
function findNumeric(constraints, constraintKind) {
|
|
1822
1968
|
return constraints.find((c) => c.constraintKind === constraintKind);
|
|
1823
1969
|
}
|
|
@@ -1829,6 +1975,126 @@ function findAllowedMembers(constraints) {
|
|
|
1829
1975
|
(c) => c.constraintKind === "allowedMembers"
|
|
1830
1976
|
);
|
|
1831
1977
|
}
|
|
1978
|
+
function isOrderedBoundConstraint(constraint) {
|
|
1979
|
+
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
|
+
}
|
|
1981
|
+
function pathKey(constraint) {
|
|
1982
|
+
return constraint.path?.segments.join(".") ?? "";
|
|
1983
|
+
}
|
|
1984
|
+
function orderedBoundFamily(kind) {
|
|
1985
|
+
switch (kind) {
|
|
1986
|
+
case "minimum":
|
|
1987
|
+
case "exclusiveMinimum":
|
|
1988
|
+
return "numeric-lower";
|
|
1989
|
+
case "maximum":
|
|
1990
|
+
case "exclusiveMaximum":
|
|
1991
|
+
return "numeric-upper";
|
|
1992
|
+
case "minLength":
|
|
1993
|
+
return "minLength";
|
|
1994
|
+
case "minItems":
|
|
1995
|
+
return "minItems";
|
|
1996
|
+
case "maxLength":
|
|
1997
|
+
return "maxLength";
|
|
1998
|
+
case "maxItems":
|
|
1999
|
+
return "maxItems";
|
|
2000
|
+
default: {
|
|
2001
|
+
const _exhaustive = kind;
|
|
2002
|
+
return _exhaustive;
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
function isNumericLowerKind(kind) {
|
|
2007
|
+
return kind === "minimum" || kind === "exclusiveMinimum";
|
|
2008
|
+
}
|
|
2009
|
+
function isNumericUpperKind(kind) {
|
|
2010
|
+
return kind === "maximum" || kind === "exclusiveMaximum";
|
|
2011
|
+
}
|
|
2012
|
+
function describeConstraintTag(constraint) {
|
|
2013
|
+
return `@${constraint.constraintKind}`;
|
|
2014
|
+
}
|
|
2015
|
+
function compareConstraintStrength(current, previous) {
|
|
2016
|
+
const family = orderedBoundFamily(current.constraintKind);
|
|
2017
|
+
if (family === "numeric-lower") {
|
|
2018
|
+
if (!isNumericLowerKind(current.constraintKind) || !isNumericLowerKind(previous.constraintKind)) {
|
|
2019
|
+
throw new Error("numeric-lower family received non-numeric lower-bound constraint");
|
|
2020
|
+
}
|
|
2021
|
+
if (current.value !== previous.value) {
|
|
2022
|
+
return current.value > previous.value ? 1 : -1;
|
|
2023
|
+
}
|
|
2024
|
+
if (current.constraintKind === "exclusiveMinimum" && previous.constraintKind === "minimum") {
|
|
2025
|
+
return 1;
|
|
2026
|
+
}
|
|
2027
|
+
if (current.constraintKind === "minimum" && previous.constraintKind === "exclusiveMinimum") {
|
|
2028
|
+
return -1;
|
|
2029
|
+
}
|
|
2030
|
+
return 0;
|
|
2031
|
+
}
|
|
2032
|
+
if (family === "numeric-upper") {
|
|
2033
|
+
if (!isNumericUpperKind(current.constraintKind) || !isNumericUpperKind(previous.constraintKind)) {
|
|
2034
|
+
throw new Error("numeric-upper family received non-numeric upper-bound constraint");
|
|
2035
|
+
}
|
|
2036
|
+
if (current.value !== previous.value) {
|
|
2037
|
+
return current.value < previous.value ? 1 : -1;
|
|
2038
|
+
}
|
|
2039
|
+
if (current.constraintKind === "exclusiveMaximum" && previous.constraintKind === "maximum") {
|
|
2040
|
+
return 1;
|
|
2041
|
+
}
|
|
2042
|
+
if (current.constraintKind === "maximum" && previous.constraintKind === "exclusiveMaximum") {
|
|
2043
|
+
return -1;
|
|
2044
|
+
}
|
|
2045
|
+
return 0;
|
|
2046
|
+
}
|
|
2047
|
+
switch (family) {
|
|
2048
|
+
case "minLength":
|
|
2049
|
+
case "minItems":
|
|
2050
|
+
if (current.value === previous.value) {
|
|
2051
|
+
return 0;
|
|
2052
|
+
}
|
|
2053
|
+
return current.value > previous.value ? 1 : -1;
|
|
2054
|
+
case "maxLength":
|
|
2055
|
+
case "maxItems":
|
|
2056
|
+
if (current.value === previous.value) {
|
|
2057
|
+
return 0;
|
|
2058
|
+
}
|
|
2059
|
+
return current.value < previous.value ? 1 : -1;
|
|
2060
|
+
default: {
|
|
2061
|
+
const _exhaustive = family;
|
|
2062
|
+
return _exhaustive;
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
function checkConstraintBroadening(ctx, fieldName, constraints) {
|
|
2067
|
+
const strongestByKey = /* @__PURE__ */ new Map();
|
|
2068
|
+
for (const constraint of constraints) {
|
|
2069
|
+
if (!isOrderedBoundConstraint(constraint)) {
|
|
2070
|
+
continue;
|
|
2071
|
+
}
|
|
2072
|
+
const key = `${orderedBoundFamily(constraint.constraintKind)}:${pathKey(constraint)}`;
|
|
2073
|
+
const previous = strongestByKey.get(key);
|
|
2074
|
+
if (previous === void 0) {
|
|
2075
|
+
strongestByKey.set(key, constraint);
|
|
2076
|
+
continue;
|
|
2077
|
+
}
|
|
2078
|
+
const strength = compareConstraintStrength(constraint, previous);
|
|
2079
|
+
if (strength < 0) {
|
|
2080
|
+
const displayFieldName = formatPathTargetFieldName(
|
|
2081
|
+
fieldName,
|
|
2082
|
+
constraint.path?.segments ?? []
|
|
2083
|
+
);
|
|
2084
|
+
addConstraintBroadening(
|
|
2085
|
+
ctx,
|
|
2086
|
+
`Field "${displayFieldName}": ${describeConstraintTag(constraint)} (${String(constraint.value)}) is broader than earlier ${describeConstraintTag(previous)} (${String(previous.value)}). Constraints can only narrow.`,
|
|
2087
|
+
constraint.provenance,
|
|
2088
|
+
previous.provenance
|
|
2089
|
+
);
|
|
2090
|
+
continue;
|
|
2091
|
+
}
|
|
2092
|
+
if (strength <= 0) {
|
|
2093
|
+
continue;
|
|
2094
|
+
}
|
|
2095
|
+
strongestByKey.set(key, constraint);
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
1832
2098
|
function checkNumericContradictions(ctx, fieldName, constraints) {
|
|
1833
2099
|
const min = findNumeric(constraints, "minimum");
|
|
1834
2100
|
const max = findNumeric(constraints, "maximum");
|
|
@@ -1925,6 +2191,8 @@ function typeLabel(type) {
|
|
|
1925
2191
|
return "array";
|
|
1926
2192
|
case "object":
|
|
1927
2193
|
return "object";
|
|
2194
|
+
case "record":
|
|
2195
|
+
return "record";
|
|
1928
2196
|
case "union":
|
|
1929
2197
|
return "union";
|
|
1930
2198
|
case "reference":
|
|
@@ -1939,85 +2207,140 @@ function typeLabel(type) {
|
|
|
1939
2207
|
}
|
|
1940
2208
|
}
|
|
1941
2209
|
}
|
|
1942
|
-
function
|
|
1943
|
-
|
|
1944
|
-
const
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
2210
|
+
function dereferenceType(ctx, type) {
|
|
2211
|
+
let current = type;
|
|
2212
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2213
|
+
while (current.kind === "reference") {
|
|
2214
|
+
if (seen.has(current.name)) {
|
|
2215
|
+
return current;
|
|
2216
|
+
}
|
|
2217
|
+
seen.add(current.name);
|
|
2218
|
+
const definition = ctx.typeRegistry[current.name];
|
|
2219
|
+
if (definition === void 0) {
|
|
2220
|
+
return current;
|
|
2221
|
+
}
|
|
2222
|
+
current = definition.type;
|
|
2223
|
+
}
|
|
2224
|
+
return current;
|
|
2225
|
+
}
|
|
2226
|
+
function resolvePathTargetType(ctx, type, segments) {
|
|
2227
|
+
const effectiveType = dereferenceType(ctx, type);
|
|
2228
|
+
if (segments.length === 0) {
|
|
2229
|
+
return { kind: "resolved", type: effectiveType };
|
|
2230
|
+
}
|
|
2231
|
+
if (effectiveType.kind === "array") {
|
|
2232
|
+
return resolvePathTargetType(ctx, effectiveType.items, segments);
|
|
2233
|
+
}
|
|
2234
|
+
if (effectiveType.kind === "object") {
|
|
2235
|
+
const [segment, ...rest] = segments;
|
|
2236
|
+
if (segment === void 0) {
|
|
2237
|
+
throw new Error("Invariant violation: object path traversal requires a segment");
|
|
2238
|
+
}
|
|
2239
|
+
const property = effectiveType.properties.find((prop) => prop.name === segment);
|
|
2240
|
+
if (property === void 0) {
|
|
2241
|
+
return { kind: "missing-property", segment };
|
|
2242
|
+
}
|
|
2243
|
+
return resolvePathTargetType(ctx, property.type, rest);
|
|
2244
|
+
}
|
|
2245
|
+
return { kind: "unresolvable", type: effectiveType };
|
|
2246
|
+
}
|
|
2247
|
+
function formatPathTargetFieldName(fieldName, path2) {
|
|
2248
|
+
return path2.length === 0 ? fieldName : `${fieldName}.${path2.join(".")}`;
|
|
2249
|
+
}
|
|
2250
|
+
function checkConstraintOnType(ctx, fieldName, type, constraint) {
|
|
2251
|
+
const effectiveType = dereferenceType(ctx, type);
|
|
2252
|
+
const isNumber = effectiveType.kind === "primitive" && effectiveType.primitiveKind === "number";
|
|
2253
|
+
const isString = effectiveType.kind === "primitive" && effectiveType.primitiveKind === "string";
|
|
2254
|
+
const isArray = effectiveType.kind === "array";
|
|
2255
|
+
const isEnum = effectiveType.kind === "enum";
|
|
2256
|
+
const label = typeLabel(effectiveType);
|
|
2257
|
+
const ck = constraint.constraintKind;
|
|
2258
|
+
switch (ck) {
|
|
2259
|
+
case "minimum":
|
|
2260
|
+
case "maximum":
|
|
2261
|
+
case "exclusiveMinimum":
|
|
2262
|
+
case "exclusiveMaximum":
|
|
2263
|
+
case "multipleOf": {
|
|
2264
|
+
if (!isNumber) {
|
|
1952
2265
|
addTypeMismatch(
|
|
1953
2266
|
ctx,
|
|
1954
|
-
`Field "${fieldName}":
|
|
2267
|
+
`Field "${fieldName}": constraint "${ck}" is only valid on number fields, but field type is "${label}"`,
|
|
1955
2268
|
constraint.provenance
|
|
1956
2269
|
);
|
|
1957
2270
|
}
|
|
1958
|
-
|
|
2271
|
+
break;
|
|
1959
2272
|
}
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
ctx,
|
|
1970
|
-
`Field "${fieldName}": constraint "${ck}" is only valid on number fields, but field type is "${label}"`,
|
|
1971
|
-
constraint.provenance
|
|
1972
|
-
);
|
|
1973
|
-
}
|
|
1974
|
-
break;
|
|
1975
|
-
}
|
|
1976
|
-
case "minLength":
|
|
1977
|
-
case "maxLength":
|
|
1978
|
-
case "pattern": {
|
|
1979
|
-
if (!isString) {
|
|
1980
|
-
addTypeMismatch(
|
|
1981
|
-
ctx,
|
|
1982
|
-
`Field "${fieldName}": constraint "${ck}" is only valid on string fields, but field type is "${label}"`,
|
|
1983
|
-
constraint.provenance
|
|
1984
|
-
);
|
|
1985
|
-
}
|
|
1986
|
-
break;
|
|
2273
|
+
case "minLength":
|
|
2274
|
+
case "maxLength":
|
|
2275
|
+
case "pattern": {
|
|
2276
|
+
if (!isString) {
|
|
2277
|
+
addTypeMismatch(
|
|
2278
|
+
ctx,
|
|
2279
|
+
`Field "${fieldName}": constraint "${ck}" is only valid on string fields, but field type is "${label}"`,
|
|
2280
|
+
constraint.provenance
|
|
2281
|
+
);
|
|
1987
2282
|
}
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
2283
|
+
break;
|
|
2284
|
+
}
|
|
2285
|
+
case "minItems":
|
|
2286
|
+
case "maxItems":
|
|
2287
|
+
case "uniqueItems": {
|
|
2288
|
+
if (!isArray) {
|
|
2289
|
+
addTypeMismatch(
|
|
2290
|
+
ctx,
|
|
2291
|
+
`Field "${fieldName}": constraint "${ck}" is only valid on array fields, but field type is "${label}"`,
|
|
2292
|
+
constraint.provenance
|
|
2293
|
+
);
|
|
1999
2294
|
}
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2295
|
+
break;
|
|
2296
|
+
}
|
|
2297
|
+
case "allowedMembers": {
|
|
2298
|
+
if (!isEnum) {
|
|
2299
|
+
addTypeMismatch(
|
|
2300
|
+
ctx,
|
|
2301
|
+
`Field "${fieldName}": constraint "allowedMembers" is only valid on enum fields, but field type is "${label}"`,
|
|
2302
|
+
constraint.provenance
|
|
2303
|
+
);
|
|
2009
2304
|
}
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2305
|
+
break;
|
|
2306
|
+
}
|
|
2307
|
+
case "custom": {
|
|
2308
|
+
checkCustomConstraint(ctx, fieldName, effectiveType, constraint);
|
|
2309
|
+
break;
|
|
2310
|
+
}
|
|
2311
|
+
default: {
|
|
2312
|
+
const _exhaustive = constraint;
|
|
2313
|
+
throw new Error(
|
|
2314
|
+
`Unhandled constraint kind: ${_exhaustive.constraintKind}`
|
|
2315
|
+
);
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
function checkTypeApplicability(ctx, fieldName, type, constraints) {
|
|
2320
|
+
for (const constraint of constraints) {
|
|
2321
|
+
if (constraint.path) {
|
|
2322
|
+
const resolution = resolvePathTargetType(ctx, type, constraint.path.segments);
|
|
2323
|
+
const targetFieldName = formatPathTargetFieldName(fieldName, constraint.path.segments);
|
|
2324
|
+
if (resolution.kind === "missing-property") {
|
|
2325
|
+
addTypeMismatch(
|
|
2326
|
+
ctx,
|
|
2327
|
+
`Field "${fieldName}": path-targeted constraint "${constraint.constraintKind}" references unknown path segment "${resolution.segment}"`,
|
|
2328
|
+
constraint.provenance
|
|
2329
|
+
);
|
|
2330
|
+
continue;
|
|
2013
2331
|
}
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
`
|
|
2332
|
+
if (resolution.kind === "unresolvable") {
|
|
2333
|
+
addTypeMismatch(
|
|
2334
|
+
ctx,
|
|
2335
|
+
`Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" is invalid because type "${typeLabel(resolution.type)}" cannot be traversed`,
|
|
2336
|
+
constraint.provenance
|
|
2018
2337
|
);
|
|
2338
|
+
continue;
|
|
2019
2339
|
}
|
|
2340
|
+
checkConstraintOnType(ctx, targetFieldName, resolution.type, constraint);
|
|
2341
|
+
continue;
|
|
2020
2342
|
}
|
|
2343
|
+
checkConstraintOnType(ctx, fieldName, type, constraint);
|
|
2021
2344
|
}
|
|
2022
2345
|
}
|
|
2023
2346
|
function checkCustomConstraint(ctx, fieldName, type, constraint) {
|
|
@@ -2061,6 +2384,7 @@ function validateConstraints(ctx, name, type, constraints) {
|
|
|
2061
2384
|
checkNumericContradictions(ctx, name, constraints);
|
|
2062
2385
|
checkLengthContradictions(ctx, name, constraints);
|
|
2063
2386
|
checkAllowedMembersContradiction(ctx, name, constraints);
|
|
2387
|
+
checkConstraintBroadening(ctx, name, constraints);
|
|
2064
2388
|
checkTypeApplicability(ctx, name, type, constraints);
|
|
2065
2389
|
}
|
|
2066
2390
|
function validateElement(ctx, element) {
|
|
@@ -2087,8 +2411,8 @@ function validateElement(ctx, element) {
|
|
|
2087
2411
|
function validateIR(ir, options) {
|
|
2088
2412
|
const ctx = {
|
|
2089
2413
|
diagnostics: [],
|
|
2090
|
-
|
|
2091
|
-
|
|
2414
|
+
extensionRegistry: options?.extensionRegistry,
|
|
2415
|
+
typeRegistry: ir.typeRegistry
|
|
2092
2416
|
};
|
|
2093
2417
|
for (const element of ir.elements) {
|
|
2094
2418
|
validateElement(ctx, element);
|
|
@@ -2142,7 +2466,7 @@ function createExtensionRegistry(extensions) {
|
|
|
2142
2466
|
}
|
|
2143
2467
|
|
|
2144
2468
|
// src/generators/method-schema.ts
|
|
2145
|
-
var
|
|
2469
|
+
var import_core4 = require("@formspec/core");
|
|
2146
2470
|
function typeToJsonSchema(type, checker) {
|
|
2147
2471
|
const typeRegistry = {};
|
|
2148
2472
|
const visiting = /* @__PURE__ */ new Set();
|
|
@@ -2150,7 +2474,7 @@ function typeToJsonSchema(type, checker) {
|
|
|
2150
2474
|
const fieldProvenance = { surface: "tsdoc", file: "", line: 0, column: 0 };
|
|
2151
2475
|
const ir = {
|
|
2152
2476
|
kind: "form-ir",
|
|
2153
|
-
irVersion:
|
|
2477
|
+
irVersion: import_core4.IR_VERSION,
|
|
2154
2478
|
elements: [
|
|
2155
2479
|
{
|
|
2156
2480
|
kind: "field",
|