@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.js
CHANGED
|
@@ -1131,760 +1131,44 @@ var jsonSchema7Schema = z3.lazy(
|
|
|
1131
1131
|
);
|
|
1132
1132
|
|
|
1133
1133
|
// src/validate/constraint-validator.ts
|
|
1134
|
-
import {
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
ctx.diagnostics.push({
|
|
1146
|
-
code: "TYPE_MISMATCH",
|
|
1147
|
-
message,
|
|
1148
|
-
severity: "error",
|
|
1149
|
-
primaryLocation: primary,
|
|
1150
|
-
relatedLocations: []
|
|
1151
|
-
});
|
|
1152
|
-
}
|
|
1153
|
-
function addUnknownExtension(ctx, message, primary) {
|
|
1154
|
-
ctx.diagnostics.push({
|
|
1155
|
-
code: "UNKNOWN_EXTENSION",
|
|
1156
|
-
message,
|
|
1157
|
-
severity: "warning",
|
|
1158
|
-
primaryLocation: primary,
|
|
1159
|
-
relatedLocations: []
|
|
1160
|
-
});
|
|
1161
|
-
}
|
|
1162
|
-
function addUnknownPathTarget(ctx, message, primary) {
|
|
1163
|
-
ctx.diagnostics.push({
|
|
1164
|
-
code: "UNKNOWN_PATH_TARGET",
|
|
1165
|
-
message,
|
|
1166
|
-
severity: "error",
|
|
1167
|
-
primaryLocation: primary,
|
|
1168
|
-
relatedLocations: []
|
|
1169
|
-
});
|
|
1170
|
-
}
|
|
1171
|
-
function addConstraintBroadening(ctx, message, primary, related) {
|
|
1172
|
-
ctx.diagnostics.push({
|
|
1173
|
-
code: "CONSTRAINT_BROADENING",
|
|
1174
|
-
message,
|
|
1175
|
-
severity: "error",
|
|
1176
|
-
primaryLocation: primary,
|
|
1177
|
-
relatedLocations: [related]
|
|
1178
|
-
});
|
|
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
|
-
}
|
|
1187
|
-
function findNumeric(constraints, constraintKind) {
|
|
1188
|
-
return constraints.find((c) => c.constraintKind === constraintKind);
|
|
1189
|
-
}
|
|
1190
|
-
function findLength(constraints, constraintKind) {
|
|
1191
|
-
return constraints.find((c) => c.constraintKind === constraintKind);
|
|
1192
|
-
}
|
|
1193
|
-
function findAllowedMembers(constraints) {
|
|
1194
|
-
return constraints.filter(
|
|
1195
|
-
(c) => c.constraintKind === "allowedMembers"
|
|
1196
|
-
);
|
|
1197
|
-
}
|
|
1198
|
-
function findConstConstraints(constraints) {
|
|
1199
|
-
return constraints.filter(
|
|
1200
|
-
(c) => c.constraintKind === "const"
|
|
1201
|
-
);
|
|
1202
|
-
}
|
|
1203
|
-
function jsonValueEquals(left, right) {
|
|
1204
|
-
if (left === right) {
|
|
1205
|
-
return true;
|
|
1206
|
-
}
|
|
1207
|
-
if (Array.isArray(left) || Array.isArray(right)) {
|
|
1208
|
-
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
|
|
1209
|
-
return false;
|
|
1210
|
-
}
|
|
1211
|
-
return left.every((item, index) => jsonValueEquals(item, right[index]));
|
|
1212
|
-
}
|
|
1213
|
-
if (isJsonObject(left) || isJsonObject(right)) {
|
|
1214
|
-
if (!isJsonObject(left) || !isJsonObject(right)) {
|
|
1215
|
-
return false;
|
|
1216
|
-
}
|
|
1217
|
-
const leftKeys = Object.keys(left).sort();
|
|
1218
|
-
const rightKeys = Object.keys(right).sort();
|
|
1219
|
-
if (leftKeys.length !== rightKeys.length) {
|
|
1220
|
-
return false;
|
|
1221
|
-
}
|
|
1222
|
-
return leftKeys.every((key, index) => {
|
|
1223
|
-
const rightKey = rightKeys[index];
|
|
1224
|
-
if (rightKey !== key) {
|
|
1225
|
-
return false;
|
|
1226
|
-
}
|
|
1227
|
-
const leftValue = left[key];
|
|
1228
|
-
const rightValue = right[rightKey];
|
|
1229
|
-
return leftValue !== void 0 && rightValue !== void 0 && jsonValueEquals(leftValue, rightValue);
|
|
1230
|
-
});
|
|
1231
|
-
}
|
|
1232
|
-
return false;
|
|
1233
|
-
}
|
|
1234
|
-
function isJsonObject(value) {
|
|
1235
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1236
|
-
}
|
|
1237
|
-
function isOrderedBoundConstraint(constraint) {
|
|
1238
|
-
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";
|
|
1239
|
-
}
|
|
1240
|
-
function pathKey(constraint) {
|
|
1241
|
-
return constraint.path?.segments.join(".") ?? "";
|
|
1242
|
-
}
|
|
1243
|
-
function orderedBoundFamily(kind) {
|
|
1244
|
-
switch (kind) {
|
|
1245
|
-
case "minimum":
|
|
1246
|
-
case "exclusiveMinimum":
|
|
1247
|
-
return "numeric-lower";
|
|
1248
|
-
case "maximum":
|
|
1249
|
-
case "exclusiveMaximum":
|
|
1250
|
-
return "numeric-upper";
|
|
1251
|
-
case "minLength":
|
|
1252
|
-
return "minLength";
|
|
1253
|
-
case "minItems":
|
|
1254
|
-
return "minItems";
|
|
1255
|
-
case "maxLength":
|
|
1256
|
-
return "maxLength";
|
|
1257
|
-
case "maxItems":
|
|
1258
|
-
return "maxItems";
|
|
1259
|
-
default: {
|
|
1260
|
-
const _exhaustive = kind;
|
|
1261
|
-
return _exhaustive;
|
|
1262
|
-
}
|
|
1263
|
-
}
|
|
1264
|
-
}
|
|
1265
|
-
function isNumericLowerKind(kind) {
|
|
1266
|
-
return kind === "minimum" || kind === "exclusiveMinimum";
|
|
1267
|
-
}
|
|
1268
|
-
function isNumericUpperKind(kind) {
|
|
1269
|
-
return kind === "maximum" || kind === "exclusiveMaximum";
|
|
1270
|
-
}
|
|
1271
|
-
function describeConstraintTag(constraint) {
|
|
1272
|
-
return `@${constraint.constraintKind}`;
|
|
1273
|
-
}
|
|
1274
|
-
function compareConstraintStrength(current, previous) {
|
|
1275
|
-
const family = orderedBoundFamily(current.constraintKind);
|
|
1276
|
-
if (family === "numeric-lower") {
|
|
1277
|
-
if (!isNumericLowerKind(current.constraintKind) || !isNumericLowerKind(previous.constraintKind)) {
|
|
1278
|
-
throw new Error("numeric-lower family received non-numeric lower-bound constraint");
|
|
1279
|
-
}
|
|
1280
|
-
if (current.value !== previous.value) {
|
|
1281
|
-
return current.value > previous.value ? 1 : -1;
|
|
1282
|
-
}
|
|
1283
|
-
if (current.constraintKind === "exclusiveMinimum" && previous.constraintKind === "minimum") {
|
|
1284
|
-
return 1;
|
|
1285
|
-
}
|
|
1286
|
-
if (current.constraintKind === "minimum" && previous.constraintKind === "exclusiveMinimum") {
|
|
1287
|
-
return -1;
|
|
1288
|
-
}
|
|
1289
|
-
return 0;
|
|
1290
|
-
}
|
|
1291
|
-
if (family === "numeric-upper") {
|
|
1292
|
-
if (!isNumericUpperKind(current.constraintKind) || !isNumericUpperKind(previous.constraintKind)) {
|
|
1293
|
-
throw new Error("numeric-upper family received non-numeric upper-bound constraint");
|
|
1294
|
-
}
|
|
1295
|
-
if (current.value !== previous.value) {
|
|
1296
|
-
return current.value < previous.value ? 1 : -1;
|
|
1297
|
-
}
|
|
1298
|
-
if (current.constraintKind === "exclusiveMaximum" && previous.constraintKind === "maximum") {
|
|
1299
|
-
return 1;
|
|
1300
|
-
}
|
|
1301
|
-
if (current.constraintKind === "maximum" && previous.constraintKind === "exclusiveMaximum") {
|
|
1302
|
-
return -1;
|
|
1303
|
-
}
|
|
1304
|
-
return 0;
|
|
1305
|
-
}
|
|
1306
|
-
switch (family) {
|
|
1307
|
-
case "minLength":
|
|
1308
|
-
case "minItems":
|
|
1309
|
-
if (current.value === previous.value) {
|
|
1310
|
-
return 0;
|
|
1311
|
-
}
|
|
1312
|
-
return current.value > previous.value ? 1 : -1;
|
|
1313
|
-
case "maxLength":
|
|
1314
|
-
case "maxItems":
|
|
1315
|
-
if (current.value === previous.value) {
|
|
1316
|
-
return 0;
|
|
1317
|
-
}
|
|
1318
|
-
return current.value < previous.value ? 1 : -1;
|
|
1319
|
-
default: {
|
|
1320
|
-
const _exhaustive = family;
|
|
1321
|
-
return _exhaustive;
|
|
1322
|
-
}
|
|
1323
|
-
}
|
|
1324
|
-
}
|
|
1325
|
-
function checkConstraintBroadening(ctx, fieldName, constraints) {
|
|
1326
|
-
const strongestByKey = /* @__PURE__ */ new Map();
|
|
1327
|
-
for (const constraint of constraints) {
|
|
1328
|
-
if (!isOrderedBoundConstraint(constraint)) {
|
|
1329
|
-
continue;
|
|
1330
|
-
}
|
|
1331
|
-
const key = `${orderedBoundFamily(constraint.constraintKind)}:${pathKey(constraint)}`;
|
|
1332
|
-
const previous = strongestByKey.get(key);
|
|
1333
|
-
if (previous === void 0) {
|
|
1334
|
-
strongestByKey.set(key, constraint);
|
|
1335
|
-
continue;
|
|
1336
|
-
}
|
|
1337
|
-
const strength = compareConstraintStrength(constraint, previous);
|
|
1338
|
-
if (strength < 0) {
|
|
1339
|
-
const displayFieldName = formatPathTargetFieldName(
|
|
1340
|
-
fieldName,
|
|
1341
|
-
constraint.path?.segments ?? []
|
|
1342
|
-
);
|
|
1343
|
-
addConstraintBroadening(
|
|
1344
|
-
ctx,
|
|
1345
|
-
`Field "${displayFieldName}": ${describeConstraintTag(constraint)} (${String(constraint.value)}) is broader than earlier ${describeConstraintTag(previous)} (${String(previous.value)}). Constraints can only narrow.`,
|
|
1346
|
-
constraint.provenance,
|
|
1347
|
-
previous.provenance
|
|
1348
|
-
);
|
|
1349
|
-
continue;
|
|
1350
|
-
}
|
|
1351
|
-
if (strength <= 0) {
|
|
1352
|
-
continue;
|
|
1353
|
-
}
|
|
1354
|
-
strongestByKey.set(key, constraint);
|
|
1355
|
-
}
|
|
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
|
-
}
|
|
1463
|
-
function checkNumericContradictions(ctx, fieldName, constraints) {
|
|
1464
|
-
const min = findNumeric(constraints, "minimum");
|
|
1465
|
-
const max = findNumeric(constraints, "maximum");
|
|
1466
|
-
const exMin = findNumeric(constraints, "exclusiveMinimum");
|
|
1467
|
-
const exMax = findNumeric(constraints, "exclusiveMaximum");
|
|
1468
|
-
if (min !== void 0 && max !== void 0 && min.value > max.value) {
|
|
1469
|
-
addContradiction(
|
|
1470
|
-
ctx,
|
|
1471
|
-
`Field "${fieldName}": minimum (${String(min.value)}) is greater than maximum (${String(max.value)})`,
|
|
1472
|
-
min.provenance,
|
|
1473
|
-
max.provenance
|
|
1474
|
-
);
|
|
1475
|
-
}
|
|
1476
|
-
if (exMin !== void 0 && max !== void 0 && exMin.value >= max.value) {
|
|
1477
|
-
addContradiction(
|
|
1478
|
-
ctx,
|
|
1479
|
-
`Field "${fieldName}": exclusiveMinimum (${String(exMin.value)}) is greater than or equal to maximum (${String(max.value)})`,
|
|
1480
|
-
exMin.provenance,
|
|
1481
|
-
max.provenance
|
|
1482
|
-
);
|
|
1483
|
-
}
|
|
1484
|
-
if (min !== void 0 && exMax !== void 0 && min.value >= exMax.value) {
|
|
1485
|
-
addContradiction(
|
|
1486
|
-
ctx,
|
|
1487
|
-
`Field "${fieldName}": minimum (${String(min.value)}) is greater than or equal to exclusiveMaximum (${String(exMax.value)})`,
|
|
1488
|
-
min.provenance,
|
|
1489
|
-
exMax.provenance
|
|
1490
|
-
);
|
|
1491
|
-
}
|
|
1492
|
-
if (exMin !== void 0 && exMax !== void 0 && exMin.value >= exMax.value) {
|
|
1493
|
-
addContradiction(
|
|
1494
|
-
ctx,
|
|
1495
|
-
`Field "${fieldName}": exclusiveMinimum (${String(exMin.value)}) is greater than or equal to exclusiveMaximum (${String(exMax.value)})`,
|
|
1496
|
-
exMin.provenance,
|
|
1497
|
-
exMax.provenance
|
|
1498
|
-
);
|
|
1499
|
-
}
|
|
1500
|
-
}
|
|
1501
|
-
function checkLengthContradictions(ctx, fieldName, constraints) {
|
|
1502
|
-
const minLen = findLength(constraints, "minLength");
|
|
1503
|
-
const maxLen = findLength(constraints, "maxLength");
|
|
1504
|
-
if (minLen !== void 0 && maxLen !== void 0 && minLen.value > maxLen.value) {
|
|
1505
|
-
addContradiction(
|
|
1506
|
-
ctx,
|
|
1507
|
-
`Field "${fieldName}": minLength (${String(minLen.value)}) is greater than maxLength (${String(maxLen.value)})`,
|
|
1508
|
-
minLen.provenance,
|
|
1509
|
-
maxLen.provenance
|
|
1510
|
-
);
|
|
1511
|
-
}
|
|
1512
|
-
const minItems = findLength(constraints, "minItems");
|
|
1513
|
-
const maxItems = findLength(constraints, "maxItems");
|
|
1514
|
-
if (minItems !== void 0 && maxItems !== void 0 && minItems.value > maxItems.value) {
|
|
1515
|
-
addContradiction(
|
|
1516
|
-
ctx,
|
|
1517
|
-
`Field "${fieldName}": minItems (${String(minItems.value)}) is greater than maxItems (${String(maxItems.value)})`,
|
|
1518
|
-
minItems.provenance,
|
|
1519
|
-
maxItems.provenance
|
|
1520
|
-
);
|
|
1521
|
-
}
|
|
1522
|
-
}
|
|
1523
|
-
function checkAllowedMembersContradiction(ctx, fieldName, constraints) {
|
|
1524
|
-
const members = findAllowedMembers(constraints);
|
|
1525
|
-
if (members.length < 2) return;
|
|
1526
|
-
const firstSet = new Set(members[0]?.members ?? []);
|
|
1527
|
-
for (let i = 1; i < members.length; i++) {
|
|
1528
|
-
const current = members[i];
|
|
1529
|
-
if (current === void 0) continue;
|
|
1530
|
-
for (const m of firstSet) {
|
|
1531
|
-
if (!current.members.includes(m)) {
|
|
1532
|
-
firstSet.delete(m);
|
|
1533
|
-
}
|
|
1534
|
-
}
|
|
1535
|
-
}
|
|
1536
|
-
if (firstSet.size === 0) {
|
|
1537
|
-
const first = members[0];
|
|
1538
|
-
const second = members[1];
|
|
1539
|
-
if (first !== void 0 && second !== void 0) {
|
|
1540
|
-
addContradiction(
|
|
1541
|
-
ctx,
|
|
1542
|
-
`Field "${fieldName}": allowedMembers constraints have an empty intersection (no valid values remain)`,
|
|
1543
|
-
first.provenance,
|
|
1544
|
-
second.provenance
|
|
1545
|
-
);
|
|
1546
|
-
}
|
|
1547
|
-
}
|
|
1548
|
-
}
|
|
1549
|
-
function checkConstContradictions(ctx, fieldName, constraints) {
|
|
1550
|
-
const constConstraints = findConstConstraints(constraints);
|
|
1551
|
-
if (constConstraints.length < 2) return;
|
|
1552
|
-
const first = constConstraints[0];
|
|
1553
|
-
if (first === void 0) return;
|
|
1554
|
-
for (let i = 1; i < constConstraints.length; i++) {
|
|
1555
|
-
const current = constConstraints[i];
|
|
1556
|
-
if (current === void 0) continue;
|
|
1557
|
-
if (jsonValueEquals(first.value, current.value)) {
|
|
1558
|
-
continue;
|
|
1559
|
-
}
|
|
1560
|
-
addContradiction(
|
|
1561
|
-
ctx,
|
|
1562
|
-
`Field "${fieldName}": conflicting @const constraints require both ${JSON.stringify(first.value)} and ${JSON.stringify(current.value)}`,
|
|
1563
|
-
first.provenance,
|
|
1564
|
-
current.provenance
|
|
1565
|
-
);
|
|
1566
|
-
}
|
|
1567
|
-
}
|
|
1568
|
-
function typeLabel(type) {
|
|
1569
|
-
switch (type.kind) {
|
|
1570
|
-
case "primitive":
|
|
1571
|
-
return type.primitiveKind;
|
|
1572
|
-
case "enum":
|
|
1573
|
-
return "enum";
|
|
1574
|
-
case "array":
|
|
1575
|
-
return "array";
|
|
1576
|
-
case "object":
|
|
1577
|
-
return "object";
|
|
1578
|
-
case "record":
|
|
1579
|
-
return "record";
|
|
1580
|
-
case "union":
|
|
1581
|
-
return "union";
|
|
1582
|
-
case "reference":
|
|
1583
|
-
return `reference(${type.name})`;
|
|
1584
|
-
case "dynamic":
|
|
1585
|
-
return `dynamic(${type.dynamicKind})`;
|
|
1586
|
-
case "custom":
|
|
1587
|
-
return `custom(${type.typeId})`;
|
|
1588
|
-
default: {
|
|
1589
|
-
const _exhaustive = type;
|
|
1590
|
-
return String(_exhaustive);
|
|
1591
|
-
}
|
|
1592
|
-
}
|
|
1593
|
-
}
|
|
1594
|
-
function dereferenceType(ctx, type) {
|
|
1595
|
-
let current = type;
|
|
1596
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1597
|
-
while (current.kind === "reference") {
|
|
1598
|
-
if (seen.has(current.name)) {
|
|
1599
|
-
return current;
|
|
1600
|
-
}
|
|
1601
|
-
seen.add(current.name);
|
|
1602
|
-
const definition = ctx.typeRegistry[current.name];
|
|
1603
|
-
if (definition === void 0) {
|
|
1604
|
-
return current;
|
|
1605
|
-
}
|
|
1606
|
-
current = definition.type;
|
|
1607
|
-
}
|
|
1608
|
-
return current;
|
|
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
|
-
}
|
|
1630
|
-
function resolvePathTargetType(ctx, type, segments) {
|
|
1631
|
-
const effectiveType = dereferenceType(ctx, type);
|
|
1632
|
-
if (segments.length === 0) {
|
|
1633
|
-
return { kind: "resolved", type: effectiveType };
|
|
1634
|
-
}
|
|
1635
|
-
if (effectiveType.kind === "array") {
|
|
1636
|
-
return resolvePathTargetType(ctx, effectiveType.items, segments);
|
|
1637
|
-
}
|
|
1638
|
-
if (effectiveType.kind === "object") {
|
|
1639
|
-
const [segment, ...rest] = segments;
|
|
1640
|
-
if (segment === void 0) {
|
|
1641
|
-
throw new Error("Invariant violation: object path traversal requires a segment");
|
|
1642
|
-
}
|
|
1643
|
-
const property = effectiveType.properties.find((prop) => prop.name === segment);
|
|
1644
|
-
if (property === void 0) {
|
|
1645
|
-
return { kind: "missing-property", segment };
|
|
1646
|
-
}
|
|
1647
|
-
return resolvePathTargetType(ctx, property.type, rest);
|
|
1648
|
-
}
|
|
1649
|
-
return { kind: "unresolvable", type: effectiveType };
|
|
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
|
-
}
|
|
1672
|
-
function formatPathTargetFieldName(fieldName, path) {
|
|
1673
|
-
return path.length === 0 ? fieldName : `${fieldName}.${path.join(".")}`;
|
|
1674
|
-
}
|
|
1675
|
-
function checkConstraintOnType(ctx, fieldName, type, constraint) {
|
|
1676
|
-
const effectiveType = dereferenceType(ctx, type);
|
|
1677
|
-
const isNumber = effectiveType.kind === "primitive" && ["number", "integer", "bigint"].includes(effectiveType.primitiveKind);
|
|
1678
|
-
const isString = effectiveType.kind === "primitive" && effectiveType.primitiveKind === "string";
|
|
1679
|
-
const isArray = effectiveType.kind === "array";
|
|
1680
|
-
const isEnum = effectiveType.kind === "enum";
|
|
1681
|
-
const arrayItemType = effectiveType.kind === "array" ? dereferenceType(ctx, effectiveType.items) : void 0;
|
|
1682
|
-
const isStringArray = arrayItemType?.kind === "primitive" && arrayItemType.primitiveKind === "string";
|
|
1683
|
-
const label = typeLabel(effectiveType);
|
|
1684
|
-
const ck = constraint.constraintKind;
|
|
1685
|
-
switch (ck) {
|
|
1686
|
-
case "minimum":
|
|
1687
|
-
case "maximum":
|
|
1688
|
-
case "exclusiveMinimum":
|
|
1689
|
-
case "exclusiveMaximum":
|
|
1690
|
-
case "multipleOf": {
|
|
1691
|
-
if (!isNumber) {
|
|
1692
|
-
addTypeMismatch(
|
|
1693
|
-
ctx,
|
|
1694
|
-
`Field "${fieldName}": constraint "${ck}" is only valid on number fields, but field type is "${label}"`,
|
|
1695
|
-
constraint.provenance
|
|
1696
|
-
);
|
|
1697
|
-
}
|
|
1698
|
-
break;
|
|
1699
|
-
}
|
|
1700
|
-
case "minLength":
|
|
1701
|
-
case "maxLength":
|
|
1702
|
-
case "pattern": {
|
|
1703
|
-
if (!isString && !isStringArray) {
|
|
1704
|
-
addTypeMismatch(
|
|
1705
|
-
ctx,
|
|
1706
|
-
`Field "${fieldName}": constraint "${ck}" is only valid on string fields or string array items, but field type is "${label}"`,
|
|
1707
|
-
constraint.provenance
|
|
1708
|
-
);
|
|
1709
|
-
}
|
|
1710
|
-
break;
|
|
1711
|
-
}
|
|
1712
|
-
case "minItems":
|
|
1713
|
-
case "maxItems":
|
|
1714
|
-
case "uniqueItems": {
|
|
1715
|
-
if (!isArray) {
|
|
1716
|
-
addTypeMismatch(
|
|
1717
|
-
ctx,
|
|
1718
|
-
`Field "${fieldName}": constraint "${ck}" is only valid on array fields, but field type is "${label}"`,
|
|
1719
|
-
constraint.provenance
|
|
1720
|
-
);
|
|
1721
|
-
}
|
|
1722
|
-
break;
|
|
1723
|
-
}
|
|
1724
|
-
case "allowedMembers": {
|
|
1725
|
-
if (!isEnum) {
|
|
1726
|
-
addTypeMismatch(
|
|
1727
|
-
ctx,
|
|
1728
|
-
`Field "${fieldName}": constraint "allowedMembers" is only valid on enum fields, but field type is "${label}"`,
|
|
1729
|
-
constraint.provenance
|
|
1730
|
-
);
|
|
1731
|
-
}
|
|
1732
|
-
break;
|
|
1733
|
-
}
|
|
1734
|
-
case "const": {
|
|
1735
|
-
const isPrimitiveConstType = effectiveType.kind === "primitive" && ["string", "number", "integer", "bigint", "boolean", "null"].includes(
|
|
1736
|
-
effectiveType.primitiveKind
|
|
1737
|
-
) || effectiveType.kind === "enum";
|
|
1738
|
-
if (!isPrimitiveConstType) {
|
|
1739
|
-
addTypeMismatch(
|
|
1740
|
-
ctx,
|
|
1741
|
-
`Field "${fieldName}": constraint "const" is only valid on primitive or enum fields, but field type is "${label}"`,
|
|
1742
|
-
constraint.provenance
|
|
1743
|
-
);
|
|
1744
|
-
break;
|
|
1745
|
-
}
|
|
1746
|
-
if (effectiveType.kind === "primitive") {
|
|
1747
|
-
const valueType = constraint.value === null ? "null" : Array.isArray(constraint.value) ? "array" : typeof constraint.value;
|
|
1748
|
-
const expectedValueType = effectiveType.primitiveKind === "integer" || effectiveType.primitiveKind === "bigint" ? "number" : effectiveType.primitiveKind;
|
|
1749
|
-
if (valueType !== expectedValueType) {
|
|
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
|
-
}
|
|
1768
|
-
case "custom": {
|
|
1769
|
-
checkCustomConstraint(ctx, fieldName, effectiveType, constraint);
|
|
1770
|
-
break;
|
|
1771
|
-
}
|
|
1772
|
-
default: {
|
|
1773
|
-
const _exhaustive = constraint;
|
|
1774
|
-
throw new Error(
|
|
1775
|
-
`Unhandled constraint kind: ${_exhaustive.constraintKind}`
|
|
1776
|
-
);
|
|
1777
|
-
}
|
|
1778
|
-
}
|
|
1779
|
-
}
|
|
1780
|
-
function checkTypeApplicability(ctx, fieldName, type, constraints) {
|
|
1781
|
-
for (const constraint of constraints) {
|
|
1782
|
-
if (constraint.path) {
|
|
1783
|
-
const resolution = resolvePathTargetType(ctx, type, constraint.path.segments);
|
|
1784
|
-
const targetFieldName = formatPathTargetFieldName(fieldName, constraint.path.segments);
|
|
1785
|
-
if (resolution.kind === "missing-property") {
|
|
1786
|
-
addUnknownPathTarget(
|
|
1787
|
-
ctx,
|
|
1788
|
-
`Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" references unknown path segment "${resolution.segment}"`,
|
|
1789
|
-
constraint.provenance
|
|
1790
|
-
);
|
|
1791
|
-
continue;
|
|
1792
|
-
}
|
|
1793
|
-
if (resolution.kind === "unresolvable") {
|
|
1794
|
-
addTypeMismatch(
|
|
1795
|
-
ctx,
|
|
1796
|
-
`Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" is invalid because type "${typeLabel(resolution.type)}" cannot be traversed`,
|
|
1797
|
-
constraint.provenance
|
|
1798
|
-
);
|
|
1799
|
-
continue;
|
|
1800
|
-
}
|
|
1801
|
-
checkConstraintOnType(ctx, targetFieldName, resolution.type, constraint);
|
|
1802
|
-
continue;
|
|
1803
|
-
}
|
|
1804
|
-
checkConstraintOnType(ctx, fieldName, type, constraint);
|
|
1805
|
-
}
|
|
1806
|
-
}
|
|
1807
|
-
function checkCustomConstraint(ctx, fieldName, type, constraint) {
|
|
1808
|
-
if (ctx.extensionRegistry === void 0) return;
|
|
1809
|
-
const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
|
|
1810
|
-
if (registration === void 0) {
|
|
1811
|
-
addUnknownExtension(
|
|
1812
|
-
ctx,
|
|
1813
|
-
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not registered in the extension registry`,
|
|
1814
|
-
constraint.provenance
|
|
1815
|
-
);
|
|
1816
|
-
return;
|
|
1817
|
-
}
|
|
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
|
-
);
|
|
1134
|
+
import {
|
|
1135
|
+
analyzeConstraintTargets
|
|
1136
|
+
} from "@formspec/analysis";
|
|
1137
|
+
function validateFieldNode(ctx, field) {
|
|
1138
|
+
const analysis = analyzeConstraintTargets(
|
|
1139
|
+
field.name,
|
|
1140
|
+
field.type,
|
|
1141
|
+
field.constraints,
|
|
1142
|
+
ctx.typeRegistry,
|
|
1143
|
+
ctx.extensionRegistry === void 0 ? void 0 : {
|
|
1144
|
+
extensionRegistry: ctx.extensionRegistry
|
|
1841
1145
|
}
|
|
1842
|
-
return;
|
|
1843
|
-
}
|
|
1844
|
-
const applicableTypes = registration.applicableTypes;
|
|
1845
|
-
const matchesApplicableType = candidateTypes.some(
|
|
1846
|
-
(candidateType) => applicableTypes.includes(candidateType.kind) && registration.isApplicableToType?.(candidateType) !== false
|
|
1847
1146
|
);
|
|
1848
|
-
|
|
1849
|
-
addTypeMismatch(
|
|
1850
|
-
ctx,
|
|
1851
|
-
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
1852
|
-
constraint.provenance
|
|
1853
|
-
);
|
|
1854
|
-
}
|
|
1855
|
-
}
|
|
1856
|
-
function validateFieldNode(ctx, field) {
|
|
1857
|
-
validateConstraints(ctx, field.name, field.type, [
|
|
1858
|
-
...collectReferencedTypeConstraints(ctx, field.type),
|
|
1859
|
-
...field.constraints
|
|
1860
|
-
]);
|
|
1147
|
+
ctx.diagnostics.push(...analysis.diagnostics);
|
|
1861
1148
|
if (field.type.kind === "object") {
|
|
1862
|
-
for (const
|
|
1863
|
-
validateObjectProperty(ctx, field.name,
|
|
1149
|
+
for (const property of field.type.properties) {
|
|
1150
|
+
validateObjectProperty(ctx, field.name, property);
|
|
1864
1151
|
}
|
|
1865
1152
|
}
|
|
1866
1153
|
}
|
|
1867
|
-
function validateObjectProperty(ctx, parentName,
|
|
1868
|
-
const qualifiedName = `${parentName}.${
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1154
|
+
function validateObjectProperty(ctx, parentName, property) {
|
|
1155
|
+
const qualifiedName = `${parentName}.${property.name}`;
|
|
1156
|
+
const analysis = analyzeConstraintTargets(
|
|
1157
|
+
qualifiedName,
|
|
1158
|
+
property.type,
|
|
1159
|
+
property.constraints,
|
|
1160
|
+
ctx.typeRegistry,
|
|
1161
|
+
ctx.extensionRegistry === void 0 ? void 0 : {
|
|
1162
|
+
extensionRegistry: ctx.extensionRegistry
|
|
1163
|
+
}
|
|
1164
|
+
);
|
|
1165
|
+
ctx.diagnostics.push(...analysis.diagnostics);
|
|
1166
|
+
if (property.type.kind === "object") {
|
|
1167
|
+
for (const nestedProperty of property.type.properties) {
|
|
1168
|
+
validateObjectProperty(ctx, qualifiedName, nestedProperty);
|
|
1876
1169
|
}
|
|
1877
1170
|
}
|
|
1878
1171
|
}
|
|
1879
|
-
function validateConstraints(ctx, name, type, constraints) {
|
|
1880
|
-
checkNumericContradictions(ctx, name, constraints);
|
|
1881
|
-
checkLengthContradictions(ctx, name, constraints);
|
|
1882
|
-
checkAllowedMembersContradiction(ctx, name, constraints);
|
|
1883
|
-
checkConstContradictions(ctx, name, constraints);
|
|
1884
|
-
checkConstraintBroadening(ctx, name, constraints);
|
|
1885
|
-
checkCustomConstraintSemantics(ctx, name, constraints);
|
|
1886
|
-
checkTypeApplicability(ctx, name, type, constraints);
|
|
1887
|
-
}
|
|
1888
1172
|
function validateElement(ctx, element) {
|
|
1889
1173
|
switch (element.kind) {
|
|
1890
1174
|
case "field":
|
|
@@ -1901,8 +1185,8 @@ function validateElement(ctx, element) {
|
|
|
1901
1185
|
}
|
|
1902
1186
|
break;
|
|
1903
1187
|
default: {
|
|
1904
|
-
const
|
|
1905
|
-
throw new Error(`Unhandled element kind: ${
|
|
1188
|
+
const exhaustive = element;
|
|
1189
|
+
throw new Error(`Unhandled element kind: ${String(exhaustive)}`);
|
|
1906
1190
|
}
|
|
1907
1191
|
}
|
|
1908
1192
|
}
|
|
@@ -1917,7 +1201,7 @@ function validateIR(ir, options) {
|
|
|
1917
1201
|
}
|
|
1918
1202
|
return {
|
|
1919
1203
|
diagnostics: ctx.diagnostics,
|
|
1920
|
-
valid: ctx.diagnostics.every((
|
|
1204
|
+
valid: ctx.diagnostics.every((diagnostic) => diagnostic.severity !== "error")
|
|
1921
1205
|
};
|
|
1922
1206
|
}
|
|
1923
1207
|
|