@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.js
CHANGED
|
@@ -177,7 +177,7 @@ function canonicalizeArrayField(field) {
|
|
|
177
177
|
const itemsType = {
|
|
178
178
|
kind: "object",
|
|
179
179
|
properties: itemProperties,
|
|
180
|
-
additionalProperties:
|
|
180
|
+
additionalProperties: true
|
|
181
181
|
};
|
|
182
182
|
const type = { kind: "array", items: itemsType };
|
|
183
183
|
const constraints = [];
|
|
@@ -212,7 +212,7 @@ function canonicalizeObjectField(field) {
|
|
|
212
212
|
const type = {
|
|
213
213
|
kind: "object",
|
|
214
214
|
properties,
|
|
215
|
-
additionalProperties:
|
|
215
|
+
additionalProperties: true
|
|
216
216
|
};
|
|
217
217
|
return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
|
|
218
218
|
}
|
|
@@ -460,11 +460,6 @@ import * as ts4 from "typescript";
|
|
|
460
460
|
|
|
461
461
|
// src/analyzer/jsdoc-constraints.ts
|
|
462
462
|
import * as ts3 from "typescript";
|
|
463
|
-
import {
|
|
464
|
-
BUILTIN_CONSTRAINT_DEFINITIONS as BUILTIN_CONSTRAINT_DEFINITIONS2,
|
|
465
|
-
isBuiltinConstraintName as isBuiltinConstraintName2,
|
|
466
|
-
normalizeConstraintTagName as normalizeConstraintTagName2
|
|
467
|
-
} from "@formspec/core";
|
|
468
463
|
|
|
469
464
|
// src/analyzer/tsdoc-parser.ts
|
|
470
465
|
import * as ts2 from "typescript";
|
|
@@ -518,6 +513,15 @@ function createFormSpecTSDocConfig() {
|
|
|
518
513
|
})
|
|
519
514
|
);
|
|
520
515
|
}
|
|
516
|
+
for (const tagName of ["displayName", "description"]) {
|
|
517
|
+
config.addTagDefinition(
|
|
518
|
+
new TSDocTagDefinition({
|
|
519
|
+
tagName: "@" + tagName,
|
|
520
|
+
syntaxKind: TSDocTagSyntaxKind.BlockTag,
|
|
521
|
+
allowMultiple: true
|
|
522
|
+
})
|
|
523
|
+
);
|
|
524
|
+
}
|
|
521
525
|
return config;
|
|
522
526
|
}
|
|
523
527
|
var sharedParser;
|
|
@@ -547,6 +551,27 @@ function parseTSDocTags(node, file = "") {
|
|
|
547
551
|
const docComment = parserContext.docComment;
|
|
548
552
|
for (const block of docComment.customBlocks) {
|
|
549
553
|
const tagName = normalizeConstraintTagName(block.blockTag.tagName.substring(1));
|
|
554
|
+
if (tagName === "displayName" || tagName === "description") {
|
|
555
|
+
const text2 = extractBlockText(block).trim();
|
|
556
|
+
if (text2 === "") continue;
|
|
557
|
+
const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
|
|
558
|
+
if (tagName === "displayName") {
|
|
559
|
+
annotations.push({
|
|
560
|
+
kind: "annotation",
|
|
561
|
+
annotationKind: "displayName",
|
|
562
|
+
value: text2,
|
|
563
|
+
provenance: provenance2
|
|
564
|
+
});
|
|
565
|
+
} else {
|
|
566
|
+
annotations.push({
|
|
567
|
+
kind: "annotation",
|
|
568
|
+
annotationKind: "description",
|
|
569
|
+
value: text2,
|
|
570
|
+
provenance: provenance2
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
550
575
|
if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
551
576
|
const text = extractBlockText(block).trim();
|
|
552
577
|
if (text === "") continue;
|
|
@@ -578,41 +603,6 @@ function parseTSDocTags(node, file = "") {
|
|
|
578
603
|
constraints.push(constraintNode);
|
|
579
604
|
}
|
|
580
605
|
}
|
|
581
|
-
let displayName;
|
|
582
|
-
let description;
|
|
583
|
-
let displayNameTag;
|
|
584
|
-
let descriptionTag;
|
|
585
|
-
for (const tag of jsDocTagsAll) {
|
|
586
|
-
const tagName = tag.tagName.text;
|
|
587
|
-
const commentText = getTagCommentText(tag);
|
|
588
|
-
if (commentText === void 0 || commentText.trim() === "") {
|
|
589
|
-
continue;
|
|
590
|
-
}
|
|
591
|
-
const trimmed = commentText.trim();
|
|
592
|
-
if (tagName === "Field_displayName") {
|
|
593
|
-
displayName = trimmed;
|
|
594
|
-
displayNameTag = tag;
|
|
595
|
-
} else if (tagName === "Field_description") {
|
|
596
|
-
description = trimmed;
|
|
597
|
-
descriptionTag = tag;
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
if (displayName !== void 0 && displayNameTag) {
|
|
601
|
-
annotations.push({
|
|
602
|
-
kind: "annotation",
|
|
603
|
-
annotationKind: "displayName",
|
|
604
|
-
value: displayName,
|
|
605
|
-
provenance: provenanceForJSDocTag(displayNameTag, file)
|
|
606
|
-
});
|
|
607
|
-
}
|
|
608
|
-
if (description !== void 0 && descriptionTag) {
|
|
609
|
-
annotations.push({
|
|
610
|
-
kind: "annotation",
|
|
611
|
-
annotationKind: "description",
|
|
612
|
-
value: description,
|
|
613
|
-
provenance: provenanceForJSDocTag(descriptionTag, file)
|
|
614
|
-
});
|
|
615
|
-
}
|
|
616
606
|
return { constraints, annotations };
|
|
617
607
|
}
|
|
618
608
|
function extractPathTarget(text) {
|
|
@@ -877,18 +867,19 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
|
|
|
877
867
|
const tsType = checker.getTypeAtLocation(prop);
|
|
878
868
|
const optional = prop.questionToken !== void 0;
|
|
879
869
|
const provenance = provenanceForNode(prop, file);
|
|
880
|
-
|
|
870
|
+
let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
|
|
881
871
|
const constraints = [];
|
|
882
872
|
if (prop.type) {
|
|
883
873
|
constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
|
|
884
874
|
}
|
|
885
875
|
constraints.push(...extractJSDocConstraintNodes(prop, file));
|
|
886
|
-
|
|
876
|
+
let annotations = [];
|
|
887
877
|
annotations.push(...extractJSDocAnnotationNodes(prop, file));
|
|
888
878
|
const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
|
|
889
879
|
if (defaultAnnotation) {
|
|
890
880
|
annotations.push(defaultAnnotation);
|
|
891
881
|
}
|
|
882
|
+
({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
|
|
892
883
|
return {
|
|
893
884
|
kind: "field",
|
|
894
885
|
name,
|
|
@@ -907,14 +898,15 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
|
|
|
907
898
|
const tsType = checker.getTypeAtLocation(prop);
|
|
908
899
|
const optional = prop.questionToken !== void 0;
|
|
909
900
|
const provenance = provenanceForNode(prop, file);
|
|
910
|
-
|
|
901
|
+
let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
|
|
911
902
|
const constraints = [];
|
|
912
903
|
if (prop.type) {
|
|
913
904
|
constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
|
|
914
905
|
}
|
|
915
906
|
constraints.push(...extractJSDocConstraintNodes(prop, file));
|
|
916
|
-
|
|
907
|
+
let annotations = [];
|
|
917
908
|
annotations.push(...extractJSDocAnnotationNodes(prop, file));
|
|
909
|
+
({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
|
|
918
910
|
return {
|
|
919
911
|
kind: "field",
|
|
920
912
|
name,
|
|
@@ -925,6 +917,68 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
|
|
|
925
917
|
provenance
|
|
926
918
|
};
|
|
927
919
|
}
|
|
920
|
+
function applyEnumMemberDisplayNames(type, annotations) {
|
|
921
|
+
if (!annotations.some(
|
|
922
|
+
(annotation) => annotation.annotationKind === "displayName" && annotation.value.trim().startsWith(":")
|
|
923
|
+
)) {
|
|
924
|
+
return { type, annotations: [...annotations] };
|
|
925
|
+
}
|
|
926
|
+
const consumed = /* @__PURE__ */ new Set();
|
|
927
|
+
const nextType = rewriteEnumDisplayNames(type, annotations, consumed);
|
|
928
|
+
if (consumed.size === 0) {
|
|
929
|
+
return { type, annotations: [...annotations] };
|
|
930
|
+
}
|
|
931
|
+
return {
|
|
932
|
+
type: nextType,
|
|
933
|
+
annotations: annotations.filter((annotation) => !consumed.has(annotation))
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
function rewriteEnumDisplayNames(type, annotations, consumed) {
|
|
937
|
+
switch (type.kind) {
|
|
938
|
+
case "enum":
|
|
939
|
+
return applyEnumMemberDisplayNamesToEnum(type, annotations, consumed);
|
|
940
|
+
case "union": {
|
|
941
|
+
return {
|
|
942
|
+
...type,
|
|
943
|
+
members: type.members.map(
|
|
944
|
+
(member) => rewriteEnumDisplayNames(member, annotations, consumed)
|
|
945
|
+
)
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
default:
|
|
949
|
+
return type;
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
function applyEnumMemberDisplayNamesToEnum(type, annotations, consumed) {
|
|
953
|
+
const displayNames = /* @__PURE__ */ new Map();
|
|
954
|
+
for (const annotation of annotations) {
|
|
955
|
+
if (annotation.annotationKind !== "displayName") continue;
|
|
956
|
+
const parsed = parseEnumMemberDisplayName(annotation.value);
|
|
957
|
+
if (!parsed) continue;
|
|
958
|
+
consumed.add(annotation);
|
|
959
|
+
const member = type.members.find((m) => String(m.value) === parsed.value);
|
|
960
|
+
if (!member) continue;
|
|
961
|
+
displayNames.set(String(member.value), parsed.label);
|
|
962
|
+
}
|
|
963
|
+
if (displayNames.size === 0) {
|
|
964
|
+
return type;
|
|
965
|
+
}
|
|
966
|
+
return {
|
|
967
|
+
...type,
|
|
968
|
+
members: type.members.map((member) => {
|
|
969
|
+
const displayName = displayNames.get(String(member.value));
|
|
970
|
+
return displayName !== void 0 ? { ...member, displayName } : member;
|
|
971
|
+
})
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
function parseEnumMemberDisplayName(value) {
|
|
975
|
+
const trimmed = value.trim();
|
|
976
|
+
const match = /^:([^\s]+)\s+([\s\S]+)$/.exec(trimmed);
|
|
977
|
+
if (!match?.[1] || !match[2]) return null;
|
|
978
|
+
const label = match[2].trim();
|
|
979
|
+
if (label === "") return null;
|
|
980
|
+
return { value: match[1], label };
|
|
981
|
+
}
|
|
928
982
|
function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
|
|
929
983
|
if (type.flags & ts4.TypeFlags.String) {
|
|
930
984
|
return { kind: "primitive", primitiveKind: "string" };
|
|
@@ -1035,7 +1089,30 @@ function resolveArrayType(type, checker, file, typeRegistry, visiting) {
|
|
|
1035
1089
|
const items = elementType ? resolveTypeNode(elementType, checker, file, typeRegistry, visiting) : { kind: "primitive", primitiveKind: "string" };
|
|
1036
1090
|
return { kind: "array", items };
|
|
1037
1091
|
}
|
|
1092
|
+
function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
|
|
1093
|
+
if (type.getProperties().length > 0) {
|
|
1094
|
+
return null;
|
|
1095
|
+
}
|
|
1096
|
+
const indexInfo = checker.getIndexInfoOfType(type, ts4.IndexKind.String);
|
|
1097
|
+
if (!indexInfo) {
|
|
1098
|
+
return null;
|
|
1099
|
+
}
|
|
1100
|
+
if (visiting.has(type)) {
|
|
1101
|
+
return null;
|
|
1102
|
+
}
|
|
1103
|
+
visiting.add(type);
|
|
1104
|
+
try {
|
|
1105
|
+
const valueType = resolveTypeNode(indexInfo.type, checker, file, typeRegistry, visiting);
|
|
1106
|
+
return { kind: "record", valueType };
|
|
1107
|
+
} finally {
|
|
1108
|
+
visiting.delete(type);
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1038
1111
|
function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
1112
|
+
const recordNode = tryResolveRecordType(type, checker, file, typeRegistry, visiting);
|
|
1113
|
+
if (recordNode) {
|
|
1114
|
+
return recordNode;
|
|
1115
|
+
}
|
|
1039
1116
|
if (visiting.has(type)) {
|
|
1040
1117
|
return { kind: "object", properties: [], additionalProperties: false };
|
|
1041
1118
|
}
|
|
@@ -1067,7 +1144,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
1067
1144
|
const objectNode = {
|
|
1068
1145
|
kind: "object",
|
|
1069
1146
|
properties,
|
|
1070
|
-
additionalProperties:
|
|
1147
|
+
additionalProperties: true
|
|
1071
1148
|
};
|
|
1072
1149
|
if (typeName) {
|
|
1073
1150
|
typeRegistry[typeName] = {
|
|
@@ -1227,11 +1304,21 @@ function detectFormSpecReference(typeNode) {
|
|
|
1227
1304
|
}
|
|
1228
1305
|
|
|
1229
1306
|
// src/json-schema/ir-generator.ts
|
|
1230
|
-
function makeContext() {
|
|
1231
|
-
|
|
1307
|
+
function makeContext(options) {
|
|
1308
|
+
const vendorPrefix = options?.vendorPrefix ?? "x-formspec";
|
|
1309
|
+
if (!vendorPrefix.startsWith("x-")) {
|
|
1310
|
+
throw new Error(
|
|
1311
|
+
`Invalid vendorPrefix "${vendorPrefix}". Extension JSON Schema keywords must start with "x-".`
|
|
1312
|
+
);
|
|
1313
|
+
}
|
|
1314
|
+
return {
|
|
1315
|
+
defs: {},
|
|
1316
|
+
extensionRegistry: options?.extensionRegistry,
|
|
1317
|
+
vendorPrefix
|
|
1318
|
+
};
|
|
1232
1319
|
}
|
|
1233
|
-
function generateJsonSchemaFromIR(ir) {
|
|
1234
|
-
const ctx = makeContext();
|
|
1320
|
+
function generateJsonSchemaFromIR(ir, options) {
|
|
1321
|
+
const ctx = makeContext(options);
|
|
1235
1322
|
for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
|
|
1236
1323
|
ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
|
|
1237
1324
|
}
|
|
@@ -1283,16 +1370,16 @@ function generateFieldSchema(field, ctx) {
|
|
|
1283
1370
|
directConstraints.push(c);
|
|
1284
1371
|
}
|
|
1285
1372
|
}
|
|
1286
|
-
applyConstraints(schema, directConstraints);
|
|
1287
|
-
applyAnnotations(schema, field.annotations);
|
|
1373
|
+
applyConstraints(schema, directConstraints, ctx);
|
|
1374
|
+
applyAnnotations(schema, field.annotations, ctx);
|
|
1288
1375
|
if (pathConstraints.length === 0) {
|
|
1289
1376
|
return schema;
|
|
1290
1377
|
}
|
|
1291
|
-
return applyPathTargetedConstraints(schema, pathConstraints);
|
|
1378
|
+
return applyPathTargetedConstraints(schema, pathConstraints, ctx);
|
|
1292
1379
|
}
|
|
1293
|
-
function applyPathTargetedConstraints(schema, pathConstraints) {
|
|
1380
|
+
function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
|
|
1294
1381
|
if (schema.type === "array" && schema.items) {
|
|
1295
|
-
schema.items = applyPathTargetedConstraints(schema.items, pathConstraints);
|
|
1382
|
+
schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx);
|
|
1296
1383
|
return schema;
|
|
1297
1384
|
}
|
|
1298
1385
|
const byTarget = /* @__PURE__ */ new Map();
|
|
@@ -1306,7 +1393,7 @@ function applyPathTargetedConstraints(schema, pathConstraints) {
|
|
|
1306
1393
|
const propertyOverrides = {};
|
|
1307
1394
|
for (const [target, constraints] of byTarget) {
|
|
1308
1395
|
const subSchema = {};
|
|
1309
|
-
applyConstraints(subSchema, constraints);
|
|
1396
|
+
applyConstraints(subSchema, constraints, ctx);
|
|
1310
1397
|
propertyOverrides[target] = subSchema;
|
|
1311
1398
|
}
|
|
1312
1399
|
if (schema.$ref) {
|
|
@@ -1350,6 +1437,8 @@ function generateTypeNode(type, ctx) {
|
|
|
1350
1437
|
return generateArrayType(type, ctx);
|
|
1351
1438
|
case "object":
|
|
1352
1439
|
return generateObjectType(type, ctx);
|
|
1440
|
+
case "record":
|
|
1441
|
+
return generateRecordType(type, ctx);
|
|
1353
1442
|
case "union":
|
|
1354
1443
|
return generateUnionType(type, ctx);
|
|
1355
1444
|
case "reference":
|
|
@@ -1357,7 +1446,7 @@ function generateTypeNode(type, ctx) {
|
|
|
1357
1446
|
case "dynamic":
|
|
1358
1447
|
return generateDynamicType(type);
|
|
1359
1448
|
case "custom":
|
|
1360
|
-
return generateCustomType(type);
|
|
1449
|
+
return generateCustomType(type, ctx);
|
|
1361
1450
|
default: {
|
|
1362
1451
|
const _exhaustive = type;
|
|
1363
1452
|
return _exhaustive;
|
|
@@ -1406,16 +1495,27 @@ function generateObjectType(type, ctx) {
|
|
|
1406
1495
|
}
|
|
1407
1496
|
return schema;
|
|
1408
1497
|
}
|
|
1498
|
+
function generateRecordType(type, ctx) {
|
|
1499
|
+
return {
|
|
1500
|
+
type: "object",
|
|
1501
|
+
additionalProperties: generateTypeNode(type.valueType, ctx)
|
|
1502
|
+
};
|
|
1503
|
+
}
|
|
1409
1504
|
function generatePropertySchema(prop, ctx) {
|
|
1410
1505
|
const schema = generateTypeNode(prop.type, ctx);
|
|
1411
|
-
applyConstraints(schema, prop.constraints);
|
|
1412
|
-
applyAnnotations(schema, prop.annotations);
|
|
1506
|
+
applyConstraints(schema, prop.constraints, ctx);
|
|
1507
|
+
applyAnnotations(schema, prop.annotations, ctx);
|
|
1413
1508
|
return schema;
|
|
1414
1509
|
}
|
|
1415
1510
|
function generateUnionType(type, ctx) {
|
|
1416
1511
|
if (isBooleanUnion(type)) {
|
|
1417
1512
|
return { type: "boolean" };
|
|
1418
1513
|
}
|
|
1514
|
+
if (isNullableUnion(type)) {
|
|
1515
|
+
return {
|
|
1516
|
+
oneOf: type.members.map((m) => generateTypeNode(m, ctx))
|
|
1517
|
+
};
|
|
1518
|
+
}
|
|
1419
1519
|
return {
|
|
1420
1520
|
anyOf: type.members.map((m) => generateTypeNode(m, ctx))
|
|
1421
1521
|
};
|
|
@@ -1425,6 +1525,13 @@ function isBooleanUnion(type) {
|
|
|
1425
1525
|
const kinds = type.members.map((m) => m.kind);
|
|
1426
1526
|
return kinds.every((k) => k === "primitive") && type.members.every((m) => m.kind === "primitive" && m.primitiveKind === "boolean");
|
|
1427
1527
|
}
|
|
1528
|
+
function isNullableUnion(type) {
|
|
1529
|
+
if (type.members.length !== 2) return false;
|
|
1530
|
+
const nullCount = type.members.filter(
|
|
1531
|
+
(m) => m.kind === "primitive" && m.primitiveKind === "null"
|
|
1532
|
+
).length;
|
|
1533
|
+
return nullCount === 1;
|
|
1534
|
+
}
|
|
1428
1535
|
function generateReferenceType(type) {
|
|
1429
1536
|
return { $ref: `#/$defs/${type.name}` };
|
|
1430
1537
|
}
|
|
@@ -1445,10 +1552,7 @@ function generateDynamicType(type) {
|
|
|
1445
1552
|
"x-formspec-schemaSource": type.sourceKey
|
|
1446
1553
|
};
|
|
1447
1554
|
}
|
|
1448
|
-
function
|
|
1449
|
-
return { type: "object" };
|
|
1450
|
-
}
|
|
1451
|
-
function applyConstraints(schema, constraints) {
|
|
1555
|
+
function applyConstraints(schema, constraints, ctx) {
|
|
1452
1556
|
for (const constraint of constraints) {
|
|
1453
1557
|
switch (constraint.constraintKind) {
|
|
1454
1558
|
case "minimum":
|
|
@@ -1493,6 +1597,7 @@ function applyConstraints(schema, constraints) {
|
|
|
1493
1597
|
case "allowedMembers":
|
|
1494
1598
|
break;
|
|
1495
1599
|
case "custom":
|
|
1600
|
+
applyCustomConstraint(schema, constraint, ctx);
|
|
1496
1601
|
break;
|
|
1497
1602
|
default: {
|
|
1498
1603
|
const _exhaustive = constraint;
|
|
@@ -1501,7 +1606,7 @@ function applyConstraints(schema, constraints) {
|
|
|
1501
1606
|
}
|
|
1502
1607
|
}
|
|
1503
1608
|
}
|
|
1504
|
-
function applyAnnotations(schema, annotations) {
|
|
1609
|
+
function applyAnnotations(schema, annotations, ctx) {
|
|
1505
1610
|
for (const annotation of annotations) {
|
|
1506
1611
|
switch (annotation.annotationKind) {
|
|
1507
1612
|
case "displayName":
|
|
@@ -1521,6 +1626,7 @@ function applyAnnotations(schema, annotations) {
|
|
|
1521
1626
|
case "formatHint":
|
|
1522
1627
|
break;
|
|
1523
1628
|
case "custom":
|
|
1629
|
+
applyCustomAnnotation(schema, annotation, ctx);
|
|
1524
1630
|
break;
|
|
1525
1631
|
default: {
|
|
1526
1632
|
const _exhaustive = annotation;
|
|
@@ -1529,6 +1635,36 @@ function applyAnnotations(schema, annotations) {
|
|
|
1529
1635
|
}
|
|
1530
1636
|
}
|
|
1531
1637
|
}
|
|
1638
|
+
function generateCustomType(type, ctx) {
|
|
1639
|
+
const registration = ctx.extensionRegistry?.findType(type.typeId);
|
|
1640
|
+
if (registration === void 0) {
|
|
1641
|
+
throw new Error(
|
|
1642
|
+
`Cannot generate JSON Schema for custom type "${type.typeId}" without a matching extension registration`
|
|
1643
|
+
);
|
|
1644
|
+
}
|
|
1645
|
+
return registration.toJsonSchema(type.payload, ctx.vendorPrefix);
|
|
1646
|
+
}
|
|
1647
|
+
function applyCustomConstraint(schema, constraint, ctx) {
|
|
1648
|
+
const registration = ctx.extensionRegistry?.findConstraint(constraint.constraintId);
|
|
1649
|
+
if (registration === void 0) {
|
|
1650
|
+
throw new Error(
|
|
1651
|
+
`Cannot generate JSON Schema for custom constraint "${constraint.constraintId}" without a matching extension registration`
|
|
1652
|
+
);
|
|
1653
|
+
}
|
|
1654
|
+
Object.assign(schema, registration.toJsonSchema(constraint.payload, ctx.vendorPrefix));
|
|
1655
|
+
}
|
|
1656
|
+
function applyCustomAnnotation(schema, annotation, ctx) {
|
|
1657
|
+
const registration = ctx.extensionRegistry?.findAnnotation(annotation.annotationId);
|
|
1658
|
+
if (registration === void 0) {
|
|
1659
|
+
throw new Error(
|
|
1660
|
+
`Cannot generate JSON Schema for custom annotation "${annotation.annotationId}" without a matching extension registration`
|
|
1661
|
+
);
|
|
1662
|
+
}
|
|
1663
|
+
if (registration.toJsonSchema === void 0) {
|
|
1664
|
+
return;
|
|
1665
|
+
}
|
|
1666
|
+
Object.assign(schema, registration.toJsonSchema(annotation.value, ctx.vendorPrefix));
|
|
1667
|
+
}
|
|
1532
1668
|
|
|
1533
1669
|
// src/ui-schema/schema.ts
|
|
1534
1670
|
import { z } from "zod";
|
|
@@ -1753,12 +1889,9 @@ function generateClassSchemas(analysis, source) {
|
|
|
1753
1889
|
}
|
|
1754
1890
|
|
|
1755
1891
|
// src/validate/constraint-validator.ts
|
|
1756
|
-
function makeCode(ctx, category, number) {
|
|
1757
|
-
return `${ctx.vendorPrefix}-${category}-${String(number).padStart(3, "0")}`;
|
|
1758
|
-
}
|
|
1759
1892
|
function addContradiction(ctx, message, primary, related) {
|
|
1760
1893
|
ctx.diagnostics.push({
|
|
1761
|
-
code:
|
|
1894
|
+
code: "CONTRADICTING_CONSTRAINTS",
|
|
1762
1895
|
message,
|
|
1763
1896
|
severity: "error",
|
|
1764
1897
|
primaryLocation: primary,
|
|
@@ -1767,7 +1900,7 @@ function addContradiction(ctx, message, primary, related) {
|
|
|
1767
1900
|
}
|
|
1768
1901
|
function addTypeMismatch(ctx, message, primary) {
|
|
1769
1902
|
ctx.diagnostics.push({
|
|
1770
|
-
code:
|
|
1903
|
+
code: "TYPE_MISMATCH",
|
|
1771
1904
|
message,
|
|
1772
1905
|
severity: "error",
|
|
1773
1906
|
primaryLocation: primary,
|
|
@@ -1776,13 +1909,22 @@ function addTypeMismatch(ctx, message, primary) {
|
|
|
1776
1909
|
}
|
|
1777
1910
|
function addUnknownExtension(ctx, message, primary) {
|
|
1778
1911
|
ctx.diagnostics.push({
|
|
1779
|
-
code:
|
|
1912
|
+
code: "UNKNOWN_EXTENSION",
|
|
1780
1913
|
message,
|
|
1781
1914
|
severity: "warning",
|
|
1782
1915
|
primaryLocation: primary,
|
|
1783
1916
|
relatedLocations: []
|
|
1784
1917
|
});
|
|
1785
1918
|
}
|
|
1919
|
+
function addConstraintBroadening(ctx, message, primary, related) {
|
|
1920
|
+
ctx.diagnostics.push({
|
|
1921
|
+
code: "CONSTRAINT_BROADENING",
|
|
1922
|
+
message,
|
|
1923
|
+
severity: "error",
|
|
1924
|
+
primaryLocation: primary,
|
|
1925
|
+
relatedLocations: [related]
|
|
1926
|
+
});
|
|
1927
|
+
}
|
|
1786
1928
|
function findNumeric(constraints, constraintKind) {
|
|
1787
1929
|
return constraints.find((c) => c.constraintKind === constraintKind);
|
|
1788
1930
|
}
|
|
@@ -1794,6 +1936,126 @@ function findAllowedMembers(constraints) {
|
|
|
1794
1936
|
(c) => c.constraintKind === "allowedMembers"
|
|
1795
1937
|
);
|
|
1796
1938
|
}
|
|
1939
|
+
function isOrderedBoundConstraint(constraint) {
|
|
1940
|
+
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
|
+
}
|
|
1942
|
+
function pathKey(constraint) {
|
|
1943
|
+
return constraint.path?.segments.join(".") ?? "";
|
|
1944
|
+
}
|
|
1945
|
+
function orderedBoundFamily(kind) {
|
|
1946
|
+
switch (kind) {
|
|
1947
|
+
case "minimum":
|
|
1948
|
+
case "exclusiveMinimum":
|
|
1949
|
+
return "numeric-lower";
|
|
1950
|
+
case "maximum":
|
|
1951
|
+
case "exclusiveMaximum":
|
|
1952
|
+
return "numeric-upper";
|
|
1953
|
+
case "minLength":
|
|
1954
|
+
return "minLength";
|
|
1955
|
+
case "minItems":
|
|
1956
|
+
return "minItems";
|
|
1957
|
+
case "maxLength":
|
|
1958
|
+
return "maxLength";
|
|
1959
|
+
case "maxItems":
|
|
1960
|
+
return "maxItems";
|
|
1961
|
+
default: {
|
|
1962
|
+
const _exhaustive = kind;
|
|
1963
|
+
return _exhaustive;
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
function isNumericLowerKind(kind) {
|
|
1968
|
+
return kind === "minimum" || kind === "exclusiveMinimum";
|
|
1969
|
+
}
|
|
1970
|
+
function isNumericUpperKind(kind) {
|
|
1971
|
+
return kind === "maximum" || kind === "exclusiveMaximum";
|
|
1972
|
+
}
|
|
1973
|
+
function describeConstraintTag(constraint) {
|
|
1974
|
+
return `@${constraint.constraintKind}`;
|
|
1975
|
+
}
|
|
1976
|
+
function compareConstraintStrength(current, previous) {
|
|
1977
|
+
const family = orderedBoundFamily(current.constraintKind);
|
|
1978
|
+
if (family === "numeric-lower") {
|
|
1979
|
+
if (!isNumericLowerKind(current.constraintKind) || !isNumericLowerKind(previous.constraintKind)) {
|
|
1980
|
+
throw new Error("numeric-lower family received non-numeric lower-bound constraint");
|
|
1981
|
+
}
|
|
1982
|
+
if (current.value !== previous.value) {
|
|
1983
|
+
return current.value > previous.value ? 1 : -1;
|
|
1984
|
+
}
|
|
1985
|
+
if (current.constraintKind === "exclusiveMinimum" && previous.constraintKind === "minimum") {
|
|
1986
|
+
return 1;
|
|
1987
|
+
}
|
|
1988
|
+
if (current.constraintKind === "minimum" && previous.constraintKind === "exclusiveMinimum") {
|
|
1989
|
+
return -1;
|
|
1990
|
+
}
|
|
1991
|
+
return 0;
|
|
1992
|
+
}
|
|
1993
|
+
if (family === "numeric-upper") {
|
|
1994
|
+
if (!isNumericUpperKind(current.constraintKind) || !isNumericUpperKind(previous.constraintKind)) {
|
|
1995
|
+
throw new Error("numeric-upper family received non-numeric upper-bound constraint");
|
|
1996
|
+
}
|
|
1997
|
+
if (current.value !== previous.value) {
|
|
1998
|
+
return current.value < previous.value ? 1 : -1;
|
|
1999
|
+
}
|
|
2000
|
+
if (current.constraintKind === "exclusiveMaximum" && previous.constraintKind === "maximum") {
|
|
2001
|
+
return 1;
|
|
2002
|
+
}
|
|
2003
|
+
if (current.constraintKind === "maximum" && previous.constraintKind === "exclusiveMaximum") {
|
|
2004
|
+
return -1;
|
|
2005
|
+
}
|
|
2006
|
+
return 0;
|
|
2007
|
+
}
|
|
2008
|
+
switch (family) {
|
|
2009
|
+
case "minLength":
|
|
2010
|
+
case "minItems":
|
|
2011
|
+
if (current.value === previous.value) {
|
|
2012
|
+
return 0;
|
|
2013
|
+
}
|
|
2014
|
+
return current.value > previous.value ? 1 : -1;
|
|
2015
|
+
case "maxLength":
|
|
2016
|
+
case "maxItems":
|
|
2017
|
+
if (current.value === previous.value) {
|
|
2018
|
+
return 0;
|
|
2019
|
+
}
|
|
2020
|
+
return current.value < previous.value ? 1 : -1;
|
|
2021
|
+
default: {
|
|
2022
|
+
const _exhaustive = family;
|
|
2023
|
+
return _exhaustive;
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
function checkConstraintBroadening(ctx, fieldName, constraints) {
|
|
2028
|
+
const strongestByKey = /* @__PURE__ */ new Map();
|
|
2029
|
+
for (const constraint of constraints) {
|
|
2030
|
+
if (!isOrderedBoundConstraint(constraint)) {
|
|
2031
|
+
continue;
|
|
2032
|
+
}
|
|
2033
|
+
const key = `${orderedBoundFamily(constraint.constraintKind)}:${pathKey(constraint)}`;
|
|
2034
|
+
const previous = strongestByKey.get(key);
|
|
2035
|
+
if (previous === void 0) {
|
|
2036
|
+
strongestByKey.set(key, constraint);
|
|
2037
|
+
continue;
|
|
2038
|
+
}
|
|
2039
|
+
const strength = compareConstraintStrength(constraint, previous);
|
|
2040
|
+
if (strength < 0) {
|
|
2041
|
+
const displayFieldName = formatPathTargetFieldName(
|
|
2042
|
+
fieldName,
|
|
2043
|
+
constraint.path?.segments ?? []
|
|
2044
|
+
);
|
|
2045
|
+
addConstraintBroadening(
|
|
2046
|
+
ctx,
|
|
2047
|
+
`Field "${displayFieldName}": ${describeConstraintTag(constraint)} (${String(constraint.value)}) is broader than earlier ${describeConstraintTag(previous)} (${String(previous.value)}). Constraints can only narrow.`,
|
|
2048
|
+
constraint.provenance,
|
|
2049
|
+
previous.provenance
|
|
2050
|
+
);
|
|
2051
|
+
continue;
|
|
2052
|
+
}
|
|
2053
|
+
if (strength <= 0) {
|
|
2054
|
+
continue;
|
|
2055
|
+
}
|
|
2056
|
+
strongestByKey.set(key, constraint);
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
1797
2059
|
function checkNumericContradictions(ctx, fieldName, constraints) {
|
|
1798
2060
|
const min = findNumeric(constraints, "minimum");
|
|
1799
2061
|
const max = findNumeric(constraints, "maximum");
|
|
@@ -1890,6 +2152,8 @@ function typeLabel(type) {
|
|
|
1890
2152
|
return "array";
|
|
1891
2153
|
case "object":
|
|
1892
2154
|
return "object";
|
|
2155
|
+
case "record":
|
|
2156
|
+
return "record";
|
|
1893
2157
|
case "union":
|
|
1894
2158
|
return "union";
|
|
1895
2159
|
case "reference":
|
|
@@ -1904,85 +2168,140 @@ function typeLabel(type) {
|
|
|
1904
2168
|
}
|
|
1905
2169
|
}
|
|
1906
2170
|
}
|
|
1907
|
-
function
|
|
1908
|
-
|
|
1909
|
-
const
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
2171
|
+
function dereferenceType(ctx, type) {
|
|
2172
|
+
let current = type;
|
|
2173
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2174
|
+
while (current.kind === "reference") {
|
|
2175
|
+
if (seen.has(current.name)) {
|
|
2176
|
+
return current;
|
|
2177
|
+
}
|
|
2178
|
+
seen.add(current.name);
|
|
2179
|
+
const definition = ctx.typeRegistry[current.name];
|
|
2180
|
+
if (definition === void 0) {
|
|
2181
|
+
return current;
|
|
2182
|
+
}
|
|
2183
|
+
current = definition.type;
|
|
2184
|
+
}
|
|
2185
|
+
return current;
|
|
2186
|
+
}
|
|
2187
|
+
function resolvePathTargetType(ctx, type, segments) {
|
|
2188
|
+
const effectiveType = dereferenceType(ctx, type);
|
|
2189
|
+
if (segments.length === 0) {
|
|
2190
|
+
return { kind: "resolved", type: effectiveType };
|
|
2191
|
+
}
|
|
2192
|
+
if (effectiveType.kind === "array") {
|
|
2193
|
+
return resolvePathTargetType(ctx, effectiveType.items, segments);
|
|
2194
|
+
}
|
|
2195
|
+
if (effectiveType.kind === "object") {
|
|
2196
|
+
const [segment, ...rest] = segments;
|
|
2197
|
+
if (segment === void 0) {
|
|
2198
|
+
throw new Error("Invariant violation: object path traversal requires a segment");
|
|
2199
|
+
}
|
|
2200
|
+
const property = effectiveType.properties.find((prop) => prop.name === segment);
|
|
2201
|
+
if (property === void 0) {
|
|
2202
|
+
return { kind: "missing-property", segment };
|
|
2203
|
+
}
|
|
2204
|
+
return resolvePathTargetType(ctx, property.type, rest);
|
|
2205
|
+
}
|
|
2206
|
+
return { kind: "unresolvable", type: effectiveType };
|
|
2207
|
+
}
|
|
2208
|
+
function formatPathTargetFieldName(fieldName, path2) {
|
|
2209
|
+
return path2.length === 0 ? fieldName : `${fieldName}.${path2.join(".")}`;
|
|
2210
|
+
}
|
|
2211
|
+
function checkConstraintOnType(ctx, fieldName, type, constraint) {
|
|
2212
|
+
const effectiveType = dereferenceType(ctx, type);
|
|
2213
|
+
const isNumber = effectiveType.kind === "primitive" && effectiveType.primitiveKind === "number";
|
|
2214
|
+
const isString = effectiveType.kind === "primitive" && effectiveType.primitiveKind === "string";
|
|
2215
|
+
const isArray = effectiveType.kind === "array";
|
|
2216
|
+
const isEnum = effectiveType.kind === "enum";
|
|
2217
|
+
const label = typeLabel(effectiveType);
|
|
2218
|
+
const ck = constraint.constraintKind;
|
|
2219
|
+
switch (ck) {
|
|
2220
|
+
case "minimum":
|
|
2221
|
+
case "maximum":
|
|
2222
|
+
case "exclusiveMinimum":
|
|
2223
|
+
case "exclusiveMaximum":
|
|
2224
|
+
case "multipleOf": {
|
|
2225
|
+
if (!isNumber) {
|
|
1917
2226
|
addTypeMismatch(
|
|
1918
2227
|
ctx,
|
|
1919
|
-
`Field "${fieldName}":
|
|
2228
|
+
`Field "${fieldName}": constraint "${ck}" is only valid on number fields, but field type is "${label}"`,
|
|
1920
2229
|
constraint.provenance
|
|
1921
2230
|
);
|
|
1922
2231
|
}
|
|
1923
|
-
|
|
2232
|
+
break;
|
|
1924
2233
|
}
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
ctx,
|
|
1935
|
-
`Field "${fieldName}": constraint "${ck}" is only valid on number fields, but field type is "${label}"`,
|
|
1936
|
-
constraint.provenance
|
|
1937
|
-
);
|
|
1938
|
-
}
|
|
1939
|
-
break;
|
|
1940
|
-
}
|
|
1941
|
-
case "minLength":
|
|
1942
|
-
case "maxLength":
|
|
1943
|
-
case "pattern": {
|
|
1944
|
-
if (!isString) {
|
|
1945
|
-
addTypeMismatch(
|
|
1946
|
-
ctx,
|
|
1947
|
-
`Field "${fieldName}": constraint "${ck}" is only valid on string fields, but field type is "${label}"`,
|
|
1948
|
-
constraint.provenance
|
|
1949
|
-
);
|
|
1950
|
-
}
|
|
1951
|
-
break;
|
|
2234
|
+
case "minLength":
|
|
2235
|
+
case "maxLength":
|
|
2236
|
+
case "pattern": {
|
|
2237
|
+
if (!isString) {
|
|
2238
|
+
addTypeMismatch(
|
|
2239
|
+
ctx,
|
|
2240
|
+
`Field "${fieldName}": constraint "${ck}" is only valid on string fields, but field type is "${label}"`,
|
|
2241
|
+
constraint.provenance
|
|
2242
|
+
);
|
|
1952
2243
|
}
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
2244
|
+
break;
|
|
2245
|
+
}
|
|
2246
|
+
case "minItems":
|
|
2247
|
+
case "maxItems":
|
|
2248
|
+
case "uniqueItems": {
|
|
2249
|
+
if (!isArray) {
|
|
2250
|
+
addTypeMismatch(
|
|
2251
|
+
ctx,
|
|
2252
|
+
`Field "${fieldName}": constraint "${ck}" is only valid on array fields, but field type is "${label}"`,
|
|
2253
|
+
constraint.provenance
|
|
2254
|
+
);
|
|
1964
2255
|
}
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
2256
|
+
break;
|
|
2257
|
+
}
|
|
2258
|
+
case "allowedMembers": {
|
|
2259
|
+
if (!isEnum) {
|
|
2260
|
+
addTypeMismatch(
|
|
2261
|
+
ctx,
|
|
2262
|
+
`Field "${fieldName}": constraint "allowedMembers" is only valid on enum fields, but field type is "${label}"`,
|
|
2263
|
+
constraint.provenance
|
|
2264
|
+
);
|
|
1974
2265
|
}
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
2266
|
+
break;
|
|
2267
|
+
}
|
|
2268
|
+
case "custom": {
|
|
2269
|
+
checkCustomConstraint(ctx, fieldName, effectiveType, constraint);
|
|
2270
|
+
break;
|
|
2271
|
+
}
|
|
2272
|
+
default: {
|
|
2273
|
+
const _exhaustive = constraint;
|
|
2274
|
+
throw new Error(
|
|
2275
|
+
`Unhandled constraint kind: ${_exhaustive.constraintKind}`
|
|
2276
|
+
);
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
function checkTypeApplicability(ctx, fieldName, type, constraints) {
|
|
2281
|
+
for (const constraint of constraints) {
|
|
2282
|
+
if (constraint.path) {
|
|
2283
|
+
const resolution = resolvePathTargetType(ctx, type, constraint.path.segments);
|
|
2284
|
+
const targetFieldName = formatPathTargetFieldName(fieldName, constraint.path.segments);
|
|
2285
|
+
if (resolution.kind === "missing-property") {
|
|
2286
|
+
addTypeMismatch(
|
|
2287
|
+
ctx,
|
|
2288
|
+
`Field "${fieldName}": path-targeted constraint "${constraint.constraintKind}" references unknown path segment "${resolution.segment}"`,
|
|
2289
|
+
constraint.provenance
|
|
2290
|
+
);
|
|
2291
|
+
continue;
|
|
1978
2292
|
}
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
`
|
|
2293
|
+
if (resolution.kind === "unresolvable") {
|
|
2294
|
+
addTypeMismatch(
|
|
2295
|
+
ctx,
|
|
2296
|
+
`Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" is invalid because type "${typeLabel(resolution.type)}" cannot be traversed`,
|
|
2297
|
+
constraint.provenance
|
|
1983
2298
|
);
|
|
2299
|
+
continue;
|
|
1984
2300
|
}
|
|
2301
|
+
checkConstraintOnType(ctx, targetFieldName, resolution.type, constraint);
|
|
2302
|
+
continue;
|
|
1985
2303
|
}
|
|
2304
|
+
checkConstraintOnType(ctx, fieldName, type, constraint);
|
|
1986
2305
|
}
|
|
1987
2306
|
}
|
|
1988
2307
|
function checkCustomConstraint(ctx, fieldName, type, constraint) {
|
|
@@ -2026,6 +2345,7 @@ function validateConstraints(ctx, name, type, constraints) {
|
|
|
2026
2345
|
checkNumericContradictions(ctx, name, constraints);
|
|
2027
2346
|
checkLengthContradictions(ctx, name, constraints);
|
|
2028
2347
|
checkAllowedMembersContradiction(ctx, name, constraints);
|
|
2348
|
+
checkConstraintBroadening(ctx, name, constraints);
|
|
2029
2349
|
checkTypeApplicability(ctx, name, type, constraints);
|
|
2030
2350
|
}
|
|
2031
2351
|
function validateElement(ctx, element) {
|
|
@@ -2052,8 +2372,8 @@ function validateElement(ctx, element) {
|
|
|
2052
2372
|
function validateIR(ir, options) {
|
|
2053
2373
|
const ctx = {
|
|
2054
2374
|
diagnostics: [],
|
|
2055
|
-
|
|
2056
|
-
|
|
2375
|
+
extensionRegistry: options?.extensionRegistry,
|
|
2376
|
+
typeRegistry: ir.typeRegistry
|
|
2057
2377
|
};
|
|
2058
2378
|
for (const element of ir.elements) {
|
|
2059
2379
|
validateElement(ctx, element);
|