@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.
Files changed (35) hide show
  1. package/dist/__tests__/fixtures/class-schema-regressions.d.ts +4 -0
  2. package/dist/__tests__/fixtures/class-schema-regressions.d.ts.map +1 -1
  3. package/dist/__tests__/parity/utils.d.ts.map +1 -1
  4. package/dist/analyzer/class-analyzer.d.ts +4 -1
  5. package/dist/analyzer/class-analyzer.d.ts.map +1 -1
  6. package/dist/analyzer/jsdoc-constraints.d.ts +2 -1
  7. package/dist/analyzer/jsdoc-constraints.d.ts.map +1 -1
  8. package/dist/analyzer/tsdoc-parser.d.ts +13 -3
  9. package/dist/analyzer/tsdoc-parser.d.ts.map +1 -1
  10. package/dist/browser.cjs +30 -748
  11. package/dist/browser.cjs.map +1 -1
  12. package/dist/browser.js +32 -748
  13. package/dist/browser.js.map +1 -1
  14. package/dist/build.d.ts +14 -14
  15. package/dist/cli.cjs +691 -1101
  16. package/dist/cli.cjs.map +1 -1
  17. package/dist/cli.js +704 -1100
  18. package/dist/cli.js.map +1 -1
  19. package/dist/generators/class-schema.d.ts.map +1 -1
  20. package/dist/index.cjs +689 -1095
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.js +703 -1095
  23. package/dist/index.js.map +1 -1
  24. package/dist/internals.cjs +663 -1069
  25. package/dist/internals.cjs.map +1 -1
  26. package/dist/internals.js +675 -1067
  27. package/dist/internals.js.map +1 -1
  28. package/dist/ui-schema/schema.d.ts +14 -14
  29. package/dist/validate/constraint-validator.d.ts +6 -45
  30. package/dist/validate/constraint-validator.d.ts.map +1 -1
  31. package/package.json +2 -1
  32. package/dist/__tests__/json-utils.test.d.ts +0 -5
  33. package/dist/__tests__/json-utils.test.d.ts.map +0 -1
  34. package/dist/analyzer/json-utils.d.ts +0 -22
  35. 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 { normalizeConstraintTagName } from "@formspec/core";
1135
- function addContradiction(ctx, message, primary, related) {
1136
- ctx.diagnostics.push({
1137
- code: "CONTRADICTING_CONSTRAINTS",
1138
- message,
1139
- severity: "error",
1140
- primaryLocation: primary,
1141
- relatedLocations: [related]
1142
- });
1143
- }
1144
- function addTypeMismatch(ctx, message, primary) {
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
- if (!matchesApplicableType) {
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 prop of field.type.properties) {
1863
- validateObjectProperty(ctx, field.name, prop);
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, prop) {
1868
- const qualifiedName = `${parentName}.${prop.name}`;
1869
- validateConstraints(ctx, qualifiedName, prop.type, [
1870
- ...collectReferencedTypeConstraints(ctx, prop.type),
1871
- ...prop.constraints
1872
- ]);
1873
- if (prop.type.kind === "object") {
1874
- for (const nestedProp of prop.type.properties) {
1875
- validateObjectProperty(ctx, qualifiedName, nestedProp);
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 _exhaustive = element;
1905
- throw new Error(`Unhandled element kind: ${_exhaustive.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((d) => d.severity !== "error")
1204
+ valid: ctx.diagnostics.every((diagnostic) => diagnostic.severity !== "error")
1921
1205
  };
1922
1206
  }
1923
1207