@formspec/build 0.1.0-alpha.15 → 0.1.0-alpha.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/fixtures/edge-cases.d.ts +11 -0
- package/dist/__tests__/fixtures/edge-cases.d.ts.map +1 -1
- package/dist/__tests__/fixtures/example-numeric-extension.d.ts +20 -0
- package/dist/__tests__/fixtures/example-numeric-extension.d.ts.map +1 -0
- package/dist/__tests__/fixtures/mixed-authoring-shipping-address.d.ts +31 -0
- package/dist/__tests__/fixtures/mixed-authoring-shipping-address.d.ts.map +1 -0
- package/dist/__tests__/mixed-authoring.test.d.ts +2 -0
- package/dist/__tests__/mixed-authoring.test.d.ts.map +1 -0
- package/dist/__tests__/numeric-extension.integration.test.d.ts +2 -0
- package/dist/__tests__/numeric-extension.integration.test.d.ts.map +1 -0
- package/dist/__tests__/parity/utils.d.ts +5 -3
- package/dist/__tests__/parity/utils.d.ts.map +1 -1
- package/dist/analyzer/class-analyzer.d.ts +8 -5
- package/dist/analyzer/class-analyzer.d.ts.map +1 -1
- package/dist/analyzer/jsdoc-constraints.d.ts +3 -2
- package/dist/analyzer/jsdoc-constraints.d.ts.map +1 -1
- package/dist/analyzer/tsdoc-parser.d.ts +38 -4
- package/dist/analyzer/tsdoc-parser.d.ts.map +1 -1
- package/dist/browser.cjs +371 -21
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +371 -21
- package/dist/browser.js.map +1 -1
- package/dist/build.d.ts +67 -3
- package/dist/canonicalize/tsdoc-canonicalizer.d.ts.map +1 -1
- package/dist/cli.cjs +1159 -150
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1159 -150
- package/dist/cli.js.map +1 -1
- package/dist/extensions/registry.d.ts +25 -1
- package/dist/extensions/registry.d.ts.map +1 -1
- package/dist/generators/class-schema.d.ts +4 -4
- package/dist/generators/class-schema.d.ts.map +1 -1
- package/dist/generators/mixed-authoring.d.ts +45 -0
- package/dist/generators/mixed-authoring.d.ts.map +1 -0
- package/dist/index.cjs +1146 -149
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1145 -149
- package/dist/index.js.map +1 -1
- package/dist/internals.cjs +1156 -149
- package/dist/internals.cjs.map +1 -1
- package/dist/internals.js +1154 -147
- package/dist/internals.js.map +1 -1
- package/dist/json-schema/ir-generator.d.ts +3 -2
- package/dist/json-schema/ir-generator.d.ts.map +1 -1
- package/dist/ui-schema/ir-generator.d.ts.map +1 -1
- package/dist/validate/constraint-validator.d.ts.map +1 -1
- package/package.json +3 -3
- package/dist/__tests__/jsdoc-constraints.test.d.ts +0 -9
- package/dist/__tests__/jsdoc-constraints.test.d.ts.map +0 -1
package/dist/browser.cjs
CHANGED
|
@@ -380,6 +380,9 @@ function generateJsonSchemaFromIR(ir, options) {
|
|
|
380
380
|
const ctx = makeContext(options);
|
|
381
381
|
for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
|
|
382
382
|
ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
|
|
383
|
+
if (typeDef.annotations && typeDef.annotations.length > 0) {
|
|
384
|
+
applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
|
|
385
|
+
}
|
|
383
386
|
}
|
|
384
387
|
const properties = {};
|
|
385
388
|
const required = [];
|
|
@@ -391,6 +394,9 @@ function generateJsonSchemaFromIR(ir, options) {
|
|
|
391
394
|
properties,
|
|
392
395
|
...uniqueRequired.length > 0 && { required: uniqueRequired }
|
|
393
396
|
};
|
|
397
|
+
if (ir.annotations && ir.annotations.length > 0) {
|
|
398
|
+
applyAnnotations(result, ir.annotations, ctx);
|
|
399
|
+
}
|
|
394
400
|
if (Object.keys(ctx.defs).length > 0) {
|
|
395
401
|
result.$defs = ctx.defs;
|
|
396
402
|
}
|
|
@@ -420,22 +426,51 @@ function collectFields(elements, properties, required, ctx) {
|
|
|
420
426
|
}
|
|
421
427
|
function generateFieldSchema(field, ctx) {
|
|
422
428
|
const schema = generateTypeNode(field.type, ctx);
|
|
429
|
+
const itemStringSchema = schema.type === "array" && schema.items?.type === "string" ? schema.items : void 0;
|
|
423
430
|
const directConstraints = [];
|
|
431
|
+
const itemConstraints = [];
|
|
424
432
|
const pathConstraints = [];
|
|
425
433
|
for (const c of field.constraints) {
|
|
426
434
|
if (c.path) {
|
|
427
435
|
pathConstraints.push(c);
|
|
436
|
+
} else if (itemStringSchema !== void 0 && isStringItemConstraint(c)) {
|
|
437
|
+
itemConstraints.push(c);
|
|
428
438
|
} else {
|
|
429
439
|
directConstraints.push(c);
|
|
430
440
|
}
|
|
431
441
|
}
|
|
432
442
|
applyConstraints(schema, directConstraints, ctx);
|
|
433
|
-
|
|
443
|
+
if (itemStringSchema !== void 0) {
|
|
444
|
+
applyConstraints(itemStringSchema, itemConstraints, ctx);
|
|
445
|
+
}
|
|
446
|
+
const rootAnnotations = [];
|
|
447
|
+
const itemAnnotations = [];
|
|
448
|
+
for (const annotation of field.annotations) {
|
|
449
|
+
if (itemStringSchema !== void 0 && annotation.annotationKind === "format") {
|
|
450
|
+
itemAnnotations.push(annotation);
|
|
451
|
+
} else {
|
|
452
|
+
rootAnnotations.push(annotation);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
applyAnnotations(schema, rootAnnotations, ctx);
|
|
456
|
+
if (itemStringSchema !== void 0) {
|
|
457
|
+
applyAnnotations(itemStringSchema, itemAnnotations, ctx);
|
|
458
|
+
}
|
|
434
459
|
if (pathConstraints.length === 0) {
|
|
435
460
|
return schema;
|
|
436
461
|
}
|
|
437
462
|
return applyPathTargetedConstraints(schema, pathConstraints, ctx);
|
|
438
463
|
}
|
|
464
|
+
function isStringItemConstraint(constraint) {
|
|
465
|
+
switch (constraint.constraintKind) {
|
|
466
|
+
case "minLength":
|
|
467
|
+
case "maxLength":
|
|
468
|
+
case "pattern":
|
|
469
|
+
return true;
|
|
470
|
+
default:
|
|
471
|
+
return false;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
439
474
|
function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
|
|
440
475
|
if (schema.type === "array" && schema.items) {
|
|
441
476
|
schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx);
|
|
@@ -653,6 +688,9 @@ function applyConstraints(schema, constraints, ctx) {
|
|
|
653
688
|
case "uniqueItems":
|
|
654
689
|
schema.uniqueItems = constraint.value;
|
|
655
690
|
break;
|
|
691
|
+
case "const":
|
|
692
|
+
schema.const = constraint.value;
|
|
693
|
+
break;
|
|
656
694
|
case "allowedMembers":
|
|
657
695
|
break;
|
|
658
696
|
case "custom":
|
|
@@ -677,8 +715,14 @@ function applyAnnotations(schema, annotations, ctx) {
|
|
|
677
715
|
case "defaultValue":
|
|
678
716
|
schema.default = annotation.value;
|
|
679
717
|
break;
|
|
718
|
+
case "format":
|
|
719
|
+
schema.format = annotation.value;
|
|
720
|
+
break;
|
|
680
721
|
case "deprecated":
|
|
681
722
|
schema.deprecated = true;
|
|
723
|
+
if (annotation.message !== void 0 && annotation.message !== "") {
|
|
724
|
+
schema["x-formspec-deprecation-description"] = annotation.message;
|
|
725
|
+
}
|
|
682
726
|
break;
|
|
683
727
|
case "placeholder":
|
|
684
728
|
break;
|
|
@@ -710,7 +754,12 @@ function applyCustomConstraint(schema, constraint, ctx) {
|
|
|
710
754
|
`Cannot generate JSON Schema for custom constraint "${constraint.constraintId}" without a matching extension registration`
|
|
711
755
|
);
|
|
712
756
|
}
|
|
713
|
-
|
|
757
|
+
assignVendorPrefixedExtensionKeywords(
|
|
758
|
+
schema,
|
|
759
|
+
registration.toJsonSchema(constraint.payload, ctx.vendorPrefix),
|
|
760
|
+
ctx.vendorPrefix,
|
|
761
|
+
`custom constraint "${constraint.constraintId}"`
|
|
762
|
+
);
|
|
714
763
|
}
|
|
715
764
|
function applyCustomAnnotation(schema, annotation, ctx) {
|
|
716
765
|
const registration = ctx.extensionRegistry?.findAnnotation(annotation.annotationId);
|
|
@@ -722,7 +771,22 @@ function applyCustomAnnotation(schema, annotation, ctx) {
|
|
|
722
771
|
if (registration.toJsonSchema === void 0) {
|
|
723
772
|
return;
|
|
724
773
|
}
|
|
725
|
-
|
|
774
|
+
assignVendorPrefixedExtensionKeywords(
|
|
775
|
+
schema,
|
|
776
|
+
registration.toJsonSchema(annotation.value, ctx.vendorPrefix),
|
|
777
|
+
ctx.vendorPrefix,
|
|
778
|
+
`custom annotation "${annotation.annotationId}"`
|
|
779
|
+
);
|
|
780
|
+
}
|
|
781
|
+
function assignVendorPrefixedExtensionKeywords(schema, extensionSchema, vendorPrefix, source) {
|
|
782
|
+
for (const [key, value] of Object.entries(extensionSchema)) {
|
|
783
|
+
if (!key.startsWith(`${vendorPrefix}-`)) {
|
|
784
|
+
throw new Error(
|
|
785
|
+
`Cannot apply ${source}: extension hooks may only emit "${vendorPrefix}-*" JSON Schema keywords`
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
schema[key] = value;
|
|
789
|
+
}
|
|
726
790
|
}
|
|
727
791
|
|
|
728
792
|
// src/json-schema/generator.ts
|
|
@@ -866,25 +930,31 @@ function createShowRule(fieldName, value) {
|
|
|
866
930
|
}
|
|
867
931
|
};
|
|
868
932
|
}
|
|
933
|
+
function flattenConditionSchema(scope, schema) {
|
|
934
|
+
if (schema.allOf === void 0) {
|
|
935
|
+
if (scope === "#") {
|
|
936
|
+
return [schema];
|
|
937
|
+
}
|
|
938
|
+
const fieldName = scope.replace("#/properties/", "");
|
|
939
|
+
return [
|
|
940
|
+
{
|
|
941
|
+
properties: {
|
|
942
|
+
[fieldName]: schema
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
];
|
|
946
|
+
}
|
|
947
|
+
return schema.allOf.flatMap((member) => flattenConditionSchema(scope, member));
|
|
948
|
+
}
|
|
869
949
|
function combineRules(parentRule, childRule) {
|
|
870
|
-
const parentCondition = parentRule.condition;
|
|
871
|
-
const childCondition = childRule.condition;
|
|
872
950
|
return {
|
|
873
951
|
effect: "SHOW",
|
|
874
952
|
condition: {
|
|
875
953
|
scope: "#",
|
|
876
954
|
schema: {
|
|
877
955
|
allOf: [
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
[parentCondition.scope.replace("#/properties/", "")]: parentCondition.schema
|
|
881
|
-
}
|
|
882
|
-
},
|
|
883
|
-
{
|
|
884
|
-
properties: {
|
|
885
|
-
[childCondition.scope.replace("#/properties/", "")]: childCondition.schema
|
|
886
|
-
}
|
|
887
|
-
}
|
|
956
|
+
...flattenConditionSchema(parentRule.condition.scope, parentRule.condition.schema),
|
|
957
|
+
...flattenConditionSchema(childRule.condition.scope, childRule.condition.schema)
|
|
888
958
|
]
|
|
889
959
|
}
|
|
890
960
|
}
|
|
@@ -892,10 +962,14 @@ function combineRules(parentRule, childRule) {
|
|
|
892
962
|
}
|
|
893
963
|
function fieldNodeToControl(field, parentRule) {
|
|
894
964
|
const displayNameAnnotation = field.annotations.find((a) => a.annotationKind === "displayName");
|
|
965
|
+
const placeholderAnnotation = field.annotations.find((a) => a.annotationKind === "placeholder");
|
|
895
966
|
const control = {
|
|
896
967
|
type: "Control",
|
|
897
968
|
scope: fieldToScope(field.name),
|
|
898
969
|
...displayNameAnnotation !== void 0 && { label: displayNameAnnotation.value },
|
|
970
|
+
...placeholderAnnotation !== void 0 && {
|
|
971
|
+
options: { placeholder: placeholderAnnotation.value }
|
|
972
|
+
},
|
|
899
973
|
...parentRule !== void 0 && { rule: parentRule }
|
|
900
974
|
};
|
|
901
975
|
return control;
|
|
@@ -961,7 +1035,10 @@ function getSchemaExtension(schema, key) {
|
|
|
961
1035
|
// src/extensions/registry.ts
|
|
962
1036
|
function createExtensionRegistry(extensions) {
|
|
963
1037
|
const typeMap = /* @__PURE__ */ new Map();
|
|
1038
|
+
const typeNameMap = /* @__PURE__ */ new Map();
|
|
964
1039
|
const constraintMap = /* @__PURE__ */ new Map();
|
|
1040
|
+
const constraintTagMap = /* @__PURE__ */ new Map();
|
|
1041
|
+
const builtinBroadeningMap = /* @__PURE__ */ new Map();
|
|
965
1042
|
const annotationMap = /* @__PURE__ */ new Map();
|
|
966
1043
|
for (const ext of extensions) {
|
|
967
1044
|
if (ext.types !== void 0) {
|
|
@@ -971,6 +1048,27 @@ function createExtensionRegistry(extensions) {
|
|
|
971
1048
|
throw new Error(`Duplicate custom type ID: "${qualifiedId}"`);
|
|
972
1049
|
}
|
|
973
1050
|
typeMap.set(qualifiedId, type);
|
|
1051
|
+
for (const sourceTypeName of type.tsTypeNames ?? [type.typeName]) {
|
|
1052
|
+
if (typeNameMap.has(sourceTypeName)) {
|
|
1053
|
+
throw new Error(`Duplicate custom type source name: "${sourceTypeName}"`);
|
|
1054
|
+
}
|
|
1055
|
+
typeNameMap.set(sourceTypeName, {
|
|
1056
|
+
extensionId: ext.extensionId,
|
|
1057
|
+
registration: type
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1060
|
+
if (type.builtinConstraintBroadenings !== void 0) {
|
|
1061
|
+
for (const broadening of type.builtinConstraintBroadenings) {
|
|
1062
|
+
const key = `${qualifiedId}:${broadening.tagName}`;
|
|
1063
|
+
if (builtinBroadeningMap.has(key)) {
|
|
1064
|
+
throw new Error(`Duplicate built-in constraint broadening: "${key}"`);
|
|
1065
|
+
}
|
|
1066
|
+
builtinBroadeningMap.set(key, {
|
|
1067
|
+
extensionId: ext.extensionId,
|
|
1068
|
+
registration: broadening
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
974
1072
|
}
|
|
975
1073
|
}
|
|
976
1074
|
if (ext.constraints !== void 0) {
|
|
@@ -982,6 +1080,17 @@ function createExtensionRegistry(extensions) {
|
|
|
982
1080
|
constraintMap.set(qualifiedId, constraint);
|
|
983
1081
|
}
|
|
984
1082
|
}
|
|
1083
|
+
if (ext.constraintTags !== void 0) {
|
|
1084
|
+
for (const tag of ext.constraintTags) {
|
|
1085
|
+
if (constraintTagMap.has(tag.tagName)) {
|
|
1086
|
+
throw new Error(`Duplicate custom constraint tag: "@${tag.tagName}"`);
|
|
1087
|
+
}
|
|
1088
|
+
constraintTagMap.set(tag.tagName, {
|
|
1089
|
+
extensionId: ext.extensionId,
|
|
1090
|
+
registration: tag
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
985
1094
|
if (ext.annotations !== void 0) {
|
|
986
1095
|
for (const annotation of ext.annotations) {
|
|
987
1096
|
const qualifiedId = `${ext.extensionId}/${annotation.annotationName}`;
|
|
@@ -995,7 +1104,10 @@ function createExtensionRegistry(extensions) {
|
|
|
995
1104
|
return {
|
|
996
1105
|
extensions,
|
|
997
1106
|
findType: (typeId) => typeMap.get(typeId),
|
|
1107
|
+
findTypeByName: (typeName) => typeNameMap.get(typeName),
|
|
998
1108
|
findConstraint: (constraintId) => constraintMap.get(constraintId),
|
|
1109
|
+
findConstraintTag: (tagName) => constraintTagMap.get(tagName),
|
|
1110
|
+
findBuiltinConstraintBroadening: (typeId, tagName) => builtinBroadeningMap.get(`${typeId}:${tagName}`),
|
|
999
1111
|
findAnnotation: (annotationId) => annotationMap.get(annotationId)
|
|
1000
1112
|
};
|
|
1001
1113
|
}
|
|
@@ -1063,6 +1175,7 @@ var jsonSchema7Schema = import_zod3.z.lazy(
|
|
|
1063
1175
|
);
|
|
1064
1176
|
|
|
1065
1177
|
// src/validate/constraint-validator.ts
|
|
1178
|
+
var import_core3 = require("@formspec/core");
|
|
1066
1179
|
function addContradiction(ctx, message, primary, related) {
|
|
1067
1180
|
ctx.diagnostics.push({
|
|
1068
1181
|
code: "CONTRADICTING_CONSTRAINTS",
|
|
@@ -1090,6 +1203,15 @@ function addUnknownExtension(ctx, message, primary) {
|
|
|
1090
1203
|
relatedLocations: []
|
|
1091
1204
|
});
|
|
1092
1205
|
}
|
|
1206
|
+
function addUnknownPathTarget(ctx, message, primary) {
|
|
1207
|
+
ctx.diagnostics.push({
|
|
1208
|
+
code: "UNKNOWN_PATH_TARGET",
|
|
1209
|
+
message,
|
|
1210
|
+
severity: "error",
|
|
1211
|
+
primaryLocation: primary,
|
|
1212
|
+
relatedLocations: []
|
|
1213
|
+
});
|
|
1214
|
+
}
|
|
1093
1215
|
function addConstraintBroadening(ctx, message, primary, related) {
|
|
1094
1216
|
ctx.diagnostics.push({
|
|
1095
1217
|
code: "CONSTRAINT_BROADENING",
|
|
@@ -1099,6 +1221,13 @@ function addConstraintBroadening(ctx, message, primary, related) {
|
|
|
1099
1221
|
relatedLocations: [related]
|
|
1100
1222
|
});
|
|
1101
1223
|
}
|
|
1224
|
+
function getExtensionIdFromConstraintId(constraintId) {
|
|
1225
|
+
const separator = constraintId.lastIndexOf("/");
|
|
1226
|
+
if (separator <= 0) {
|
|
1227
|
+
return null;
|
|
1228
|
+
}
|
|
1229
|
+
return constraintId.slice(0, separator);
|
|
1230
|
+
}
|
|
1102
1231
|
function findNumeric(constraints, constraintKind) {
|
|
1103
1232
|
return constraints.find((c) => c.constraintKind === constraintKind);
|
|
1104
1233
|
}
|
|
@@ -1110,6 +1239,45 @@ function findAllowedMembers(constraints) {
|
|
|
1110
1239
|
(c) => c.constraintKind === "allowedMembers"
|
|
1111
1240
|
);
|
|
1112
1241
|
}
|
|
1242
|
+
function findConstConstraints(constraints) {
|
|
1243
|
+
return constraints.filter(
|
|
1244
|
+
(c) => c.constraintKind === "const"
|
|
1245
|
+
);
|
|
1246
|
+
}
|
|
1247
|
+
function jsonValueEquals(left, right) {
|
|
1248
|
+
if (left === right) {
|
|
1249
|
+
return true;
|
|
1250
|
+
}
|
|
1251
|
+
if (Array.isArray(left) || Array.isArray(right)) {
|
|
1252
|
+
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
|
|
1253
|
+
return false;
|
|
1254
|
+
}
|
|
1255
|
+
return left.every((item, index) => jsonValueEquals(item, right[index]));
|
|
1256
|
+
}
|
|
1257
|
+
if (isJsonObject(left) || isJsonObject(right)) {
|
|
1258
|
+
if (!isJsonObject(left) || !isJsonObject(right)) {
|
|
1259
|
+
return false;
|
|
1260
|
+
}
|
|
1261
|
+
const leftKeys = Object.keys(left).sort();
|
|
1262
|
+
const rightKeys = Object.keys(right).sort();
|
|
1263
|
+
if (leftKeys.length !== rightKeys.length) {
|
|
1264
|
+
return false;
|
|
1265
|
+
}
|
|
1266
|
+
return leftKeys.every((key, index) => {
|
|
1267
|
+
const rightKey = rightKeys[index];
|
|
1268
|
+
if (rightKey !== key) {
|
|
1269
|
+
return false;
|
|
1270
|
+
}
|
|
1271
|
+
const leftValue = left[key];
|
|
1272
|
+
const rightValue = right[rightKey];
|
|
1273
|
+
return leftValue !== void 0 && rightValue !== void 0 && jsonValueEquals(leftValue, rightValue);
|
|
1274
|
+
});
|
|
1275
|
+
}
|
|
1276
|
+
return false;
|
|
1277
|
+
}
|
|
1278
|
+
function isJsonObject(value) {
|
|
1279
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1280
|
+
}
|
|
1113
1281
|
function isOrderedBoundConstraint(constraint) {
|
|
1114
1282
|
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";
|
|
1115
1283
|
}
|
|
@@ -1230,6 +1398,112 @@ function checkConstraintBroadening(ctx, fieldName, constraints) {
|
|
|
1230
1398
|
strongestByKey.set(key, constraint);
|
|
1231
1399
|
}
|
|
1232
1400
|
}
|
|
1401
|
+
function compareCustomConstraintStrength(current, previous) {
|
|
1402
|
+
const order = current.comparePayloads(current.constraint.payload, previous.constraint.payload);
|
|
1403
|
+
const equalPayloadTiebreaker = order === 0 ? compareSemanticInclusivity(current.role.inclusive, previous.role.inclusive) : order;
|
|
1404
|
+
switch (current.role.bound) {
|
|
1405
|
+
case "lower":
|
|
1406
|
+
return equalPayloadTiebreaker;
|
|
1407
|
+
case "upper":
|
|
1408
|
+
return equalPayloadTiebreaker === 0 ? 0 : -equalPayloadTiebreaker;
|
|
1409
|
+
case "exact":
|
|
1410
|
+
return order === 0 ? 0 : Number.NaN;
|
|
1411
|
+
default: {
|
|
1412
|
+
const _exhaustive = current.role.bound;
|
|
1413
|
+
return _exhaustive;
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
function compareSemanticInclusivity(currentInclusive, previousInclusive) {
|
|
1418
|
+
if (currentInclusive === previousInclusive) {
|
|
1419
|
+
return 0;
|
|
1420
|
+
}
|
|
1421
|
+
return currentInclusive ? -1 : 1;
|
|
1422
|
+
}
|
|
1423
|
+
function customConstraintsContradict(lower, upper) {
|
|
1424
|
+
const order = lower.comparePayloads(lower.constraint.payload, upper.constraint.payload);
|
|
1425
|
+
if (order > 0) {
|
|
1426
|
+
return true;
|
|
1427
|
+
}
|
|
1428
|
+
if (order < 0) {
|
|
1429
|
+
return false;
|
|
1430
|
+
}
|
|
1431
|
+
return !lower.role.inclusive || !upper.role.inclusive;
|
|
1432
|
+
}
|
|
1433
|
+
function describeCustomConstraintTag(constraint) {
|
|
1434
|
+
return constraint.provenance.tagName ?? constraint.constraintId;
|
|
1435
|
+
}
|
|
1436
|
+
function checkCustomConstraintSemantics(ctx, fieldName, constraints) {
|
|
1437
|
+
if (ctx.extensionRegistry === void 0) {
|
|
1438
|
+
return;
|
|
1439
|
+
}
|
|
1440
|
+
const strongestByKey = /* @__PURE__ */ new Map();
|
|
1441
|
+
const lowerByFamily = /* @__PURE__ */ new Map();
|
|
1442
|
+
const upperByFamily = /* @__PURE__ */ new Map();
|
|
1443
|
+
for (const constraint of constraints) {
|
|
1444
|
+
if (constraint.constraintKind !== "custom") {
|
|
1445
|
+
continue;
|
|
1446
|
+
}
|
|
1447
|
+
const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
|
|
1448
|
+
if (registration?.comparePayloads === void 0 || registration.semanticRole === void 0) {
|
|
1449
|
+
continue;
|
|
1450
|
+
}
|
|
1451
|
+
const entry = {
|
|
1452
|
+
constraint,
|
|
1453
|
+
comparePayloads: registration.comparePayloads,
|
|
1454
|
+
role: registration.semanticRole
|
|
1455
|
+
};
|
|
1456
|
+
const familyKey = `${registration.semanticRole.family}:${pathKey(constraint)}`;
|
|
1457
|
+
const boundKey = `${familyKey}:${registration.semanticRole.bound}`;
|
|
1458
|
+
const previous = strongestByKey.get(boundKey);
|
|
1459
|
+
if (previous !== void 0) {
|
|
1460
|
+
const strength = compareCustomConstraintStrength(entry, previous);
|
|
1461
|
+
if (Number.isNaN(strength)) {
|
|
1462
|
+
addContradiction(
|
|
1463
|
+
ctx,
|
|
1464
|
+
`Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} conflicts with ${describeCustomConstraintTag(previous.constraint)}`,
|
|
1465
|
+
constraint.provenance,
|
|
1466
|
+
previous.constraint.provenance
|
|
1467
|
+
);
|
|
1468
|
+
continue;
|
|
1469
|
+
}
|
|
1470
|
+
if (strength < 0) {
|
|
1471
|
+
addConstraintBroadening(
|
|
1472
|
+
ctx,
|
|
1473
|
+
`Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} is broader than earlier ${describeCustomConstraintTag(previous.constraint)}. Constraints can only narrow.`,
|
|
1474
|
+
constraint.provenance,
|
|
1475
|
+
previous.constraint.provenance
|
|
1476
|
+
);
|
|
1477
|
+
continue;
|
|
1478
|
+
}
|
|
1479
|
+
if (strength > 0) {
|
|
1480
|
+
strongestByKey.set(boundKey, entry);
|
|
1481
|
+
}
|
|
1482
|
+
} else {
|
|
1483
|
+
strongestByKey.set(boundKey, entry);
|
|
1484
|
+
}
|
|
1485
|
+
if (registration.semanticRole.bound === "lower") {
|
|
1486
|
+
lowerByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
|
|
1487
|
+
} else if (registration.semanticRole.bound === "upper") {
|
|
1488
|
+
upperByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
for (const [familyKey, lower] of lowerByFamily) {
|
|
1492
|
+
const upper = upperByFamily.get(familyKey);
|
|
1493
|
+
if (upper === void 0) {
|
|
1494
|
+
continue;
|
|
1495
|
+
}
|
|
1496
|
+
if (!customConstraintsContradict(lower, upper)) {
|
|
1497
|
+
continue;
|
|
1498
|
+
}
|
|
1499
|
+
addContradiction(
|
|
1500
|
+
ctx,
|
|
1501
|
+
`Field "${formatPathTargetFieldName(fieldName, lower.constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(lower.constraint)} contradicts ${describeCustomConstraintTag(upper.constraint)}`,
|
|
1502
|
+
lower.constraint.provenance,
|
|
1503
|
+
upper.constraint.provenance
|
|
1504
|
+
);
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1233
1507
|
function checkNumericContradictions(ctx, fieldName, constraints) {
|
|
1234
1508
|
const min = findNumeric(constraints, "minimum");
|
|
1235
1509
|
const max = findNumeric(constraints, "maximum");
|
|
@@ -1316,6 +1590,25 @@ function checkAllowedMembersContradiction(ctx, fieldName, constraints) {
|
|
|
1316
1590
|
}
|
|
1317
1591
|
}
|
|
1318
1592
|
}
|
|
1593
|
+
function checkConstContradictions(ctx, fieldName, constraints) {
|
|
1594
|
+
const constConstraints = findConstConstraints(constraints);
|
|
1595
|
+
if (constConstraints.length < 2) return;
|
|
1596
|
+
const first = constConstraints[0];
|
|
1597
|
+
if (first === void 0) return;
|
|
1598
|
+
for (let i = 1; i < constConstraints.length; i++) {
|
|
1599
|
+
const current = constConstraints[i];
|
|
1600
|
+
if (current === void 0) continue;
|
|
1601
|
+
if (jsonValueEquals(first.value, current.value)) {
|
|
1602
|
+
continue;
|
|
1603
|
+
}
|
|
1604
|
+
addContradiction(
|
|
1605
|
+
ctx,
|
|
1606
|
+
`Field "${fieldName}": conflicting @const constraints require both ${JSON.stringify(first.value)} and ${JSON.stringify(current.value)}`,
|
|
1607
|
+
first.provenance,
|
|
1608
|
+
current.provenance
|
|
1609
|
+
);
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1319
1612
|
function typeLabel(type) {
|
|
1320
1613
|
switch (type.kind) {
|
|
1321
1614
|
case "primitive":
|
|
@@ -1388,6 +1681,8 @@ function checkConstraintOnType(ctx, fieldName, type, constraint) {
|
|
|
1388
1681
|
const isString = effectiveType.kind === "primitive" && effectiveType.primitiveKind === "string";
|
|
1389
1682
|
const isArray = effectiveType.kind === "array";
|
|
1390
1683
|
const isEnum = effectiveType.kind === "enum";
|
|
1684
|
+
const arrayItemType = effectiveType.kind === "array" ? dereferenceType(ctx, effectiveType.items) : void 0;
|
|
1685
|
+
const isStringArray = arrayItemType?.kind === "primitive" && arrayItemType.primitiveKind === "string";
|
|
1391
1686
|
const label = typeLabel(effectiveType);
|
|
1392
1687
|
const ck = constraint.constraintKind;
|
|
1393
1688
|
switch (ck) {
|
|
@@ -1408,10 +1703,10 @@ function checkConstraintOnType(ctx, fieldName, type, constraint) {
|
|
|
1408
1703
|
case "minLength":
|
|
1409
1704
|
case "maxLength":
|
|
1410
1705
|
case "pattern": {
|
|
1411
|
-
if (!isString) {
|
|
1706
|
+
if (!isString && !isStringArray) {
|
|
1412
1707
|
addTypeMismatch(
|
|
1413
1708
|
ctx,
|
|
1414
|
-
`Field "${fieldName}": constraint "${ck}" is only valid on string fields, but field type is "${label}"`,
|
|
1709
|
+
`Field "${fieldName}": constraint "${ck}" is only valid on string fields or string array items, but field type is "${label}"`,
|
|
1415
1710
|
constraint.provenance
|
|
1416
1711
|
);
|
|
1417
1712
|
}
|
|
@@ -1439,6 +1734,37 @@ function checkConstraintOnType(ctx, fieldName, type, constraint) {
|
|
|
1439
1734
|
}
|
|
1440
1735
|
break;
|
|
1441
1736
|
}
|
|
1737
|
+
case "const": {
|
|
1738
|
+
const isPrimitiveConstType = effectiveType.kind === "primitive" && ["string", "number", "boolean", "null"].includes(effectiveType.primitiveKind) || effectiveType.kind === "enum";
|
|
1739
|
+
if (!isPrimitiveConstType) {
|
|
1740
|
+
addTypeMismatch(
|
|
1741
|
+
ctx,
|
|
1742
|
+
`Field "${fieldName}": constraint "const" is only valid on primitive or enum fields, but field type is "${label}"`,
|
|
1743
|
+
constraint.provenance
|
|
1744
|
+
);
|
|
1745
|
+
break;
|
|
1746
|
+
}
|
|
1747
|
+
if (effectiveType.kind === "primitive") {
|
|
1748
|
+
const valueType = constraint.value === null ? "null" : Array.isArray(constraint.value) ? "array" : typeof constraint.value;
|
|
1749
|
+
if (valueType !== effectiveType.primitiveKind) {
|
|
1750
|
+
addTypeMismatch(
|
|
1751
|
+
ctx,
|
|
1752
|
+
`Field "${fieldName}": @const value type "${valueType}" is incompatible with field type "${effectiveType.primitiveKind}"`,
|
|
1753
|
+
constraint.provenance
|
|
1754
|
+
);
|
|
1755
|
+
}
|
|
1756
|
+
break;
|
|
1757
|
+
}
|
|
1758
|
+
const memberValues = effectiveType.members.map((member) => member.value);
|
|
1759
|
+
if (!memberValues.some((member) => jsonValueEquals(member, constraint.value))) {
|
|
1760
|
+
addTypeMismatch(
|
|
1761
|
+
ctx,
|
|
1762
|
+
`Field "${fieldName}": @const value ${JSON.stringify(constraint.value)} is not one of the enum members`,
|
|
1763
|
+
constraint.provenance
|
|
1764
|
+
);
|
|
1765
|
+
}
|
|
1766
|
+
break;
|
|
1767
|
+
}
|
|
1442
1768
|
case "custom": {
|
|
1443
1769
|
checkCustomConstraint(ctx, fieldName, effectiveType, constraint);
|
|
1444
1770
|
break;
|
|
@@ -1457,9 +1783,9 @@ function checkTypeApplicability(ctx, fieldName, type, constraints) {
|
|
|
1457
1783
|
const resolution = resolvePathTargetType(ctx, type, constraint.path.segments);
|
|
1458
1784
|
const targetFieldName = formatPathTargetFieldName(fieldName, constraint.path.segments);
|
|
1459
1785
|
if (resolution.kind === "missing-property") {
|
|
1460
|
-
|
|
1786
|
+
addUnknownPathTarget(
|
|
1461
1787
|
ctx,
|
|
1462
|
-
`Field "${
|
|
1788
|
+
`Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" references unknown path segment "${resolution.segment}"`,
|
|
1463
1789
|
constraint.provenance
|
|
1464
1790
|
);
|
|
1465
1791
|
continue;
|
|
@@ -1489,8 +1815,30 @@ function checkCustomConstraint(ctx, fieldName, type, constraint) {
|
|
|
1489
1815
|
);
|
|
1490
1816
|
return;
|
|
1491
1817
|
}
|
|
1492
|
-
|
|
1493
|
-
if (
|
|
1818
|
+
const normalizedTagName = constraint.provenance.tagName === void 0 ? void 0 : (0, import_core3.normalizeConstraintTagName)(constraint.provenance.tagName.replace(/^@/, ""));
|
|
1819
|
+
if (normalizedTagName !== void 0) {
|
|
1820
|
+
const tagRegistration = ctx.extensionRegistry.findConstraintTag(normalizedTagName);
|
|
1821
|
+
const extensionId = getExtensionIdFromConstraintId(constraint.constraintId);
|
|
1822
|
+
if (extensionId !== null && tagRegistration?.extensionId === extensionId && tagRegistration.registration.constraintName === registration.constraintName && tagRegistration.registration.isApplicableToType?.(type) === false) {
|
|
1823
|
+
addTypeMismatch(
|
|
1824
|
+
ctx,
|
|
1825
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
1826
|
+
constraint.provenance
|
|
1827
|
+
);
|
|
1828
|
+
return;
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
if (registration.applicableTypes === null) {
|
|
1832
|
+
if (registration.isApplicableToType?.(type) === false) {
|
|
1833
|
+
addTypeMismatch(
|
|
1834
|
+
ctx,
|
|
1835
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
1836
|
+
constraint.provenance
|
|
1837
|
+
);
|
|
1838
|
+
}
|
|
1839
|
+
return;
|
|
1840
|
+
}
|
|
1841
|
+
if (!registration.applicableTypes.includes(type.kind) || registration.isApplicableToType?.(type) === false) {
|
|
1494
1842
|
addTypeMismatch(
|
|
1495
1843
|
ctx,
|
|
1496
1844
|
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
@@ -1519,7 +1867,9 @@ function validateConstraints(ctx, name, type, constraints) {
|
|
|
1519
1867
|
checkNumericContradictions(ctx, name, constraints);
|
|
1520
1868
|
checkLengthContradictions(ctx, name, constraints);
|
|
1521
1869
|
checkAllowedMembersContradiction(ctx, name, constraints);
|
|
1870
|
+
checkConstContradictions(ctx, name, constraints);
|
|
1522
1871
|
checkConstraintBroadening(ctx, name, constraints);
|
|
1872
|
+
checkCustomConstraintSemantics(ctx, name, constraints);
|
|
1523
1873
|
checkTypeApplicability(ctx, name, type, constraints);
|
|
1524
1874
|
}
|
|
1525
1875
|
function validateElement(ctx, element) {
|