@formspec/build 0.1.0-alpha.16 → 0.1.0-alpha.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/__tests__/fixtures/example-numeric-extension.d.ts +20 -0
  2. package/dist/__tests__/fixtures/example-numeric-extension.d.ts.map +1 -0
  3. package/dist/__tests__/fixtures/mixed-authoring-shipping-address.d.ts +1 -0
  4. package/dist/__tests__/fixtures/mixed-authoring-shipping-address.d.ts.map +1 -1
  5. package/dist/__tests__/numeric-extension.integration.test.d.ts +2 -0
  6. package/dist/__tests__/numeric-extension.integration.test.d.ts.map +1 -0
  7. package/dist/analyzer/class-analyzer.d.ts +5 -4
  8. package/dist/analyzer/class-analyzer.d.ts.map +1 -1
  9. package/dist/analyzer/jsdoc-constraints.d.ts +3 -2
  10. package/dist/analyzer/jsdoc-constraints.d.ts.map +1 -1
  11. package/dist/analyzer/tsdoc-parser.d.ts +18 -2
  12. package/dist/analyzer/tsdoc-parser.d.ts.map +1 -1
  13. package/dist/browser.cjs +199 -4
  14. package/dist/browser.cjs.map +1 -1
  15. package/dist/browser.js +199 -4
  16. package/dist/browser.js.map +1 -1
  17. package/dist/build.d.ts +28 -2
  18. package/dist/cli.cjs +547 -84
  19. package/dist/cli.cjs.map +1 -1
  20. package/dist/cli.js +547 -84
  21. package/dist/cli.js.map +1 -1
  22. package/dist/extensions/registry.d.ts +25 -1
  23. package/dist/extensions/registry.d.ts.map +1 -1
  24. package/dist/generators/class-schema.d.ts +4 -4
  25. package/dist/generators/class-schema.d.ts.map +1 -1
  26. package/dist/index.cjs +546 -84
  27. package/dist/index.cjs.map +1 -1
  28. package/dist/index.js +546 -84
  29. package/dist/index.js.map +1 -1
  30. package/dist/internals.cjs +645 -73
  31. package/dist/internals.cjs.map +1 -1
  32. package/dist/internals.js +643 -71
  33. package/dist/internals.js.map +1 -1
  34. package/dist/validate/constraint-validator.d.ts.map +1 -1
  35. package/package.json +3 -3
package/dist/browser.js CHANGED
@@ -704,7 +704,12 @@ function applyCustomConstraint(schema, constraint, ctx) {
704
704
  `Cannot generate JSON Schema for custom constraint "${constraint.constraintId}" without a matching extension registration`
705
705
  );
706
706
  }
707
- Object.assign(schema, registration.toJsonSchema(constraint.payload, ctx.vendorPrefix));
707
+ assignVendorPrefixedExtensionKeywords(
708
+ schema,
709
+ registration.toJsonSchema(constraint.payload, ctx.vendorPrefix),
710
+ ctx.vendorPrefix,
711
+ `custom constraint "${constraint.constraintId}"`
712
+ );
708
713
  }
