@formspec/build 0.1.0-alpha.16 → 0.1.0-alpha.19
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/README.md +74 -128
- package/dist/__tests__/class-schema.test.d.ts +2 -0
- package/dist/__tests__/class-schema.test.d.ts.map +1 -0
- package/dist/__tests__/date-extension.integration.test.d.ts +2 -0
- package/dist/__tests__/date-extension.integration.test.d.ts.map +1 -0
- package/dist/__tests__/fixtures/class-schema-regressions.d.ts +83 -0
- package/dist/__tests__/fixtures/class-schema-regressions.d.ts.map +1 -0
- package/dist/__tests__/fixtures/example-date-extension.d.ts +12 -0
- package/dist/__tests__/fixtures/example-date-extension.d.ts.map +1 -0
- 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/extension-forms.d.ts +7 -0
- package/dist/__tests__/fixtures/extension-forms.d.ts.map +1 -0
- package/dist/__tests__/fixtures/mixed-authoring-shipping-address.d.ts +1 -0
- package/dist/__tests__/fixtures/mixed-authoring-shipping-address.d.ts.map +1 -1
- package/dist/__tests__/fixtures/named-primitive-aliases.d.ts +15 -0
- package/dist/__tests__/fixtures/named-primitive-aliases.d.ts.map +1 -0
- package/dist/__tests__/fixtures/nested-array-path-constraints.d.ts +14 -0
- package/dist/__tests__/fixtures/nested-array-path-constraints.d.ts.map +1 -0
- package/dist/__tests__/fixtures/sample-forms.d.ts +10 -0
- package/dist/__tests__/fixtures/sample-forms.d.ts.map +1 -1
- package/dist/__tests__/generate-schemas.test.d.ts +2 -0
- package/dist/__tests__/generate-schemas.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/parity.test.d.ts +6 -2
- package/dist/__tests__/parity/parity.test.d.ts.map +1 -1
- package/dist/__tests__/parity/utils.d.ts +9 -4
- package/dist/__tests__/parity/utils.d.ts.map +1 -1
- package/dist/analyzer/class-analyzer.d.ts +5 -4
- 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/program.d.ts +15 -0
- package/dist/analyzer/program.d.ts.map +1 -1
- package/dist/analyzer/tsdoc-parser.d.ts +23 -2
- package/dist/analyzer/tsdoc-parser.d.ts.map +1 -1
- package/dist/browser.cjs +269 -11
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.js +269 -11
- package/dist/browser.js.map +1 -1
- package/dist/build.d.ts +28 -2
- package/dist/canonicalize/chain-dsl-canonicalizer.d.ts.map +1 -1
- package/dist/canonicalize/tsdoc-canonicalizer.d.ts.map +1 -1
- package/dist/cli.cjs +1640 -282
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1638 -281
- 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/method-schema.d.ts.map +1 -1
- package/dist/generators/mixed-authoring.d.ts.map +1 -1
- package/dist/index.cjs +1615 -271
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1615 -271
- package/dist/index.js.map +1 -1
- package/dist/internals.cjs +990 -236
- package/dist/internals.cjs.map +1 -1
- package/dist/internals.js +988 -234
- package/dist/internals.js.map +1 -1
- package/dist/json-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/browser.js
CHANGED
|
@@ -20,6 +20,7 @@ function canonicalizeChainDSL(form) {
|
|
|
20
20
|
kind: "form-ir",
|
|
21
21
|
irVersion: IR_VERSION,
|
|
22
22
|
elements: canonicalizeElements(form.elements),
|
|
23
|
+
rootAnnotations: [],
|
|
23
24
|
typeRegistry: {},
|
|
24
25
|
provenance: CHAIN_DSL_PROVENANCE
|
|
25
26
|
};
|
|
@@ -330,6 +331,9 @@ function generateJsonSchemaFromIR(ir, options) {
|
|
|
330
331
|
const ctx = makeContext(options);
|
|
331
332
|
for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
|
|
332
333
|
ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
|
|
334
|
+
if (typeDef.constraints && typeDef.constraints.length > 0) {
|
|
335
|
+
applyConstraints(ctx.defs[name], typeDef.constraints, ctx);
|
|
336
|
+
}
|
|
333
337
|
if (typeDef.annotations && typeDef.annotations.length > 0) {
|
|
334
338
|
applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
|
|
335
339
|
}
|
|
@@ -498,7 +502,9 @@ function generateTypeNode(type, ctx) {
|
|
|
498
502
|
}
|
|
499
503
|
}
|
|
500
504
|
function generatePrimitiveType(type) {
|
|
501
|
-
return {
|
|
505
|
+
return {
|
|
506
|
+
type: type.primitiveKind === "integer" || type.primitiveKind === "bigint" ? "integer" : type.primitiveKind
|
|
507
|
+
};
|
|
502
508
|
}
|
|
503
509
|
function generateEnumType(type) {
|
|
504
510
|
const hasDisplayNames = type.members.some((m) => m.displayName !== void 0);
|
|
@@ -671,7 +677,7 @@ function applyAnnotations(schema, annotations, ctx) {
|
|
|
671
677
|
case "deprecated":
|
|
672
678
|
schema.deprecated = true;
|
|
673
679
|
if (annotation.message !== void 0 && annotation.message !== "") {
|
|
674
|
-
schema[
|
|
680
|
+
schema[`${ctx.vendorPrefix}-deprecation-description`] = annotation.message;
|
|
675
681
|
}
|
|
676
682
|
break;
|
|
677
683
|
case "placeholder":
|
|
@@ -704,7 +710,12 @@ function applyCustomConstraint(schema, constraint, ctx) {
|
|
|
704
710
|
`Cannot generate JSON Schema for custom constraint "${constraint.constraintId}" without a matching extension registration`
|
|
705
711
|
);
|
|
706
712
|
}
|
|
707
|
-
|
|
713
|
+
assignVendorPrefixedExtensionKeywords(
|
|
714
|
+
schema,
|
|
715
|
+
registration.toJsonSchema(constraint.payload, ctx.vendorPrefix),
|
|
716
|
+
ctx.vendorPrefix,
|
|
717
|
+
`custom constraint "${constraint.constraintId}"`
|
|
718
|
+
);
|
|
708
719
|
}
|
|
709
720
|
function applyCustomAnnotation(schema, annotation, ctx) {
|
|
710
721
|
const registration = ctx.extensionRegistry?.findAnnotation(annotation.annotationId);
|
|
@@ -716,7 +727,22 @@ function applyCustomAnnotation(schema, annotation, ctx) {
|
|
|
716
727
|
if (registration.toJsonSchema === void 0) {
|
|
717
728
|
return;
|
|
718
729
|
}
|
|
719
|
-
|
|
730
|
+
assignVendorPrefixedExtensionKeywords(
|
|
731
|
+
schema,
|
|
732
|
+
registration.toJsonSchema(annotation.value, ctx.vendorPrefix),
|
|
733
|
+
ctx.vendorPrefix,
|
|
734
|
+
`custom annotation "${annotation.annotationId}"`
|
|
735
|
+
);
|
|
736
|
+
}
|
|
737
|
+
function assignVendorPrefixedExtensionKeywords(schema, extensionSchema, vendorPrefix, source) {
|
|
738
|
+
for (const [key, value] of Object.entries(extensionSchema)) {
|
|
739
|
+
if (!key.startsWith(`${vendorPrefix}-`)) {
|
|
740
|
+
throw new Error(
|
|
741
|
+
`Cannot apply ${source}: extension hooks may only emit "${vendorPrefix}-*" JSON Schema keywords`
|
|
742
|
+
);
|
|
743
|
+
}
|
|
744
|
+
schema[key] = value;
|
|
745
|
+
}
|
|
720
746
|
}
|
|
721
747
|
|
|
722
748
|
// src/json-schema/generator.ts
|
|
@@ -965,7 +991,10 @@ function getSchemaExtension(schema, key) {
|
|
|
965
991
|
// src/extensions/registry.ts
|
|
966
992
|
function createExtensionRegistry(extensions) {
|
|
967
993
|
const typeMap = /* @__PURE__ */ new Map();
|
|
994
|
+
const typeNameMap = /* @__PURE__ */ new Map();
|
|
968
995
|
const constraintMap = /* @__PURE__ */ new Map();
|
|
996
|
+
const constraintTagMap = /* @__PURE__ */ new Map();
|
|
997
|
+
const builtinBroadeningMap = /* @__PURE__ */ new Map();
|
|
969
998
|
const annotationMap = /* @__PURE__ */ new Map();
|
|
970
999
|
for (const ext of extensions) {
|
|
971
1000
|
if (ext.types !== void 0) {
|
|
@@ -975,6 +1004,27 @@ function createExtensionRegistry(extensions) {
|
|
|
975
1004
|
throw new Error(`Duplicate custom type ID: "${qualifiedId}"`);
|
|
976
1005
|
}
|
|
977
1006
|
typeMap.set(qualifiedId, type);
|
|
1007
|
+
for (const sourceTypeName of type.tsTypeNames ?? [type.typeName]) {
|
|
1008
|
+
if (typeNameMap.has(sourceTypeName)) {
|
|
1009
|
+
throw new Error(`Duplicate custom type source name: "${sourceTypeName}"`);
|
|
1010
|
+
}
|
|
1011
|
+
typeNameMap.set(sourceTypeName, {
|
|
1012
|
+
extensionId: ext.extensionId,
|
|
1013
|
+
registration: type
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
if (type.builtinConstraintBroadenings !== void 0) {
|
|
1017
|
+
for (const broadening of type.builtinConstraintBroadenings) {
|
|
1018
|
+
const key = `${qualifiedId}:${broadening.tagName}`;
|
|
1019
|
+
if (builtinBroadeningMap.has(key)) {
|
|
1020
|
+
throw new Error(`Duplicate built-in constraint broadening: "${key}"`);
|
|
1021
|
+
}
|
|
1022
|
+
builtinBroadeningMap.set(key, {
|
|
1023
|
+
extensionId: ext.extensionId,
|
|
1024
|
+
registration: broadening
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
978
1028
|
}
|
|
979
1029
|
}
|
|
980
1030
|
if (ext.constraints !== void 0) {
|
|
@@ -986,6 +1036,17 @@ function createExtensionRegistry(extensions) {
|
|
|
986
1036
|
constraintMap.set(qualifiedId, constraint);
|
|
987
1037
|
}
|
|
988
1038
|
}
|
|
1039
|
+
if (ext.constraintTags !== void 0) {
|
|
1040
|
+
for (const tag of ext.constraintTags) {
|
|
1041
|
+
if (constraintTagMap.has(tag.tagName)) {
|
|
1042
|
+
throw new Error(`Duplicate custom constraint tag: "@${tag.tagName}"`);
|
|
1043
|
+
}
|
|
1044
|
+
constraintTagMap.set(tag.tagName, {
|
|
1045
|
+
extensionId: ext.extensionId,
|
|
1046
|
+
registration: tag
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
989
1050
|
if (ext.annotations !== void 0) {
|
|
990
1051
|
for (const annotation of ext.annotations) {
|
|
991
1052
|
const qualifiedId = `${ext.extensionId}/${annotation.annotationName}`;
|
|
@@ -999,7 +1060,10 @@ function createExtensionRegistry(extensions) {
|
|
|
999
1060
|
return {
|
|
1000
1061
|
extensions,
|
|
1001
1062
|
findType: (typeId) => typeMap.get(typeId),
|
|
1063
|
+
findTypeByName: (typeName) => typeNameMap.get(typeName),
|
|
1002
1064
|
findConstraint: (constraintId) => constraintMap.get(constraintId),
|
|
1065
|
+
findConstraintTag: (tagName) => constraintTagMap.get(tagName),
|
|
1066
|
+
findBuiltinConstraintBroadening: (typeId, tagName) => builtinBroadeningMap.get(`${typeId}:${tagName}`),
|
|
1003
1067
|
findAnnotation: (annotationId) => annotationMap.get(annotationId)
|
|
1004
1068
|
};
|
|
1005
1069
|
}
|
|
@@ -1067,6 +1131,7 @@ var jsonSchema7Schema = z3.lazy(
|
|
|
1067
1131
|
);
|
|
1068
1132
|
|
|
1069
1133
|
// src/validate/constraint-validator.ts
|
|
1134
|
+
import { normalizeConstraintTagName } from "@formspec/core";
|
|
1070
1135
|
function addContradiction(ctx, message, primary, related) {
|
|
1071
1136
|
ctx.diagnostics.push({
|
|
1072
1137
|
code: "CONTRADICTING_CONSTRAINTS",
|
|
@@ -1112,6 +1177,13 @@ function addConstraintBroadening(ctx, message, primary, related) {
|
|
|
1112
1177
|
relatedLocations: [related]
|
|
1113
1178
|
});
|
|
1114
1179
|
}
|
|
1180
|
+
function getExtensionIdFromConstraintId(constraintId) {
|
|
1181
|
+
const separator = constraintId.lastIndexOf("/");
|
|
1182
|
+
if (separator <= 0) {
|
|
1183
|
+
return null;
|
|
1184
|
+
}
|
|
1185
|
+
return constraintId.slice(0, separator);
|
|
1186
|
+
}
|
|
1115
1187
|
function findNumeric(constraints, constraintKind) {
|
|
1116
1188
|
return constraints.find((c) => c.constraintKind === constraintKind);
|
|
1117
1189
|
}
|
|
@@ -1282,6 +1354,112 @@ function checkConstraintBroadening(ctx, fieldName, constraints) {
|
|
|
1282
1354
|
strongestByKey.set(key, constraint);
|
|
1283
1355
|
}
|
|
1284
1356
|
}
|
|
1357
|
+
function compareCustomConstraintStrength(current, previous) {
|
|
1358
|
+
const order = current.comparePayloads(current.constraint.payload, previous.constraint.payload);
|
|
1359
|
+
const equalPayloadTiebreaker = order === 0 ? compareSemanticInclusivity(current.role.inclusive, previous.role.inclusive) : order;
|
|
1360
|
+
switch (current.role.bound) {
|
|
1361
|
+
case "lower":
|
|
1362
|
+
return equalPayloadTiebreaker;
|
|
1363
|
+
case "upper":
|
|
1364
|
+
return equalPayloadTiebreaker === 0 ? 0 : -equalPayloadTiebreaker;
|
|
1365
|
+
case "exact":
|
|
1366
|
+
return order === 0 ? 0 : Number.NaN;
|
|
1367
|
+
default: {
|
|
1368
|
+
const _exhaustive = current.role.bound;
|
|
1369
|
+
return _exhaustive;
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
function compareSemanticInclusivity(currentInclusive, previousInclusive) {
|
|
1374
|
+
if (currentInclusive === previousInclusive) {
|
|
1375
|
+
return 0;
|
|
1376
|
+
}
|
|
1377
|
+
return currentInclusive ? -1 : 1;
|
|
1378
|
+
}
|
|
1379
|
+
function customConstraintsContradict(lower, upper) {
|
|
1380
|
+
const order = lower.comparePayloads(lower.constraint.payload, upper.constraint.payload);
|
|
1381
|
+
if (order > 0) {
|
|
1382
|
+
return true;
|
|
1383
|
+
}
|
|
1384
|
+
if (order < 0) {
|
|
1385
|
+
return false;
|
|
1386
|
+
}
|
|
1387
|
+
return !lower.role.inclusive || !upper.role.inclusive;
|
|
1388
|
+
}
|
|
1389
|
+
function describeCustomConstraintTag(constraint) {
|
|
1390
|
+
return constraint.provenance.tagName ?? constraint.constraintId;
|
|
1391
|
+
}
|
|
1392
|
+
function checkCustomConstraintSemantics(ctx, fieldName, constraints) {
|
|
1393
|
+
if (ctx.extensionRegistry === void 0) {
|
|
1394
|
+
return;
|
|
1395
|
+
}
|
|
1396
|
+
const strongestByKey = /* @__PURE__ */ new Map();
|
|
1397
|
+
const lowerByFamily = /* @__PURE__ */ new Map();
|
|
1398
|
+
const upperByFamily = /* @__PURE__ */ new Map();
|
|
1399
|
+
for (const constraint of constraints) {
|
|
1400
|
+
if (constraint.constraintKind !== "custom") {
|
|
1401
|
+
continue;
|
|
1402
|
+
}
|
|
1403
|
+
const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
|
|
1404
|
+
if (registration?.comparePayloads === void 0 || registration.semanticRole === void 0) {
|
|
1405
|
+
continue;
|
|
1406
|
+
}
|
|
1407
|
+
const entry = {
|
|
1408
|
+
constraint,
|
|
1409
|
+
comparePayloads: registration.comparePayloads,
|
|
1410
|
+
role: registration.semanticRole
|
|
1411
|
+
};
|
|
1412
|
+
const familyKey = `${registration.semanticRole.family}:${pathKey(constraint)}`;
|
|
1413
|
+
const boundKey = `${familyKey}:${registration.semanticRole.bound}`;
|
|
1414
|
+
const previous = strongestByKey.get(boundKey);
|
|
1415
|
+
if (previous !== void 0) {
|
|
1416
|
+
const strength = compareCustomConstraintStrength(entry, previous);
|
|
1417
|
+
if (Number.isNaN(strength)) {
|
|
1418
|
+
addContradiction(
|
|
1419
|
+
ctx,
|
|
1420
|
+
`Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} conflicts with ${describeCustomConstraintTag(previous.constraint)}`,
|
|
1421
|
+
constraint.provenance,
|
|
1422
|
+
previous.constraint.provenance
|
|
1423
|
+
);
|
|
1424
|
+
continue;
|
|
1425
|
+
}
|
|
1426
|
+
if (strength < 0) {
|
|
1427
|
+
addConstraintBroadening(
|
|
1428
|
+
ctx,
|
|
1429
|
+
`Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} is broader than earlier ${describeCustomConstraintTag(previous.constraint)}. Constraints can only narrow.`,
|
|
1430
|
+
constraint.provenance,
|
|
1431
|
+
previous.constraint.provenance
|
|
1432
|
+
);
|
|
1433
|
+
continue;
|
|
1434
|
+
}
|
|
1435
|
+
if (strength > 0) {
|
|
1436
|
+
strongestByKey.set(boundKey, entry);
|
|
1437
|
+
}
|
|
1438
|
+
} else {
|
|
1439
|
+
strongestByKey.set(boundKey, entry);
|
|
1440
|
+
}
|
|
1441
|
+
if (registration.semanticRole.bound === "lower") {
|
|
1442
|
+
lowerByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
|
|
1443
|
+
} else if (registration.semanticRole.bound === "upper") {
|
|
1444
|
+
upperByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
for (const [familyKey, lower] of lowerByFamily) {
|
|
1448
|
+
const upper = upperByFamily.get(familyKey);
|
|
1449
|
+
if (upper === void 0) {
|
|
1450
|
+
continue;
|
|
1451
|
+
}
|
|
1452
|
+
if (!customConstraintsContradict(lower, upper)) {
|
|
1453
|
+
continue;
|
|
1454
|
+
}
|
|
1455
|
+
addContradiction(
|
|
1456
|
+
ctx,
|
|
1457
|
+
`Field "${formatPathTargetFieldName(fieldName, lower.constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(lower.constraint)} contradicts ${describeCustomConstraintTag(upper.constraint)}`,
|
|
1458
|
+
lower.constraint.provenance,
|
|
1459
|
+
upper.constraint.provenance
|
|
1460
|
+
);
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1285
1463
|
function checkNumericContradictions(ctx, fieldName, constraints) {
|
|
1286
1464
|
const min = findNumeric(constraints, "minimum");
|
|
1287
1465
|
const max = findNumeric(constraints, "maximum");
|
|
@@ -1429,6 +1607,26 @@ function dereferenceType(ctx, type) {
|
|
|
1429
1607
|
}
|
|
1430
1608
|
return current;
|
|
1431
1609
|
}
|
|
1610
|
+
function collectReferencedTypeConstraints(ctx, type) {
|
|
1611
|
+
const collected = [];
|
|
1612
|
+
let current = type;
|
|
1613
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1614
|
+
while (current.kind === "reference") {
|
|
1615
|
+
if (seen.has(current.name)) {
|
|
1616
|
+
break;
|
|
1617
|
+
}
|
|
1618
|
+
seen.add(current.name);
|
|
1619
|
+
const definition = ctx.typeRegistry[current.name];
|
|
1620
|
+
if (definition === void 0) {
|
|
1621
|
+
break;
|
|
1622
|
+
}
|
|
1623
|
+
if (definition.constraints !== void 0) {
|
|
1624
|
+
collected.push(...definition.constraints);
|
|
1625
|
+
}
|
|
1626
|
+
current = definition.type;
|
|
1627
|
+
}
|
|
1628
|
+
return collected;
|
|
1629
|
+
}
|
|
1432
1630
|
function resolvePathTargetType(ctx, type, segments) {
|
|
1433
1631
|
const effectiveType = dereferenceType(ctx, type);
|
|
1434
1632
|
if (segments.length === 0) {
|
|
@@ -1450,12 +1648,33 @@ function resolvePathTargetType(ctx, type, segments) {
|
|
|
1450
1648
|
}
|
|
1451
1649
|
return { kind: "unresolvable", type: effectiveType };
|
|
1452
1650
|
}
|
|
1651
|
+
function isNullType(type) {
|
|
1652
|
+
return type.kind === "primitive" && type.primitiveKind === "null";
|
|
1653
|
+
}
|
|
1654
|
+
function collectCustomConstraintCandidateTypes(ctx, type) {
|
|
1655
|
+
const effectiveType = dereferenceType(ctx, type);
|
|
1656
|
+
const candidates = [effectiveType];
|
|
1657
|
+
if (effectiveType.kind === "array") {
|
|
1658
|
+
candidates.push(...collectCustomConstraintCandidateTypes(ctx, effectiveType.items));
|
|
1659
|
+
}
|
|
1660
|
+
if (effectiveType.kind === "union") {
|
|
1661
|
+
const memberTypes = effectiveType.members.map((member) => dereferenceType(ctx, member));
|
|
1662
|
+
const nonNullMembers = memberTypes.filter((member) => !isNullType(member));
|
|
1663
|
+
if (nonNullMembers.length === 1 && nonNullMembers.length < memberTypes.length) {
|
|
1664
|
+
const [nullableMember] = nonNullMembers;
|
|
1665
|
+
if (nullableMember !== void 0) {
|
|
1666
|
+
candidates.push(...collectCustomConstraintCandidateTypes(ctx, nullableMember));
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
return candidates;
|
|
1671
|
+
}
|
|
1453
1672
|
function formatPathTargetFieldName(fieldName, path) {
|
|
1454
1673
|
return path.length === 0 ? fieldName : `${fieldName}.${path.join(".")}`;
|
|
1455
1674
|
}
|
|
1456
1675
|
function checkConstraintOnType(ctx, fieldName, type, constraint) {
|
|
1457
1676
|
const effectiveType = dereferenceType(ctx, type);
|
|
1458
|
-
const isNumber = effectiveType.kind === "primitive" &&
|
|
1677
|
+
const isNumber = effectiveType.kind === "primitive" && ["number", "integer", "bigint"].includes(effectiveType.primitiveKind);
|
|
1459
1678
|
const isString = effectiveType.kind === "primitive" && effectiveType.primitiveKind === "string";
|
|
1460
1679
|
const isArray = effectiveType.kind === "array";
|
|
1461
1680
|
const isEnum = effectiveType.kind === "enum";
|
|
@@ -1513,7 +1732,9 @@ function checkConstraintOnType(ctx, fieldName, type, constraint) {
|
|
|
1513
1732
|
break;
|
|
1514
1733
|
}
|
|
1515
1734
|
case "const": {
|
|
1516
|
-
const isPrimitiveConstType = effectiveType.kind === "primitive" && ["string", "number", "boolean", "null"].includes(
|
|
1735
|
+
const isPrimitiveConstType = effectiveType.kind === "primitive" && ["string", "number", "integer", "bigint", "boolean", "null"].includes(
|
|
1736
|
+
effectiveType.primitiveKind
|
|
1737
|
+
) || effectiveType.kind === "enum";
|
|
1517
1738
|
if (!isPrimitiveConstType) {
|
|
1518
1739
|
addTypeMismatch(
|
|
1519
1740
|
ctx,
|
|
@@ -1524,7 +1745,8 @@ function checkConstraintOnType(ctx, fieldName, type, constraint) {
|
|
|
1524
1745
|
}
|
|
1525
1746
|
if (effectiveType.kind === "primitive") {
|
|
1526
1747
|
const valueType = constraint.value === null ? "null" : Array.isArray(constraint.value) ? "array" : typeof constraint.value;
|
|
1527
|
-
|
|
1748
|
+
const expectedValueType = effectiveType.primitiveKind === "integer" || effectiveType.primitiveKind === "bigint" ? "number" : effectiveType.primitiveKind;
|
|
1749
|
+
if (valueType !== expectedValueType) {
|
|
1528
1750
|
addTypeMismatch(
|
|
1529
1751
|
ctx,
|
|
1530
1752
|
`Field "${fieldName}": @const value type "${valueType}" is incompatible with field type "${effectiveType.primitiveKind}"`,
|
|
@@ -1593,8 +1815,37 @@ function checkCustomConstraint(ctx, fieldName, type, constraint) {
|
|
|
1593
1815
|
);
|
|
1594
1816
|
return;
|
|
1595
1817
|
}
|
|
1596
|
-
|
|
1597
|
-
|
|
1818
|
+
const candidateTypes = collectCustomConstraintCandidateTypes(ctx, type);
|
|
1819
|
+
const normalizedTagName = constraint.provenance.tagName === void 0 ? void 0 : normalizeConstraintTagName(constraint.provenance.tagName.replace(/^@/, ""));
|
|
1820
|
+
if (normalizedTagName !== void 0) {
|
|
1821
|
+
const tagRegistration = ctx.extensionRegistry.findConstraintTag(normalizedTagName);
|
|
1822
|
+
const extensionId = getExtensionIdFromConstraintId(constraint.constraintId);
|
|
1823
|
+
if (extensionId !== null && tagRegistration?.extensionId === extensionId && tagRegistration.registration.constraintName === registration.constraintName && !candidateTypes.some(
|
|
1824
|
+
(candidateType) => tagRegistration.registration.isApplicableToType?.(candidateType) !== false
|
|
1825
|
+
)) {
|
|
1826
|
+
addTypeMismatch(
|
|
1827
|
+
ctx,
|
|
1828
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
1829
|
+
constraint.provenance
|
|
1830
|
+
);
|
|
1831
|
+
return;
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
if (registration.applicableTypes === null) {
|
|
1835
|
+
if (!candidateTypes.some((candidateType) => registration.isApplicableToType?.(candidateType) !== false)) {
|
|
1836
|
+
addTypeMismatch(
|
|
1837
|
+
ctx,
|
|
1838
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
1839
|
+
constraint.provenance
|
|
1840
|
+
);
|
|
1841
|
+
}
|
|
1842
|
+
return;
|
|
1843
|
+
}
|
|
1844
|
+
const applicableTypes = registration.applicableTypes;
|
|
1845
|
+
const matchesApplicableType = candidateTypes.some(
|
|
1846
|
+
(candidateType) => applicableTypes.includes(candidateType.kind) && registration.isApplicableToType?.(candidateType) !== false
|
|
1847
|
+
);
|
|
1848
|
+
if (!matchesApplicableType) {
|
|
1598
1849
|
addTypeMismatch(
|
|
1599
1850
|
ctx,
|
|
1600
1851
|
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
@@ -1603,7 +1854,10 @@ function checkCustomConstraint(ctx, fieldName, type, constraint) {
|
|
|
1603
1854
|
}
|
|
1604
1855
|
}
|
|
1605
1856
|
function validateFieldNode(ctx, field) {
|
|
1606
|
-
validateConstraints(ctx, field.name, field.type,
|
|
1857
|
+
validateConstraints(ctx, field.name, field.type, [
|
|
1858
|
+
...collectReferencedTypeConstraints(ctx, field.type),
|
|
1859
|
+
...field.constraints
|
|
1860
|
+
]);
|
|
1607
1861
|
if (field.type.kind === "object") {
|
|
1608
1862
|
for (const prop of field.type.properties) {
|
|
1609
1863
|
validateObjectProperty(ctx, field.name, prop);
|
|
@@ -1612,7 +1866,10 @@ function validateFieldNode(ctx, field) {
|
|
|
1612
1866
|
}
|
|
1613
1867
|
function validateObjectProperty(ctx, parentName, prop) {
|
|
1614
1868
|
const qualifiedName = `${parentName}.${prop.name}`;
|
|
1615
|
-
validateConstraints(ctx, qualifiedName, prop.type,
|
|
1869
|
+
validateConstraints(ctx, qualifiedName, prop.type, [
|
|
1870
|
+
...collectReferencedTypeConstraints(ctx, prop.type),
|
|
1871
|
+
...prop.constraints
|
|
1872
|
+
]);
|
|
1616
1873
|
if (prop.type.kind === "object") {
|
|
1617
1874
|
for (const nestedProp of prop.type.properties) {
|
|
1618
1875
|
validateObjectProperty(ctx, qualifiedName, nestedProp);
|
|
@@ -1625,6 +1882,7 @@ function validateConstraints(ctx, name, type, constraints) {
|
|
|
1625
1882
|
checkAllowedMembersContradiction(ctx, name, constraints);
|
|
1626
1883
|
checkConstContradictions(ctx, name, constraints);
|
|
1627
1884
|
checkConstraintBroadening(ctx, name, constraints);
|
|
1885
|
+
checkCustomConstraintSemantics(ctx, name, constraints);
|
|
1628
1886
|
checkTypeApplicability(ctx, name, type, constraints);
|
|
1629
1887
|
}
|
|
1630
1888
|
function validateElement(ctx, element) {
|