@formspec/build 0.1.0-alpha.19 → 0.1.0-alpha.20
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/class-schema-regressions.d.ts +4 -0
- package/dist/__tests__/fixtures/class-schema-regressions.d.ts.map +1 -1
- package/dist/__tests__/parity/utils.d.ts.map +1 -1
- package/dist/analyzer/class-analyzer.d.ts +4 -1
- package/dist/analyzer/class-analyzer.d.ts.map +1 -1
- package/dist/analyzer/jsdoc-constraints.d.ts +2 -1
- package/dist/analyzer/jsdoc-constraints.d.ts.map +1 -1
- package/dist/analyzer/tsdoc-parser.d.ts +13 -3
- package/dist/analyzer/tsdoc-parser.d.ts.map +1 -1
- package/dist/browser.cjs +30 -748
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.js +32 -748
- package/dist/browser.js.map +1 -1
- package/dist/build.d.ts +14 -14
- package/dist/cli.cjs +691 -1101
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +704 -1100
- package/dist/cli.js.map +1 -1
- package/dist/generators/class-schema.d.ts.map +1 -1
- package/dist/index.cjs +689 -1095
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +703 -1095
- package/dist/index.js.map +1 -1
- package/dist/internals.cjs +663 -1069
- package/dist/internals.cjs.map +1 -1
- package/dist/internals.js +675 -1067
- package/dist/internals.js.map +1 -1
- package/dist/ui-schema/schema.d.ts +14 -14
- package/dist/validate/constraint-validator.d.ts +6 -45
- package/dist/validate/constraint-validator.d.ts.map +1 -1
- package/package.json +2 -1
- package/dist/__tests__/json-utils.test.d.ts +0 -5
- package/dist/__tests__/json-utils.test.d.ts.map +0 -1
- package/dist/analyzer/json-utils.d.ts +0 -22
- package/dist/analyzer/json-utils.d.ts.map +0 -1
package/dist/browser.cjs
CHANGED
|
@@ -1181,760 +1181,42 @@ var jsonSchema7Schema = import_zod3.z.lazy(
|
|
|
1181
1181
|
);
|
|
1182
1182
|
|
|
1183
1183
|
// src/validate/constraint-validator.ts
|
|
1184
|
-
var
|
|
1185
|
-
function
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
}
|
|
1194
|
-
function addTypeMismatch(ctx, message, primary) {
|
|
1195
|
-
ctx.diagnostics.push({
|
|
1196
|
-
code: "TYPE_MISMATCH",
|
|
1197
|
-
message,
|
|
1198
|
-
severity: "error",
|
|
1199
|
-
primaryLocation: primary,
|
|
1200
|
-
relatedLocations: []
|
|
1201
|
-
});
|
|
1202
|
-
}
|
|
1203
|
-
function addUnknownExtension(ctx, message, primary) {
|
|
1204
|
-
ctx.diagnostics.push({
|
|
1205
|
-
code: "UNKNOWN_EXTENSION",
|
|
1206
|
-
message,
|
|
1207
|
-
severity: "warning",
|
|
1208
|
-
primaryLocation: primary,
|
|
1209
|
-
relatedLocations: []
|
|
1210
|
-
});
|
|
1211
|
-
}
|
|
1212
|
-
function addUnknownPathTarget(ctx, message, primary) {
|
|
1213
|
-
ctx.diagnostics.push({
|
|
1214
|
-
code: "UNKNOWN_PATH_TARGET",
|
|
1215
|
-
message,
|
|
1216
|
-
severity: "error",
|
|
1217
|
-
primaryLocation: primary,
|
|
1218
|
-
relatedLocations: []
|
|
1219
|
-
});
|
|
1220
|
-
}
|
|
1221
|
-
function addConstraintBroadening(ctx, message, primary, related) {
|
|
1222
|
-
ctx.diagnostics.push({
|
|
1223
|
-
code: "CONSTRAINT_BROADENING",
|
|
1224
|
-
message,
|
|
1225
|
-
severity: "error",
|
|
1226
|
-
primaryLocation: primary,
|
|
1227
|
-
relatedLocations: [related]
|
|
1228
|
-
});
|
|
1229
|
-
}
|
|
1230
|
-
function getExtensionIdFromConstraintId(constraintId) {
|
|
1231
|
-
const separator = constraintId.lastIndexOf("/");
|
|
1232
|
-
if (separator <= 0) {
|
|
1233
|
-
return null;
|
|
1234
|
-
}
|
|
1235
|
-
return constraintId.slice(0, separator);
|
|
1236
|
-
}
|
|
1237
|
-
function findNumeric(constraints, constraintKind) {
|
|
1238
|
-
return constraints.find((c) => c.constraintKind === constraintKind);
|
|
1239
|
-
}
|
|
1240
|
-
function findLength(constraints, constraintKind) {
|
|
1241
|
-
return constraints.find((c) => c.constraintKind === constraintKind);
|
|
1242
|
-
}
|
|
1243
|
-
function findAllowedMembers(constraints) {
|
|
1244
|
-
return constraints.filter(
|
|
1245
|
-
(c) => c.constraintKind === "allowedMembers"
|
|
1246
|
-
);
|
|
1247
|
-
}
|
|
1248
|
-
function findConstConstraints(constraints) {
|
|
1249
|
-
return constraints.filter(
|
|
1250
|
-
(c) => c.constraintKind === "const"
|
|
1251
|
-
);
|
|
1252
|
-
}
|
|
1253
|
-
function jsonValueEquals(left, right) {
|
|
1254
|
-
if (left === right) {
|
|
1255
|
-
return true;
|
|
1256
|
-
}
|
|
1257
|
-
if (Array.isArray(left) || Array.isArray(right)) {
|
|
1258
|
-
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
|
|
1259
|
-
return false;
|
|
1260
|
-
}
|
|
1261
|
-
return left.every((item, index) => jsonValueEquals(item, right[index]));
|
|
1262
|
-
}
|
|
1263
|
-
if (isJsonObject(left) || isJsonObject(right)) {
|
|
1264
|
-
if (!isJsonObject(left) || !isJsonObject(right)) {
|
|
1265
|
-
return false;
|
|
1266
|
-
}
|
|
1267
|
-
const leftKeys = Object.keys(left).sort();
|
|
1268
|
-
const rightKeys = Object.keys(right).sort();
|
|
1269
|
-
if (leftKeys.length !== rightKeys.length) {
|
|
1270
|
-
return false;
|
|
1271
|
-
}
|
|
1272
|
-
return leftKeys.every((key, index) => {
|
|
1273
|
-
const rightKey = rightKeys[index];
|
|
1274
|
-
if (rightKey !== key) {
|
|
1275
|
-
return false;
|
|
1276
|
-
}
|
|
1277
|
-
const leftValue = left[key];
|
|
1278
|
-
const rightValue = right[rightKey];
|
|
1279
|
-
return leftValue !== void 0 && rightValue !== void 0 && jsonValueEquals(leftValue, rightValue);
|
|
1280
|
-
});
|
|
1281
|
-
}
|
|
1282
|
-
return false;
|
|
1283
|
-
}
|
|
1284
|
-
function isJsonObject(value) {
|
|
1285
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1286
|
-
}
|
|
1287
|
-
function isOrderedBoundConstraint(constraint) {
|
|
1288
|
-
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";
|
|
1289
|
-
}
|
|
1290
|
-
function pathKey(constraint) {
|
|
1291
|
-
return constraint.path?.segments.join(".") ?? "";
|
|
1292
|
-
}
|
|
1293
|
-
function orderedBoundFamily(kind) {
|
|
1294
|
-
switch (kind) {
|
|
1295
|
-
case "minimum":
|
|
1296
|
-
case "exclusiveMinimum":
|
|
1297
|
-
return "numeric-lower";
|
|
1298
|
-
case "maximum":
|
|
1299
|
-
case "exclusiveMaximum":
|
|
1300
|
-
return "numeric-upper";
|
|
1301
|
-
case "minLength":
|
|
1302
|
-
return "minLength";
|
|
1303
|
-
case "minItems":
|
|
1304
|
-
return "minItems";
|
|
1305
|
-
case "maxLength":
|
|
1306
|
-
return "maxLength";
|
|
1307
|
-
case "maxItems":
|
|
1308
|
-
return "maxItems";
|
|
1309
|
-
default: {
|
|
1310
|
-
const _exhaustive = kind;
|
|
1311
|
-
return _exhaustive;
|
|
1312
|
-
}
|
|
1313
|
-
}
|
|
1314
|
-
}
|
|
1315
|
-
function isNumericLowerKind(kind) {
|
|
1316
|
-
return kind === "minimum" || kind === "exclusiveMinimum";
|
|
1317
|
-
}
|
|
1318
|
-
function isNumericUpperKind(kind) {
|
|
1319
|
-
return kind === "maximum" || kind === "exclusiveMaximum";
|
|
1320
|
-
}
|
|
1321
|
-
function describeConstraintTag(constraint) {
|
|
1322
|
-
return `@${constraint.constraintKind}`;
|
|
1323
|
-
}
|
|
1324
|
-
function compareConstraintStrength(current, previous) {
|
|
1325
|
-
const family = orderedBoundFamily(current.constraintKind);
|
|
1326
|
-
if (family === "numeric-lower") {
|
|
1327
|
-
if (!isNumericLowerKind(current.constraintKind) || !isNumericLowerKind(previous.constraintKind)) {
|
|
1328
|
-
throw new Error("numeric-lower family received non-numeric lower-bound constraint");
|
|
1329
|
-
}
|
|
1330
|
-
if (current.value !== previous.value) {
|
|
1331
|
-
return current.value > previous.value ? 1 : -1;
|
|
1332
|
-
}
|
|
1333
|
-
if (current.constraintKind === "exclusiveMinimum" && previous.constraintKind === "minimum") {
|
|
1334
|
-
return 1;
|
|
1335
|
-
}
|
|
1336
|
-
if (current.constraintKind === "minimum" && previous.constraintKind === "exclusiveMinimum") {
|
|
1337
|
-
return -1;
|
|
1338
|
-
}
|
|
1339
|
-
return 0;
|
|
1340
|
-
}
|
|
1341
|
-
if (family === "numeric-upper") {
|
|
1342
|
-
if (!isNumericUpperKind(current.constraintKind) || !isNumericUpperKind(previous.constraintKind)) {
|
|
1343
|
-
throw new Error("numeric-upper family received non-numeric upper-bound constraint");
|
|
1344
|
-
}
|
|
1345
|
-
if (current.value !== previous.value) {
|
|
1346
|
-
return current.value < previous.value ? 1 : -1;
|
|
1347
|
-
}
|
|
1348
|
-
if (current.constraintKind === "exclusiveMaximum" && previous.constraintKind === "maximum") {
|
|
1349
|
-
return 1;
|
|
1350
|
-
}
|
|
1351
|
-
if (current.constraintKind === "maximum" && previous.constraintKind === "exclusiveMaximum") {
|
|
1352
|
-
return -1;
|
|
1353
|
-
}
|
|
1354
|
-
return 0;
|
|
1355
|
-
}
|
|
1356
|
-
switch (family) {
|
|
1357
|
-
case "minLength":
|
|
1358
|
-
case "minItems":
|
|
1359
|
-
if (current.value === previous.value) {
|
|
1360
|
-
return 0;
|
|
1361
|
-
}
|
|
1362
|
-
return current.value > previous.value ? 1 : -1;
|
|
1363
|
-
case "maxLength":
|
|
1364
|
-
case "maxItems":
|
|
1365
|
-
if (current.value === previous.value) {
|
|
1366
|
-
return 0;
|
|
1367
|
-
}
|
|
1368
|
-
return current.value < previous.value ? 1 : -1;
|
|
1369
|
-
default: {
|
|
1370
|
-
const _exhaustive = family;
|
|
1371
|
-
return _exhaustive;
|
|
1372
|
-
}
|
|
1373
|
-
}
|
|
1374
|
-
}
|
|
1375
|
-
function checkConstraintBroadening(ctx, fieldName, constraints) {
|
|
1376
|
-
const strongestByKey = /* @__PURE__ */ new Map();
|
|
1377
|
-
for (const constraint of constraints) {
|
|
1378
|
-
if (!isOrderedBoundConstraint(constraint)) {
|
|
1379
|
-
continue;
|
|
1380
|
-
}
|
|
1381
|
-
const key = `${orderedBoundFamily(constraint.constraintKind)}:${pathKey(constraint)}`;
|
|
1382
|
-
const previous = strongestByKey.get(key);
|
|
1383
|
-
if (previous === void 0) {
|
|
1384
|
-
strongestByKey.set(key, constraint);
|
|
1385
|
-
continue;
|
|
1386
|
-
}
|
|
1387
|
-
const strength = compareConstraintStrength(constraint, previous);
|
|
1388
|
-
if (strength < 0) {
|
|
1389
|
-
const displayFieldName = formatPathTargetFieldName(
|
|
1390
|
-
fieldName,
|
|
1391
|
-
constraint.path?.segments ?? []
|
|
1392
|
-
);
|
|
1393
|
-
addConstraintBroadening(
|
|
1394
|
-
ctx,
|
|
1395
|
-
`Field "${displayFieldName}": ${describeConstraintTag(constraint)} (${String(constraint.value)}) is broader than earlier ${describeConstraintTag(previous)} (${String(previous.value)}). Constraints can only narrow.`,
|
|
1396
|
-
constraint.provenance,
|
|
1397
|
-
previous.provenance
|
|
1398
|
-
);
|
|
1399
|
-
continue;
|
|
1400
|
-
}
|
|
1401
|
-
if (strength <= 0) {
|
|
1402
|
-
continue;
|
|
1403
|
-
}
|
|
1404
|
-
strongestByKey.set(key, constraint);
|
|
1405
|
-
}
|
|
1406
|
-
}
|
|
1407
|
-
function compareCustomConstraintStrength(current, previous) {
|
|
1408
|
-
const order = current.comparePayloads(current.constraint.payload, previous.constraint.payload);
|
|
1409
|
-
const equalPayloadTiebreaker = order === 0 ? compareSemanticInclusivity(current.role.inclusive, previous.role.inclusive) : order;
|
|
1410
|
-
switch (current.role.bound) {
|
|
1411
|
-
case "lower":
|
|
1412
|
-
return equalPayloadTiebreaker;
|
|
1413
|
-
case "upper":
|
|
1414
|
-
return equalPayloadTiebreaker === 0 ? 0 : -equalPayloadTiebreaker;
|
|
1415
|
-
case "exact":
|
|
1416
|
-
return order === 0 ? 0 : Number.NaN;
|
|
1417
|
-
default: {
|
|
1418
|
-
const _exhaustive = current.role.bound;
|
|
1419
|
-
return _exhaustive;
|
|
1420
|
-
}
|
|
1421
|
-
}
|
|
1422
|
-
}
|
|
1423
|
-
function compareSemanticInclusivity(currentInclusive, previousInclusive) {
|
|
1424
|
-
if (currentInclusive === previousInclusive) {
|
|
1425
|
-
return 0;
|
|
1426
|
-
}
|
|
1427
|
-
return currentInclusive ? -1 : 1;
|
|
1428
|
-
}
|
|
1429
|
-
function customConstraintsContradict(lower, upper) {
|
|
1430
|
-
const order = lower.comparePayloads(lower.constraint.payload, upper.constraint.payload);
|
|
1431
|
-
if (order > 0) {
|
|
1432
|
-
return true;
|
|
1433
|
-
}
|
|
1434
|
-
if (order < 0) {
|
|
1435
|
-
return false;
|
|
1436
|
-
}
|
|
1437
|
-
return !lower.role.inclusive || !upper.role.inclusive;
|
|
1438
|
-
}
|
|
1439
|
-
function describeCustomConstraintTag(constraint) {
|
|
1440
|
-
return constraint.provenance.tagName ?? constraint.constraintId;
|
|
1441
|
-
}
|
|
1442
|
-
function checkCustomConstraintSemantics(ctx, fieldName, constraints) {
|
|
1443
|
-
if (ctx.extensionRegistry === void 0) {
|
|
1444
|
-
return;
|
|
1445
|
-
}
|
|
1446
|
-
const strongestByKey = /* @__PURE__ */ new Map();
|
|
1447
|
-
const lowerByFamily = /* @__PURE__ */ new Map();
|
|
1448
|
-
const upperByFamily = /* @__PURE__ */ new Map();
|
|
1449
|
-
for (const constraint of constraints) {
|
|
1450
|
-
if (constraint.constraintKind !== "custom") {
|
|
1451
|
-
continue;
|
|
1452
|
-
}
|
|
1453
|
-
const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
|
|
1454
|
-
if (registration?.comparePayloads === void 0 || registration.semanticRole === void 0) {
|
|
1455
|
-
continue;
|
|
1456
|
-
}
|
|
1457
|
-
const entry = {
|
|
1458
|
-
constraint,
|
|
1459
|
-
comparePayloads: registration.comparePayloads,
|
|
1460
|
-
role: registration.semanticRole
|
|
1461
|
-
};
|
|
1462
|
-
const familyKey = `${registration.semanticRole.family}:${pathKey(constraint)}`;
|
|
1463
|
-
const boundKey = `${familyKey}:${registration.semanticRole.bound}`;
|
|
1464
|
-
const previous = strongestByKey.get(boundKey);
|
|
1465
|
-
if (previous !== void 0) {
|
|
1466
|
-
const strength = compareCustomConstraintStrength(entry, previous);
|
|
1467
|
-
if (Number.isNaN(strength)) {
|
|
1468
|
-
addContradiction(
|
|
1469
|
-
ctx,
|
|
1470
|
-
`Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} conflicts with ${describeCustomConstraintTag(previous.constraint)}`,
|
|
1471
|
-
constraint.provenance,
|
|
1472
|
-
previous.constraint.provenance
|
|
1473
|
-
);
|
|
1474
|
-
continue;
|
|
1475
|
-
}
|
|
1476
|
-
if (strength < 0) {
|
|
1477
|
-
addConstraintBroadening(
|
|
1478
|
-
ctx,
|
|
1479
|
-
`Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} is broader than earlier ${describeCustomConstraintTag(previous.constraint)}. Constraints can only narrow.`,
|
|
1480
|
-
constraint.provenance,
|
|
1481
|
-
previous.constraint.provenance
|
|
1482
|
-
);
|
|
1483
|
-
continue;
|
|
1484
|
-
}
|
|
1485
|
-
if (strength > 0) {
|
|
1486
|
-
strongestByKey.set(boundKey, entry);
|
|
1487
|
-
}
|
|
1488
|
-
} else {
|
|
1489
|
-
strongestByKey.set(boundKey, entry);
|
|
1490
|
-
}
|
|
1491
|
-
if (registration.semanticRole.bound === "lower") {
|
|
1492
|
-
lowerByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
|
|
1493
|
-
} else if (registration.semanticRole.bound === "upper") {
|
|
1494
|
-
upperByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
|
|
1495
|
-
}
|
|
1496
|
-
}
|
|
1497
|
-
for (const [familyKey, lower] of lowerByFamily) {
|
|
1498
|
-
const upper = upperByFamily.get(familyKey);
|
|
1499
|
-
if (upper === void 0) {
|
|
1500
|
-
continue;
|
|
1501
|
-
}
|
|
1502
|
-
if (!customConstraintsContradict(lower, upper)) {
|
|
1503
|
-
continue;
|
|
1504
|
-
}
|
|
1505
|
-
addContradiction(
|
|
1506
|
-
ctx,
|
|
1507
|
-
`Field "${formatPathTargetFieldName(fieldName, lower.constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(lower.constraint)} contradicts ${describeCustomConstraintTag(upper.constraint)}`,
|
|
1508
|
-
lower.constraint.provenance,
|
|
1509
|
-
upper.constraint.provenance
|
|
1510
|
-
);
|
|
1511
|
-
}
|
|
1512
|
-
}
|
|
1513
|
-
function checkNumericContradictions(ctx, fieldName, constraints) {
|
|
1514
|
-
const min = findNumeric(constraints, "minimum");
|
|
1515
|
-
const max = findNumeric(constraints, "maximum");
|
|
1516
|
-
const exMin = findNumeric(constraints, "exclusiveMinimum");
|
|
1517
|
-
const exMax = findNumeric(constraints, "exclusiveMaximum");
|
|
1518
|
-
if (min !== void 0 && max !== void 0 && min.value > max.value) {
|
|
1519
|
-
addContradiction(
|
|
1520
|
-
ctx,
|
|
1521
|
-
`Field "${fieldName}": minimum (${String(min.value)}) is greater than maximum (${String(max.value)})`,
|
|
1522
|
-
min.provenance,
|
|
1523
|
-
max.provenance
|
|
1524
|
-
);
|
|
1525
|
-
}
|
|
1526
|
-
if (exMin !== void 0 && max !== void 0 && exMin.value >= max.value) {
|
|
1527
|
-
addContradiction(
|
|
1528
|
-
ctx,
|
|
1529
|
-
`Field "${fieldName}": exclusiveMinimum (${String(exMin.value)}) is greater than or equal to maximum (${String(max.value)})`,
|
|
1530
|
-
exMin.provenance,
|
|
1531
|
-
max.provenance
|
|
1532
|
-
);
|
|
1533
|
-
}
|
|
1534
|
-
if (min !== void 0 && exMax !== void 0 && min.value >= exMax.value) {
|
|
1535
|
-
addContradiction(
|
|
1536
|
-
ctx,
|
|
1537
|
-
`Field "${fieldName}": minimum (${String(min.value)}) is greater than or equal to exclusiveMaximum (${String(exMax.value)})`,
|
|
1538
|
-
min.provenance,
|
|
1539
|
-
exMax.provenance
|
|
1540
|
-
);
|
|
1541
|
-
}
|
|
1542
|
-
if (exMin !== void 0 && exMax !== void 0 && exMin.value >= exMax.value) {
|
|
1543
|
-
addContradiction(
|
|
1544
|
-
ctx,
|
|
1545
|
-
`Field "${fieldName}": exclusiveMinimum (${String(exMin.value)}) is greater than or equal to exclusiveMaximum (${String(exMax.value)})`,
|
|
1546
|
-
exMin.provenance,
|
|
1547
|
-
exMax.provenance
|
|
1548
|
-
);
|
|
1549
|
-
}
|
|
1550
|
-
}
|
|
1551
|
-
function checkLengthContradictions(ctx, fieldName, constraints) {
|
|
1552
|
-
const minLen = findLength(constraints, "minLength");
|
|
1553
|
-
const maxLen = findLength(constraints, "maxLength");
|
|
1554
|
-
if (minLen !== void 0 && maxLen !== void 0 && minLen.value > maxLen.value) {
|
|
1555
|
-
addContradiction(
|
|
1556
|
-
ctx,
|
|
1557
|
-
`Field "${fieldName}": minLength (${String(minLen.value)}) is greater than maxLength (${String(maxLen.value)})`,
|
|
1558
|
-
minLen.provenance,
|
|
1559
|
-
maxLen.provenance
|
|
1560
|
-
);
|
|
1561
|
-
}
|
|
1562
|
-
const minItems = findLength(constraints, "minItems");
|
|
1563
|
-
const maxItems = findLength(constraints, "maxItems");
|
|
1564
|
-
if (minItems !== void 0 && maxItems !== void 0 && minItems.value > maxItems.value) {
|
|
1565
|
-
addContradiction(
|
|
1566
|
-
ctx,
|
|
1567
|
-
`Field "${fieldName}": minItems (${String(minItems.value)}) is greater than maxItems (${String(maxItems.value)})`,
|
|
1568
|
-
minItems.provenance,
|
|
1569
|
-
maxItems.provenance
|
|
1570
|
-
);
|
|
1571
|
-
}
|
|
1572
|
-
}
|
|
1573
|
-
function checkAllowedMembersContradiction(ctx, fieldName, constraints) {
|
|
1574
|
-
const members = findAllowedMembers(constraints);
|
|
1575
|
-
if (members.length < 2) return;
|
|
1576
|
-
const firstSet = new Set(members[0]?.members ?? []);
|
|
1577
|
-
for (let i = 1; i < members.length; i++) {
|
|
1578
|
-
const current = members[i];
|
|
1579
|
-
if (current === void 0) continue;
|
|
1580
|
-
for (const m of firstSet) {
|
|
1581
|
-
if (!current.members.includes(m)) {
|
|
1582
|
-
firstSet.delete(m);
|
|
1583
|
-
}
|
|
1584
|
-
}
|
|
1585
|
-
}
|
|
1586
|
-
if (firstSet.size === 0) {
|
|
1587
|
-
const first = members[0];
|
|
1588
|
-
const second = members[1];
|
|
1589
|
-
if (first !== void 0 && second !== void 0) {
|
|
1590
|
-
addContradiction(
|
|
1591
|
-
ctx,
|
|
1592
|
-
`Field "${fieldName}": allowedMembers constraints have an empty intersection (no valid values remain)`,
|
|
1593
|
-
first.provenance,
|
|
1594
|
-
second.provenance
|
|
1595
|
-
);
|
|
1596
|
-
}
|
|
1597
|
-
}
|
|
1598
|
-
}
|
|
1599
|
-
function checkConstContradictions(ctx, fieldName, constraints) {
|
|
1600
|
-
const constConstraints = findConstConstraints(constraints);
|
|
1601
|
-
if (constConstraints.length < 2) return;
|
|
1602
|
-
const first = constConstraints[0];
|
|
1603
|
-
if (first === void 0) return;
|
|
1604
|
-
for (let i = 1; i < constConstraints.length; i++) {
|
|
1605
|
-
const current = constConstraints[i];
|
|
1606
|
-
if (current === void 0) continue;
|
|
1607
|
-
if (jsonValueEquals(first.value, current.value)) {
|
|
1608
|
-
continue;
|
|
1609
|
-
}
|
|
1610
|
-
addContradiction(
|
|
1611
|
-
ctx,
|
|
1612
|
-
`Field "${fieldName}": conflicting @const constraints require both ${JSON.stringify(first.value)} and ${JSON.stringify(current.value)}`,
|
|
1613
|
-
first.provenance,
|
|
1614
|
-
current.provenance
|
|
1615
|
-
);
|
|
1616
|
-
}
|
|
1617
|
-
}
|
|
1618
|
-
function typeLabel(type) {
|
|
1619
|
-
switch (type.kind) {
|
|
1620
|
-
case "primitive":
|
|
1621
|
-
return type.primitiveKind;
|
|
1622
|
-
case "enum":
|
|
1623
|
-
return "enum";
|
|
1624
|
-
case "array":
|
|
1625
|
-
return "array";
|
|
1626
|
-
case "object":
|
|
1627
|
-
return "object";
|
|
1628
|
-
case "record":
|
|
1629
|
-
return "record";
|
|
1630
|
-
case "union":
|
|
1631
|
-
return "union";
|
|
1632
|
-
case "reference":
|
|
1633
|
-
return `reference(${type.name})`;
|
|
1634
|
-
case "dynamic":
|
|
1635
|
-
return `dynamic(${type.dynamicKind})`;
|
|
1636
|
-
case "custom":
|
|
1637
|
-
return `custom(${type.typeId})`;
|
|
1638
|
-
default: {
|
|
1639
|
-
const _exhaustive = type;
|
|
1640
|
-
return String(_exhaustive);
|
|
1641
|
-
}
|
|
1642
|
-
}
|
|
1643
|
-
}
|
|
1644
|
-
function dereferenceType(ctx, type) {
|
|
1645
|
-
let current = type;
|
|
1646
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1647
|
-
while (current.kind === "reference") {
|
|
1648
|
-
if (seen.has(current.name)) {
|
|
1649
|
-
return current;
|
|
1650
|
-
}
|
|
1651
|
-
seen.add(current.name);
|
|
1652
|
-
const definition = ctx.typeRegistry[current.name];
|
|
1653
|
-
if (definition === void 0) {
|
|
1654
|
-
return current;
|
|
1655
|
-
}
|
|
1656
|
-
current = definition.type;
|
|
1657
|
-
}
|
|
1658
|
-
return current;
|
|
1659
|
-
}
|
|
1660
|
-
function collectReferencedTypeConstraints(ctx, type) {
|
|
1661
|
-
const collected = [];
|
|
1662
|
-
let current = type;
|
|
1663
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1664
|
-
while (current.kind === "reference") {
|
|
1665
|
-
if (seen.has(current.name)) {
|
|
1666
|
-
break;
|
|
1667
|
-
}
|
|
1668
|
-
seen.add(current.name);
|
|
1669
|
-
const definition = ctx.typeRegistry[current.name];
|
|
1670
|
-
if (definition === void 0) {
|
|
1671
|
-
break;
|
|
1672
|
-
}
|
|
1673
|
-
if (definition.constraints !== void 0) {
|
|
1674
|
-
collected.push(...definition.constraints);
|
|
1675
|
-
}
|
|
1676
|
-
current = definition.type;
|
|
1677
|
-
}
|
|
1678
|
-
return collected;
|
|
1679
|
-
}
|
|
1680
|
-
function resolvePathTargetType(ctx, type, segments) {
|
|
1681
|
-
const effectiveType = dereferenceType(ctx, type);
|
|
1682
|
-
if (segments.length === 0) {
|
|
1683
|
-
return { kind: "resolved", type: effectiveType };
|
|
1684
|
-
}
|
|
1685
|
-
if (effectiveType.kind === "array") {
|
|
1686
|
-
return resolvePathTargetType(ctx, effectiveType.items, segments);
|
|
1687
|
-
}
|
|
1688
|
-
if (effectiveType.kind === "object") {
|
|
1689
|
-
const [segment, ...rest] = segments;
|
|
1690
|
-
if (segment === void 0) {
|
|
1691
|
-
throw new Error("Invariant violation: object path traversal requires a segment");
|
|
1692
|
-
}
|
|
1693
|
-
const property = effectiveType.properties.find((prop) => prop.name === segment);
|
|
1694
|
-
if (property === void 0) {
|
|
1695
|
-
return { kind: "missing-property", segment };
|
|
1696
|
-
}
|
|
1697
|
-
return resolvePathTargetType(ctx, property.type, rest);
|
|
1698
|
-
}
|
|
1699
|
-
return { kind: "unresolvable", type: effectiveType };
|
|
1700
|
-
}
|
|
1701
|
-
function isNullType(type) {
|
|
1702
|
-
return type.kind === "primitive" && type.primitiveKind === "null";
|
|
1703
|
-
}
|
|
1704
|
-
function collectCustomConstraintCandidateTypes(ctx, type) {
|
|
1705
|
-
const effectiveType = dereferenceType(ctx, type);
|
|
1706
|
-
const candidates = [effectiveType];
|
|
1707
|
-
if (effectiveType.kind === "array") {
|
|
1708
|
-
candidates.push(...collectCustomConstraintCandidateTypes(ctx, effectiveType.items));
|
|
1709
|
-
}
|
|
1710
|
-
if (effectiveType.kind === "union") {
|
|
1711
|
-
const memberTypes = effectiveType.members.map((member) => dereferenceType(ctx, member));
|
|
1712
|
-
const nonNullMembers = memberTypes.filter((member) => !isNullType(member));
|
|
1713
|
-
if (nonNullMembers.length === 1 && nonNullMembers.length < memberTypes.length) {
|
|
1714
|
-
const [nullableMember] = nonNullMembers;
|
|
1715
|
-
if (nullableMember !== void 0) {
|
|
1716
|
-
candidates.push(...collectCustomConstraintCandidateTypes(ctx, nullableMember));
|
|
1717
|
-
}
|
|
1718
|
-
}
|
|
1719
|
-
}
|
|
1720
|
-
return candidates;
|
|
1721
|
-
}
|
|
1722
|
-
function formatPathTargetFieldName(fieldName, path) {
|
|
1723
|
-
return path.length === 0 ? fieldName : `${fieldName}.${path.join(".")}`;
|
|
1724
|
-
}
|
|
1725
|
-
function checkConstraintOnType(ctx, fieldName, type, constraint) {
|
|
1726
|
-
const effectiveType = dereferenceType(ctx, type);
|
|
1727
|
-
const isNumber = effectiveType.kind === "primitive" && ["number", "integer", "bigint"].includes(effectiveType.primitiveKind);
|
|
1728
|
-
const isString = effectiveType.kind === "primitive" && effectiveType.primitiveKind === "string";
|
|
1729
|
-
const isArray = effectiveType.kind === "array";
|
|
1730
|
-
const isEnum = effectiveType.kind === "enum";
|
|
1731
|
-
const arrayItemType = effectiveType.kind === "array" ? dereferenceType(ctx, effectiveType.items) : void 0;
|
|
1732
|
-
const isStringArray = arrayItemType?.kind === "primitive" && arrayItemType.primitiveKind === "string";
|
|
1733
|
-
const label = typeLabel(effectiveType);
|
|
1734
|
-
const ck = constraint.constraintKind;
|
|
1735
|
-
switch (ck) {
|
|
1736
|
-
case "minimum":
|
|
1737
|
-
case "maximum":
|
|
1738
|
-
case "exclusiveMinimum":
|
|
1739
|
-
case "exclusiveMaximum":
|
|
1740
|
-
case "multipleOf": {
|
|
1741
|
-
if (!isNumber) {
|
|
1742
|
-
addTypeMismatch(
|
|
1743
|
-
ctx,
|
|
1744
|
-
`Field "${fieldName}": constraint "${ck}" is only valid on number fields, but field type is "${label}"`,
|
|
1745
|
-
constraint.provenance
|
|
1746
|
-
);
|
|
1747
|
-
}
|
|
1748
|
-
break;
|
|
1749
|
-
}
|
|
1750
|
-
case "minLength":
|
|
1751
|
-
case "maxLength":
|
|
1752
|
-
case "pattern": {
|
|
1753
|
-
if (!isString && !isStringArray) {
|
|
1754
|
-
addTypeMismatch(
|
|
1755
|
-
ctx,
|
|
1756
|
-
`Field "${fieldName}": constraint "${ck}" is only valid on string fields or string array items, but field type is "${label}"`,
|
|
1757
|
-
constraint.provenance
|
|
1758
|
-
);
|
|
1759
|
-
}
|
|
1760
|
-
break;
|
|
1761
|
-
}
|
|
1762
|
-
case "minItems":
|
|
1763
|
-
case "maxItems":
|
|
1764
|
-
case "uniqueItems": {
|
|
1765
|
-
if (!isArray) {
|
|
1766
|
-
addTypeMismatch(
|
|
1767
|
-
ctx,
|
|
1768
|
-
`Field "${fieldName}": constraint "${ck}" is only valid on array fields, but field type is "${label}"`,
|
|
1769
|
-
constraint.provenance
|
|
1770
|
-
);
|
|
1771
|
-
}
|
|
1772
|
-
break;
|
|
1773
|
-
}
|
|
1774
|
-
case "allowedMembers": {
|
|
1775
|
-
if (!isEnum) {
|
|
1776
|
-
addTypeMismatch(
|
|
1777
|
-
ctx,
|
|
1778
|
-
`Field "${fieldName}": constraint "allowedMembers" is only valid on enum fields, but field type is "${label}"`,
|
|
1779
|
-
constraint.provenance
|
|
1780
|
-
);
|
|
1781
|
-
}
|
|
1782
|
-
break;
|
|
1783
|
-
}
|
|
1784
|
-
case "const": {
|
|
1785
|
-
const isPrimitiveConstType = effectiveType.kind === "primitive" && ["string", "number", "integer", "bigint", "boolean", "null"].includes(
|
|
1786
|
-
effectiveType.primitiveKind
|
|
1787
|
-
) || effectiveType.kind === "enum";
|
|
1788
|
-
if (!isPrimitiveConstType) {
|
|
1789
|
-
addTypeMismatch(
|
|
1790
|
-
ctx,
|
|
1791
|
-
`Field "${fieldName}": constraint "const" is only valid on primitive or enum fields, but field type is "${label}"`,
|
|
1792
|
-
constraint.provenance
|
|
1793
|
-
);
|
|
1794
|
-
break;
|
|
1795
|
-
}
|
|
1796
|
-
if (effectiveType.kind === "primitive") {
|
|
1797
|
-
const valueType = constraint.value === null ? "null" : Array.isArray(constraint.value) ? "array" : typeof constraint.value;
|
|
1798
|
-
const expectedValueType = effectiveType.primitiveKind === "integer" || effectiveType.primitiveKind === "bigint" ? "number" : effectiveType.primitiveKind;
|
|
1799
|
-
if (valueType !== expectedValueType) {
|
|
1800
|
-
addTypeMismatch(
|
|
1801
|
-
ctx,
|
|
1802
|
-
`Field "${fieldName}": @const value type "${valueType}" is incompatible with field type "${effectiveType.primitiveKind}"`,
|
|
1803
|
-
constraint.provenance
|
|
1804
|
-
);
|
|
1805
|
-
}
|
|
1806
|
-
break;
|
|
1807
|
-
}
|
|
1808
|
-
const memberValues = effectiveType.members.map((member) => member.value);
|
|
1809
|
-
if (!memberValues.some((member) => jsonValueEquals(member, constraint.value))) {
|
|
1810
|
-
addTypeMismatch(
|
|
1811
|
-
ctx,
|
|
1812
|
-
`Field "${fieldName}": @const value ${JSON.stringify(constraint.value)} is not one of the enum members`,
|
|
1813
|
-
constraint.provenance
|
|
1814
|
-
);
|
|
1815
|
-
}
|
|
1816
|
-
break;
|
|
1817
|
-
}
|
|
1818
|
-
case "custom": {
|
|
1819
|
-
checkCustomConstraint(ctx, fieldName, effectiveType, constraint);
|
|
1820
|
-
break;
|
|
1821
|
-
}
|
|
1822
|
-
default: {
|
|
1823
|
-
const _exhaustive = constraint;
|
|
1824
|
-
throw new Error(
|
|
1825
|
-
`Unhandled constraint kind: ${_exhaustive.constraintKind}`
|
|
1826
|
-
);
|
|
1827
|
-
}
|
|
1828
|
-
}
|
|
1829
|
-
}
|
|
1830
|
-
function checkTypeApplicability(ctx, fieldName, type, constraints) {
|
|
1831
|
-
for (const constraint of constraints) {
|
|
1832
|
-
if (constraint.path) {
|
|
1833
|
-
const resolution = resolvePathTargetType(ctx, type, constraint.path.segments);
|
|
1834
|
-
const targetFieldName = formatPathTargetFieldName(fieldName, constraint.path.segments);
|
|
1835
|
-
if (resolution.kind === "missing-property") {
|
|
1836
|
-
addUnknownPathTarget(
|
|
1837
|
-
ctx,
|
|
1838
|
-
`Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" references unknown path segment "${resolution.segment}"`,
|
|
1839
|
-
constraint.provenance
|
|
1840
|
-
);
|
|
1841
|
-
continue;
|
|
1842
|
-
}
|
|
1843
|
-
if (resolution.kind === "unresolvable") {
|
|
1844
|
-
addTypeMismatch(
|
|
1845
|
-
ctx,
|
|
1846
|
-
`Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" is invalid because type "${typeLabel(resolution.type)}" cannot be traversed`,
|
|
1847
|
-
constraint.provenance
|
|
1848
|
-
);
|
|
1849
|
-
continue;
|
|
1850
|
-
}
|
|
1851
|
-
checkConstraintOnType(ctx, targetFieldName, resolution.type, constraint);
|
|
1852
|
-
continue;
|
|
1853
|
-
}
|
|
1854
|
-
checkConstraintOnType(ctx, fieldName, type, constraint);
|
|
1855
|
-
}
|
|
1856
|
-
}
|
|
1857
|
-
function checkCustomConstraint(ctx, fieldName, type, constraint) {
|
|
1858
|
-
if (ctx.extensionRegistry === void 0) return;
|
|
1859
|
-
const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
|
|
1860
|
-
if (registration === void 0) {
|
|
1861
|
-
addUnknownExtension(
|
|
1862
|
-
ctx,
|
|
1863
|
-
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not registered in the extension registry`,
|
|
1864
|
-
constraint.provenance
|
|
1865
|
-
);
|
|
1866
|
-
return;
|
|
1867
|
-
}
|
|
1868
|
-
const candidateTypes = collectCustomConstraintCandidateTypes(ctx, type);
|
|
1869
|
-
const normalizedTagName = constraint.provenance.tagName === void 0 ? void 0 : (0, import_core3.normalizeConstraintTagName)(constraint.provenance.tagName.replace(/^@/, ""));
|
|
1870
|
-
if (normalizedTagName !== void 0) {
|
|
1871
|
-
const tagRegistration = ctx.extensionRegistry.findConstraintTag(normalizedTagName);
|
|
1872
|
-
const extensionId = getExtensionIdFromConstraintId(constraint.constraintId);
|
|
1873
|
-
if (extensionId !== null && tagRegistration?.extensionId === extensionId && tagRegistration.registration.constraintName === registration.constraintName && !candidateTypes.some(
|
|
1874
|
-
(candidateType) => tagRegistration.registration.isApplicableToType?.(candidateType) !== false
|
|
1875
|
-
)) {
|
|
1876
|
-
addTypeMismatch(
|
|
1877
|
-
ctx,
|
|
1878
|
-
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
1879
|
-
constraint.provenance
|
|
1880
|
-
);
|
|
1881
|
-
return;
|
|
1882
|
-
}
|
|
1883
|
-
}
|
|
1884
|
-
if (registration.applicableTypes === null) {
|
|
1885
|
-
if (!candidateTypes.some((candidateType) => registration.isApplicableToType?.(candidateType) !== false)) {
|
|
1886
|
-
addTypeMismatch(
|
|
1887
|
-
ctx,
|
|
1888
|
-
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
1889
|
-
constraint.provenance
|
|
1890
|
-
);
|
|
1184
|
+
var import_analysis = require("@formspec/analysis");
|
|
1185
|
+
function validateFieldNode(ctx, field) {
|
|
1186
|
+
const analysis = (0, import_analysis.analyzeConstraintTargets)(
|
|
1187
|
+
field.name,
|
|
1188
|
+
field.type,
|
|
1189
|
+
field.constraints,
|
|
1190
|
+
ctx.typeRegistry,
|
|
1191
|
+
ctx.extensionRegistry === void 0 ? void 0 : {
|
|
1192
|
+
extensionRegistry: ctx.extensionRegistry
|
|
1891
1193
|
}
|
|
1892
|
-
return;
|
|
1893
|
-
}
|
|
1894
|
-
const applicableTypes = registration.applicableTypes;
|
|
1895
|
-
const matchesApplicableType = candidateTypes.some(
|
|
1896
|
-
(candidateType) => applicableTypes.includes(candidateType.kind) && registration.isApplicableToType?.(candidateType) !== false
|
|
1897
1194
|
);
|
|
1898
|
-
|
|
1899
|
-
addTypeMismatch(
|
|
1900
|
-
ctx,
|
|
1901
|
-
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
1902
|
-
constraint.provenance
|
|
1903
|
-
);
|
|
1904
|
-
}
|
|
1905
|
-
}
|
|
1906
|
-
function validateFieldNode(ctx, field) {
|
|
1907
|
-
validateConstraints(ctx, field.name, field.type, [
|
|
1908
|
-
...collectReferencedTypeConstraints(ctx, field.type),
|
|
1909
|
-
...field.constraints
|
|
1910
|
-
]);
|
|
1195
|
+
ctx.diagnostics.push(...analysis.diagnostics);
|
|
1911
1196
|
if (field.type.kind === "object") {
|
|
1912
|
-
for (const
|
|
1913
|
-
validateObjectProperty(ctx, field.name,
|
|
1197
|
+
for (const property of field.type.properties) {
|
|
1198
|
+
validateObjectProperty(ctx, field.name, property);
|
|
1914
1199
|
}
|
|
1915
1200
|
}
|
|
1916
1201
|
}
|
|
1917
|
-
function validateObjectProperty(ctx, parentName,
|
|
1918
|
-
const qualifiedName = `${parentName}.${
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1202
|
+
function validateObjectProperty(ctx, parentName, property) {
|
|
1203
|
+
const qualifiedName = `${parentName}.${property.name}`;
|
|
1204
|
+
const analysis = (0, import_analysis.analyzeConstraintTargets)(
|
|
1205
|
+
qualifiedName,
|
|
1206
|
+
property.type,
|
|
1207
|
+
property.constraints,
|
|
1208
|
+
ctx.typeRegistry,
|
|
1209
|
+
ctx.extensionRegistry === void 0 ? void 0 : {
|
|
1210
|
+
extensionRegistry: ctx.extensionRegistry
|
|
1211
|
+
}
|
|
1212
|
+
);
|
|
1213
|
+
ctx.diagnostics.push(...analysis.diagnostics);
|
|
1214
|
+
if (property.type.kind === "object") {
|
|
1215
|
+
for (const nestedProperty of property.type.properties) {
|
|
1216
|
+
validateObjectProperty(ctx, qualifiedName, nestedProperty);
|
|
1926
1217
|
}
|
|
1927
1218
|
}
|
|
1928
1219
|
}
|
|
1929
|
-
function validateConstraints(ctx, name, type, constraints) {
|
|
1930
|
-
checkNumericContradictions(ctx, name, constraints);
|
|
1931
|
-
checkLengthContradictions(ctx, name, constraints);
|
|
1932
|
-
checkAllowedMembersContradiction(ctx, name, constraints);
|
|
1933
|
-
checkConstContradictions(ctx, name, constraints);
|
|
1934
|
-
checkConstraintBroadening(ctx, name, constraints);
|
|
1935
|
-
checkCustomConstraintSemantics(ctx, name, constraints);
|
|
1936
|
-
checkTypeApplicability(ctx, name, type, constraints);
|
|
1937
|
-
}
|
|
1938
1220
|
function validateElement(ctx, element) {
|
|
1939
1221
|
switch (element.kind) {
|
|
1940
1222
|
case "field":
|
|
@@ -1951,8 +1233,8 @@ function validateElement(ctx, element) {
|
|
|
1951
1233
|
}
|
|
1952
1234
|
break;
|
|
1953
1235
|
default: {
|
|
1954
|
-
const
|
|
1955
|
-
throw new Error(`Unhandled element kind: ${
|
|
1236
|
+
const exhaustive = element;
|
|
1237
|
+
throw new Error(`Unhandled element kind: ${String(exhaustive)}`);
|
|
1956
1238
|
}
|
|
1957
1239
|
}
|
|
1958
1240
|
}
|
|
@@ -1967,7 +1249,7 @@ function validateIR(ir, options) {
|
|
|
1967
1249
|
}
|
|
1968
1250
|
return {
|
|
1969
1251
|
diagnostics: ctx.diagnostics,
|
|
1970
|
-
valid: ctx.diagnostics.every((
|
|
1252
|
+
valid: ctx.diagnostics.every((diagnostic) => diagnostic.severity !== "error")
|
|
1971
1253
|
};
|
|
1972
1254
|
}
|
|
1973
1255
|
|