709
714
  function applyCustomAnnotation(schema, annotation, ctx) {
710
715
  const registration = ctx.extensionRegistry?.findAnnotation(annotation.annotationId);
@@ -716,7 +721,22 @@ function applyCustomAnnotation(schema, annotation, ctx) {
716
721
  if (registration.toJsonSchema === void 0) {
717
722
  return;
718
723
  }
719
- Object.assign(schema, registration.toJsonSchema(annotation.value, ctx.vendorPrefix));
724
+ assignVendorPrefixedExtensionKeywords(
725
+ schema,
726
+ registration.toJsonSchema(annotation.value, ctx.vendorPrefix),
727
+ ctx.vendorPrefix,
728
+ `custom annotation "${annotation.annotationId}"`
729
+ );
730
+ }
731
+ function assignVendorPrefixedExtensionKeywords(schema, extensionSchema, vendorPrefix, source) {
732
+ for (const [key, value] of Object.entries(extensionSchema)) {
733
+ if (!key.startsWith(`${vendorPrefix}-`)) {
734
+ throw new Error(
735
+ `Cannot apply ${source}: extension hooks may only emit "${vendorPrefix}-*" JSON Schema keywords`
736
+ );
737
+ }
738
+ schema[key] = value;
739
+ }
720
740
  }
721
741
 
722
742
  // src/json-schema/generator.ts
@@ -965,7 +985,10 @@ function getSchemaExtension(schema, key) {
965
985
  // src/extensions/registry.ts
966
986
  function createExtensionRegistry(extensions) {
967
987
  const typeMap = /* @__PURE__ */ new Map();
988
+ const typeNameMap = /* @__PURE__ */ new Map();
968
989
  const constraintMap = /* @__PURE__ */ new Map();
990
+ const constraintTagMap = /* @__PURE__ */ new Map();
991
+ const builtinBroadeningMap = /* @__PURE__ */ new Map();
969
992
  const annotationMap = /* @__PURE__ */ new Map();
970
993
  for (const ext of extensions) {
971
994
  if (ext.types !== void 0) {
@@ -975,6 +998,27 @@ function createExtensionRegistry(extensions) {
975
998
  throw new Error(`Duplicate custom type ID: "${qualifiedId}"`);
976
999
  }
977
1000
  typeMap.set(qualifiedId, type);
1001
+ for (const sourceTypeName of type.tsTypeNames ?? [type.typeName]) {
1002
+ if (typeNameMap.has(sourceTypeName)) {
1003
+ throw new Error(`Duplicate custom type source name: "${sourceTypeName}"`);
1004
+ }
1005
+ typeNameMap.set(sourceTypeName, {
1006
+ extensionId: ext.extensionId,
1007
+ registration: type
1008
+ });
1009
+ }
1010
+ if (type.builtinConstraintBroadenings !== void 0) {
1011
+ for (const broadening of type.builtinConstraintBroadenings) {
1012
+ const key = `${qualifiedId}:${broadening.tagName}`;
1013
+ if (builtinBroadeningMap.has(key)) {
1014
+ throw new Error(`Duplicate built-in constraint broadening: "${key}"`);
1015
+ }
1016
+ builtinBroadeningMap.set(key, {
1017
+ extensionId: ext.extensionId,
1018
+ registration: broadening
1019
+ });
1020
+ }
1021
+ }
978
1022
  }
979
1023
  }
980
1024
  if (ext.constraints !== void 0) {
@@ -986,6 +1030,17 @@ function createExtensionRegistry(extensions) {
986
1030
  constraintMap.set(qualifiedId, constraint);
987
1031
  }
988
1032
  }
1033
+ if (ext.constraintTags !== void 0) {
1034
+ for (const tag of ext.constraintTags) {
1035
+ if (constraintTagMap.has(tag.tagName)) {
1036
+ throw new Error(`Duplicate custom constraint tag: "@${tag.tagName}"`);
1037
+ }
1038
+ constraintTagMap.set(tag.tagName, {
1039
+ extensionId: ext.extensionId,
1040
+ registration: tag
1041
+ });
1042
+ }
1043
+ }
989
1044
  if (ext.annotations !== void 0) {
990
1045
  for (const annotation of ext.annotations) {
991
1046
  const qualifiedId = `${ext.extensionId}/${annotation.annotationName}`;
@@ -999,7 +1054,10 @@ function createExtensionRegistry(extensions) {
999
1054
  return {
1000
1055
  extensions,
1001
1056
  findType: (typeId) => typeMap.get(typeId),
1057
+ findTypeByName: (typeName) => typeNameMap.get(typeName),
1002
1058
  findConstraint: (constraintId) => constraintMap.get(constraintId),
1059
+ findConstraintTag: (tagName) => constraintTagMap.get(tagName),
1060
+ findBuiltinConstraintBroadening: (typeId, tagName) => builtinBroadeningMap.get(`${typeId}:${tagName}`),
1003
1061
  findAnnotation: (annotationId) => annotationMap.get(annotationId)
1004
1062
  };
1005
1063
  }
@@ -1067,6 +1125,7 @@ var jsonSchema7Schema = z3.lazy(
1067
1125
  );
1068
1126
 
1069
1127
  // src/validate/constraint-validator.ts
1128
+ import { normalizeConstraintTagName } from "@formspec/core";
1070
1129
  function addContradiction(ctx, message, primary, related) {
1071
1130
  ctx.diagnostics.push({
1072
1131
  code: "CONTRADICTING_CONSTRAINTS",
@@ -1112,6 +1171,13 @@ function addConstraintBroadening(ctx, message, primary, related) {
1112
1171
  relatedLocations: [related]
1113
1172
  });
1114
1173
  }
1174
+ function getExtensionIdFromConstraintId(constraintId) {
1175
+ const separator = constraintId.lastIndexOf("/");
1176
+ if (separator <= 0) {
1177
+ return null;
1178
+ }
1179
+ return constraintId.slice(0, separator);
1180
+ }
1115
1181
  function findNumeric(constraints, constraintKind) {
1116
1182
  return constraints.find((c) => c.constraintKind === constraintKind);
1117
1183
  }
@@ -1282,6 +1348,112 @@ function checkConstraintBroadening(ctx, fieldName, constraints) {
1282
1348
  strongestByKey.set(key, constraint);
1283
1349
  }
1284
1350
  }
1351
+ function compareCustomConstraintStrength(current, previous) {
1352
+ const order = current.comparePayloads(current.constraint.payload, previous.constraint.payload);
1353
+ const equalPayloadTiebreaker = order === 0 ? compareSemanticInclusivity(current.role.inclusive, previous.role.inclusive) : order;
1354
+ switch (current.role.bound) {
1355
+ case "lower":
1356
+ return equalPayloadTiebreaker;
1357
+ case "upper":
1358
+ return equalPayloadTiebreaker === 0 ? 0 : -equalPayloadTiebreaker;
1359
+ case "exact":
1360
+ return order === 0 ? 0 : Number.NaN;
1361
+ default: {
1362
+ const _exhaustive = current.role.bound;
1363
+ return _exhaustive;
1364
+ }
1365
+ }
1366
+ }
1367
+ function compareSemanticInclusivity(currentInclusive, previousInclusive) {
1368
+ if (currentInclusive === previousInclusive) {
1369
+ return 0;
1370
+ }
1371
+ return currentInclusive ? -1 : 1;
1372
+ }
1373
+ function customConstraintsContradict(lower, upper) {
1374
+ const order = lower.comparePayloads(lower.constraint.payload, upper.constraint.payload);
1375
+ if (order > 0) {
1376
+ return true;
1377
+ }
1378
+ if (order < 0) {
1379
+ return false;
1380
+ }
1381
+ return !lower.role.inclusive || !upper.role.inclusive;
1382
+ }
1383
+ function describeCustomConstraintTag(constraint) {
1384
+ return constraint.provenance.tagName ?? constraint.constraintId;
1385
+ }
1386
+ function checkCustomConstraintSemantics(ctx, fieldName, constraints) {
1387
+ if (ctx.extensionRegistry === void 0) {
1388
+ return;
1389
+ }
1390
+ const strongestByKey = /* @__PURE__ */ new Map();
1391
+ const lowerByFamily = /* @__PURE__ */ new Map();
1392
+ const upperByFamily = /* @__PURE__ */ new Map();
1393
+ for (const constraint of constraints) {
1394
+ if (constraint.constraintKind !== "custom") {
1395
+ continue;
1396
+ }
1397
+ const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
1398
+ if (registration?.comparePayloads === void 0 || registration.semanticRole === void 0) {
1399
+ continue;
1400
+ }
1401
+ const entry = {
1402
+ constraint,
1403
+ comparePayloads: registration.comparePayloads,
1404
+ role: registration.semanticRole
1405
+ };
1406
+ const familyKey = `${registration.semanticRole.family}:${pathKey(constraint)}`;
1407
+ const boundKey = `${familyKey}:${registration.semanticRole.bound}`;
1408
+ const previous = strongestByKey.get(boundKey);
1409
+ if (previous !== void 0) {
1410
+ const strength = compareCustomConstraintStrength(entry, previous);
1411
+ if (Number.isNaN(strength)) {
1412
+ addContradiction(
1413
+ ctx,
1414
+ `Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} conflicts with ${describeCustomConstraintTag(previous.constraint)}`,
1415
+ constraint.provenance,
1416
+ previous.constraint.provenance
1417
+ );
1418
+ continue;
1419
+ }
1420
+ if (strength < 0) {
1421
+ addConstraintBroadening(
1422
+ ctx,
1423
+ `Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} is broader than earlier ${describeCustomConstraintTag(previous.constraint)}. Constraints can only narrow.`,
1424
+ constraint.provenance,
1425
+ previous.constraint.provenance
1426
+ );
1427
+ continue;
1428
+ }
1429
+ if (strength > 0) {
1430
+ strongestByKey.set(boundKey, entry);
1431
+ }
1432
+ } else {
1433
+ strongestByKey.set(boundKey, entry);
1434
+ }
1435
+ if (registration.semanticRole.bound === "lower") {
1436
+ lowerByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
1437
+ } else if (registration.semanticRole.bound === "upper") {
1438
+ upperByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
1439
+ }
1440
+ }
1441
+ for (const [familyKey, lower] of lowerByFamily) {
1442
+ const upper = upperByFamily.get(familyKey);
1443
+ if (upper === void 0) {
1444
+ continue;
1445
+ }
1446
+ if (!customConstraintsContradict(lower, upper)) {
1447
+ continue;
1448
+ }
1449
+ addContradiction(
1450
+ ctx,
1451
+ `Field "${formatPathTargetFieldName(fieldName, lower.constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(lower.constraint)} contradicts ${describeCustomConstraintTag(upper.constraint)}`,
1452
+ lower.constraint.provenance,
1453
+ upper.constraint.provenance
1454
+ );
1455
+ }
1456
+ }
1285
1457
  function checkNumericContradictions(ctx, fieldName, constraints) {
1286
1458
  const min = findNumeric(constraints, "minimum");
1287
1459
  const max = findNumeric(constraints, "maximum");
@@ -1593,8 +1765,30 @@ function checkCustomConstraint(ctx, fieldName, type, constraint) {
1593
1765
  );
1594
1766
  return;
1595
1767
  }
1596
- if (registration.applicableTypes === null) return;
1597
- if (!registration.applicableTypes.includes(type.kind)) {
1768
+ const normalizedTagName = constraint.provenance.tagName === void 0 ? void 0 : normalizeConstraintTagName(constraint.provenance.tagName.replace(/^@/, ""));
1769
+ if (normalizedTagName !== void 0) {
1770
+ const tagRegistration = ctx.extensionRegistry.findConstraintTag(normalizedTagName);
1771
+ const extensionId = getExtensionIdFromConstraintId(constraint.constraintId);
1772
+ if (extensionId !== null && tagRegistration?.extensionId === extensionId && tagRegistration.registration.constraintName === registration.constraintName && tagRegistration.registration.isApplicableToType?.(type) === false) {
1773
+ addTypeMismatch(
1774
+ ctx,
1775
+ `Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
1776
+ constraint.provenance
1777
+ );
1778
+ return;
1779
+ }
1780
+ }
1781
+ if (registration.applicableTypes === null) {
1782
+ if (registration.isApplicableToType?.(type) === false) {
1783
+ addTypeMismatch(
1784
+ ctx,
1785
+ `Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
1786
+ constraint.provenance
1787
+ );
1788
+ }
1789
+ return;
1790
+ }
1791
+ if (!registration.applicableTypes.includes(type.kind) || registration.isApplicableToType?.(type) === false) {
1598
1792
  addTypeMismatch(
1599
1793
  ctx,
1600
1794
  `Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
@@ -1625,6 +1819,7 @@ function validateConstraints(ctx, name, type, constraints) {
1625
1819
  checkAllowedMembersContradiction(ctx, name, constraints);
1626
1820
  checkConstContradictions(ctx, name, constraints);
1627
1821
  checkConstraintBroadening(ctx, name, constraints);
1822
+ checkCustomConstraintSemantics(ctx, name, constraints);
1628
1823
  checkTypeApplicability(ctx, name, type, constraints);
1629
1824
  }
1630
1825
  function validateElement(ctx, element) {